999 lines
35 KiB
JavaScript
999 lines
35 KiB
JavaScript
|
|
(function () {
|
|||
|
|
"use strict";
|
|||
|
|
|
|||
|
|
const api = {
|
|||
|
|
getState: () => ({}),
|
|||
|
|
getElements: () => ({}),
|
|||
|
|
getSelectedMonth: () => null,
|
|||
|
|
getSelectedDayFilterContext: () => null,
|
|||
|
|
clearSelectedDayFilter: () => {},
|
|||
|
|
toggleDayFilterEntry: () => {},
|
|||
|
|
toggleDayRangeFilter: () => {},
|
|||
|
|
getMonthSubtitle: () => "",
|
|||
|
|
getMonthDayLinkRows: () => [],
|
|||
|
|
buildDecanTarotRowsForMonth: () => [],
|
|||
|
|
buildHolidayList: () => [],
|
|||
|
|
matchesSearch: () => true,
|
|||
|
|
eventSearchText: () => "",
|
|||
|
|
holidaySearchText: () => "",
|
|||
|
|
getDisplayTarotName: (cardName) => cardName || "",
|
|||
|
|
cap: (value) => String(value || "").trim(),
|
|||
|
|
formatGregorianReferenceDate: () => "--",
|
|||
|
|
getDaysInMonth: () => null,
|
|||
|
|
getMonthStartWeekday: () => "--",
|
|||
|
|
getGregorianMonthStartDate: () => null,
|
|||
|
|
formatCalendarDateFromGregorian: () => "--",
|
|||
|
|
parseMonthDayToken: () => null,
|
|||
|
|
parseMonthDayTokensFromText: () => [],
|
|||
|
|
parseMonthDayStartToken: () => null,
|
|||
|
|
parseDayRangeFromText: () => null,
|
|||
|
|
parseMonthRange: () => "",
|
|||
|
|
formatIsoDate: () => "",
|
|||
|
|
resolveHolidayGregorianDate: () => null,
|
|||
|
|
isMonthDayInRange: () => false,
|
|||
|
|
intersectDateRanges: () => null,
|
|||
|
|
getGregorianReferenceDateForCalendarMonth: () => null,
|
|||
|
|
normalizeCalendarText: (value) => String(value || "").trim().toLowerCase(),
|
|||
|
|
findGodIdByName: () => null
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function init(config) {
|
|||
|
|
Object.assign(api, config || {});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getState() {
|
|||
|
|
return api.getState?.() || {};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function planetLabel(planetId) {
|
|||
|
|
if (!planetId) {
|
|||
|
|
return "Planet";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const planet = getState().planetsById?.get(planetId);
|
|||
|
|
if (!planet) {
|
|||
|
|
return api.cap(planetId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return `${planet.symbol || ""} ${planet.name || api.cap(planetId)}`.trim();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function zodiacLabel(signId) {
|
|||
|
|
if (!signId) {
|
|||
|
|
return "Zodiac";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const sign = getState().signsById?.get(signId);
|
|||
|
|
if (!sign) {
|
|||
|
|
return api.cap(signId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return `${sign.symbol || ""} ${sign.name || api.cap(signId)}`.trim();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function godLabel(godId, godName) {
|
|||
|
|
if (godName) {
|
|||
|
|
return godName;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!godId) {
|
|||
|
|
return "Deity";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const god = getState().godsById?.get(godId);
|
|||
|
|
return god?.name || api.cap(godId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function hebrewLabel(hebrewLetterId) {
|
|||
|
|
if (!hebrewLetterId) {
|
|||
|
|
return "Hebrew Letter";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const letter = getState().hebrewById?.get(hebrewLetterId);
|
|||
|
|
if (!letter) {
|
|||
|
|
return api.cap(hebrewLetterId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return `${letter.char || ""} ${letter.name || api.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 explicitTrumpNumber = Number(associations.tarotTrumpNumber);
|
|||
|
|
const tarotTrumpNumber = Number.isFinite(explicitTrumpNumber) ? explicitTrumpNumber : null;
|
|||
|
|
const tarotLabel = api.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 renderFactsCard(month) {
|
|||
|
|
const currentState = getState();
|
|||
|
|
const monthOrder = Number(month?.order);
|
|||
|
|
const daysInMonth = api.getDaysInMonth(currentState.selectedYear, monthOrder);
|
|||
|
|
const hoursInMonth = Number.isFinite(daysInMonth) ? daysInMonth * 24 : null;
|
|||
|
|
const firstWeekday = Number.isFinite(monthOrder)
|
|||
|
|
? api.getMonthStartWeekday(currentState.selectedYear, monthOrder)
|
|||
|
|
: "--";
|
|||
|
|
const gregorianStartDate = api.getGregorianMonthStartDate(monthOrder);
|
|||
|
|
const hebrewStartReference = api.formatCalendarDateFromGregorian(gregorianStartDate, "hebrew");
|
|||
|
|
const islamicStartReference = api.formatCalendarDateFromGregorian(gregorianStartDate, "islamic");
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Month Facts</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<dl class="alpha-dl">
|
|||
|
|
<dt>Year</dt><dd>${currentState.selectedYear}</dd>
|
|||
|
|
<dt>Start Date (Gregorian)</dt><dd>${api.formatGregorianReferenceDate(gregorianStartDate)}</dd>
|
|||
|
|
<dt>Days</dt><dd>${daysInMonth ?? "--"}</dd>
|
|||
|
|
<dt>Hours</dt><dd>${hoursInMonth ?? "--"}</dd>
|
|||
|
|
<dt>Starts On</dt><dd>${firstWeekday}</dd>
|
|||
|
|
<dt>Hebrew On 1st</dt><dd>${hebrewStartReference}</dd>
|
|||
|
|
<dt>Islamic On 1st</dt><dd>${islamicStartReference}</dd>
|
|||
|
|
<dt>North Season</dt><dd>${month.seasonNorth || "--"}</dd>
|
|||
|
|
<dt>South Season</dt><dd>${month.seasonSouth || "--"}</dd>
|
|||
|
|
</dl>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderAssociationsCard(month) {
|
|||
|
|
const monthOrder = Number(month?.order);
|
|||
|
|
const associations = {
|
|||
|
|
...(month?.associations || {}),
|
|||
|
|
...(Number.isFinite(monthOrder) ? { numberValue: Math.trunc(monthOrder) } : {})
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Associations</strong>
|
|||
|
|
<div class="planet-text">${month.coreTheme || "--"}</div>
|
|||
|
|
${buildAssociationButtons(associations)}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderEventsCard(month) {
|
|||
|
|
const currentState = getState();
|
|||
|
|
const allEvents = Array.isArray(month?.events) ? month.events : [];
|
|||
|
|
if (!allEvents.length) {
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Monthly Events</strong>
|
|||
|
|
<div class="planet-text">No monthly events listed.</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const selectedDay = api.getSelectedDayFilterContext(month);
|
|||
|
|
|
|||
|
|
function eventMatchesDay(event) {
|
|||
|
|
if (!selectedDay) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return selectedDay.entries.some((entry) => {
|
|||
|
|
const targetDate = entry.gregorianDate;
|
|||
|
|
const targetMonth = targetDate?.getMonth() + 1;
|
|||
|
|
const targetDayNo = targetDate?.getDate();
|
|||
|
|
|
|||
|
|
const explicitDate = api.parseMonthDayToken(event?.date);
|
|||
|
|
if (explicitDate && Number.isFinite(targetMonth) && Number.isFinite(targetDayNo)) {
|
|||
|
|
return explicitDate.month === targetMonth && explicitDate.day === targetDayNo;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rangeTokens = api.parseMonthDayTokensFromText(event?.dateRange || event?.dateText || "");
|
|||
|
|
if (rangeTokens.length >= 2 && Number.isFinite(targetMonth) && Number.isFinite(targetDayNo)) {
|
|||
|
|
const start = rangeTokens[0];
|
|||
|
|
const end = rangeTokens[1];
|
|||
|
|
return api.isMonthDayInRange(targetMonth, targetDayNo, start.month, start.day, end.month, end.day);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dayRange = api.parseDayRangeFromText(event?.date || event?.dateRange || event?.dateText || "");
|
|||
|
|
if (dayRange) {
|
|||
|
|
return entry.dayNumber >= dayRange.startDay && entry.dayNumber <= dayRange.endDay;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dayFiltered = allEvents.filter((event) => eventMatchesDay(event));
|
|||
|
|
const events = currentState.searchQuery
|
|||
|
|
? dayFiltered.filter((event) => api.matchesSearch(api.eventSearchText(event)))
|
|||
|
|
: dayFiltered;
|
|||
|
|
|
|||
|
|
if (!events.length) {
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Monthly Events</strong>
|
|||
|
|
<div class="planet-text">No monthly events match current search.</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rows = events.map((event) => {
|
|||
|
|
const dateText = event?.date || event?.dateRange || "--";
|
|||
|
|
return `
|
|||
|
|
<div class="cal-item-row">
|
|||
|
|
<div class="cal-item-head">
|
|||
|
|
<span class="cal-item-name">${event?.name || "Untitled"}</span>
|
|||
|
|
<span class="planet-list-meta">${dateText}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="planet-text">${event?.description || ""}</div>
|
|||
|
|
${buildAssociationButtons(event?.associations)}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}).join("");
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Monthly Events</strong>
|
|||
|
|
<div class="cal-item-stack">${rows}</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderHolidaysCard(month, title = "Holiday Repository") {
|
|||
|
|
const currentState = getState();
|
|||
|
|
const allHolidays = api.buildHolidayList(month);
|
|||
|
|
if (!allHolidays.length) {
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>${title}</strong>
|
|||
|
|
<div class="planet-text">No holidays listed in the repository for this month.</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const selectedDay = api.getSelectedDayFilterContext(month);
|
|||
|
|
|
|||
|
|
function holidayMatchesDay(holiday) {
|
|||
|
|
if (!selectedDay) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return selectedDay.entries.some((entry) => {
|
|||
|
|
const targetDate = entry.gregorianDate;
|
|||
|
|
const targetMonth = targetDate?.getMonth() + 1;
|
|||
|
|
const targetDayNo = targetDate?.getDate();
|
|||
|
|
|
|||
|
|
const exactResolved = api.resolveHolidayGregorianDate(holiday);
|
|||
|
|
if (exactResolved instanceof Date && !Number.isNaN(exactResolved.getTime()) && targetDate instanceof Date) {
|
|||
|
|
return api.formatIsoDate(exactResolved) === api.formatIsoDate(targetDate);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (currentState.selectedCalendar === "gregorian" && Number.isFinite(targetMonth) && Number.isFinite(targetDayNo)) {
|
|||
|
|
const tokens = api.parseMonthDayTokensFromText(holiday?.dateText || holiday?.dateRange || "");
|
|||
|
|
if (tokens.length >= 2) {
|
|||
|
|
const start = tokens[0];
|
|||
|
|
const end = tokens[1];
|
|||
|
|
return api.isMonthDayInRange(targetMonth, targetDayNo, start.month, start.day, end.month, end.day);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (tokens.length === 1) {
|
|||
|
|
const single = tokens[0];
|
|||
|
|
return single.month === targetMonth && single.day === targetDayNo;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const direct = api.parseMonthDayStartToken(holiday?.monthDayStart || holiday?.dateText || "");
|
|||
|
|
if (direct) {
|
|||
|
|
return direct.month === targetMonth && direct.day === targetDayNo;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (Number.isFinite(Number(holiday?.day))) {
|
|||
|
|
return Number(holiday.day) === entry.dayNumber;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const localRange = api.parseDayRangeFromText(holiday?.dateText || holiday?.dateRange || "");
|
|||
|
|
if (localRange) {
|
|||
|
|
return entry.dayNumber >= localRange.startDay && entry.dayNumber <= localRange.endDay;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const dayFiltered = allHolidays.filter((holiday) => holidayMatchesDay(holiday));
|
|||
|
|
const holidays = currentState.searchQuery
|
|||
|
|
? dayFiltered.filter((holiday) => api.matchesSearch(api.holidaySearchText(holiday)))
|
|||
|
|
: dayFiltered;
|
|||
|
|
|
|||
|
|
if (!holidays.length) {
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>${title}</strong>
|
|||
|
|
<div class="planet-text">No holidays match current filters.</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rows = holidays.map((holiday) => {
|
|||
|
|
const dateText = holiday?.dateText || holiday?.dateRange || holiday?.date || "--";
|
|||
|
|
return `
|
|||
|
|
<div class="cal-item-row">
|
|||
|
|
<div class="cal-item-head">
|
|||
|
|
<span class="cal-item-name">${holiday?.name || "Untitled"}</span>
|
|||
|
|
<span class="planet-list-meta">${dateText}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="planet-text">${holiday?.description || holiday?.kind || ""}</div>
|
|||
|
|
${buildAssociationButtons(holiday?.associations)}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}).join("");
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>${title}</strong>
|
|||
|
|
<div class="cal-item-stack">${rows}</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function findSignIdByAstrologyName(name) {
|
|||
|
|
const token = api.normalizeCalendarText(name);
|
|||
|
|
if (!token) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const [signId, sign] of getState().signsById || []) {
|
|||
|
|
const idToken = api.normalizeCalendarText(signId);
|
|||
|
|
const nameToken = api.normalizeCalendarText(sign?.name?.en || sign?.name || "");
|
|||
|
|
if (token === idToken || token === nameToken) {
|
|||
|
|
return signId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildMajorArcanaRowsForMonth(month) {
|
|||
|
|
const currentState = getState();
|
|||
|
|
if (currentState.selectedCalendar !== "gregorian") {
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const monthOrder = Number(month?.order);
|
|||
|
|
if (!Number.isFinite(monthOrder)) {
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const monthStart = new Date(currentState.selectedYear, monthOrder - 1, 1, 12, 0, 0, 0);
|
|||
|
|
const monthEnd = new Date(currentState.selectedYear, monthOrder, 0, 12, 0, 0, 0);
|
|||
|
|
const rows = [];
|
|||
|
|
|
|||
|
|
currentState.hebrewById?.forEach((letter) => {
|
|||
|
|
const astrologyType = api.normalizeCalendarText(letter?.astrology?.type);
|
|||
|
|
if (astrologyType !== "zodiac") {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const signId = findSignIdByAstrologyName(letter?.astrology?.name);
|
|||
|
|
const sign = signId ? currentState.signsById?.get(signId) : null;
|
|||
|
|
if (!sign) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const startToken = api.parseMonthDayToken(sign?.start);
|
|||
|
|
const endToken = api.parseMonthDayToken(sign?.end);
|
|||
|
|
if (!startToken || !endToken) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const spanStart = new Date(currentState.selectedYear, startToken.month - 1, startToken.day, 12, 0, 0, 0);
|
|||
|
|
const spanEnd = new Date(currentState.selectedYear, endToken.month - 1, endToken.day, 12, 0, 0, 0);
|
|||
|
|
const wraps = spanEnd.getTime() < spanStart.getTime();
|
|||
|
|
|
|||
|
|
const segments = wraps
|
|||
|
|
? [
|
|||
|
|
{
|
|||
|
|
start: spanStart,
|
|||
|
|
end: new Date(currentState.selectedYear, 11, 31, 12, 0, 0, 0)
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
start: new Date(currentState.selectedYear, 0, 1, 12, 0, 0, 0),
|
|||
|
|
end: spanEnd
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
: [{ start: spanStart, end: spanEnd }];
|
|||
|
|
|
|||
|
|
segments.forEach((segment) => {
|
|||
|
|
const overlap = api.intersectDateRanges(segment.start, segment.end, monthStart, monthEnd);
|
|||
|
|
if (!overlap) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const rangeStartDay = overlap.start.getDate();
|
|||
|
|
const rangeEndDay = overlap.end.getDate();
|
|||
|
|
const cardName = String(letter?.tarot?.card || "").trim();
|
|||
|
|
const trumpNumber = Number(letter?.tarot?.trumpNumber);
|
|||
|
|
if (!cardName) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
rows.push({
|
|||
|
|
id: `${signId}-${rangeStartDay}-${rangeEndDay}`,
|
|||
|
|
signId,
|
|||
|
|
signName: sign?.name?.en || sign?.name || signId,
|
|||
|
|
signSymbol: sign?.symbol || "",
|
|||
|
|
cardName,
|
|||
|
|
trumpNumber: Number.isFinite(trumpNumber) ? Math.trunc(trumpNumber) : null,
|
|||
|
|
hebrewLetterId: String(letter?.hebrewLetterId || "").trim(),
|
|||
|
|
hebrewLetterName: String(letter?.name || "").trim(),
|
|||
|
|
hebrewLetterChar: String(letter?.char || "").trim(),
|
|||
|
|
dayStart: rangeStartDay,
|
|||
|
|
dayEnd: rangeEndDay,
|
|||
|
|
rangeLabel: `${month?.name || "Month"} ${rangeStartDay}-${rangeEndDay}`
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
rows.sort((left, right) => {
|
|||
|
|
if (left.dayStart !== right.dayStart) {
|
|||
|
|
return left.dayStart - right.dayStart;
|
|||
|
|
}
|
|||
|
|
return left.cardName.localeCompare(right.cardName);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return rows;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderMajorArcanaCard(month) {
|
|||
|
|
const selectedDay = api.getSelectedDayFilterContext(month);
|
|||
|
|
const allRows = buildMajorArcanaRowsForMonth(month);
|
|||
|
|
|
|||
|
|
const rows = selectedDay
|
|||
|
|
? allRows.filter((row) => selectedDay.entries.some((entry) => entry.dayNumber >= row.dayStart && entry.dayNumber <= row.dayEnd))
|
|||
|
|
: allRows;
|
|||
|
|
|
|||
|
|
if (!rows.length) {
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Major Arcana Windows</strong>
|
|||
|
|
<div class="planet-text">No major arcana windows for this month.</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const list = rows.map((row) => {
|
|||
|
|
const label = row.hebrewLetterId
|
|||
|
|
? `${row.hebrewLetterChar ? `${row.hebrewLetterChar} ` : ""}${row.hebrewLetterName || row.hebrewLetterId}`
|
|||
|
|
: "--";
|
|||
|
|
const displayCardName = api.getDisplayTarotName(row.cardName, row.trumpNumber);
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="cal-item-row">
|
|||
|
|
<div class="cal-item-head">
|
|||
|
|
<span class="cal-item-name">${displayCardName}${row.trumpNumber != null ? ` · Trump ${row.trumpNumber}` : ""}</span>
|
|||
|
|
<span class="planet-list-meta">${row.rangeLabel}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="planet-list-meta">${row.signSymbol} ${row.signName} · Hebrew: ${label}</div>
|
|||
|
|
<div class="alpha-nav-btns">
|
|||
|
|
<button class="alpha-nav-btn" data-nav="calendar-day-range" data-range-start="${row.dayStart}" data-range-end="${row.dayEnd}">${row.rangeLabel} ↗</button>
|
|||
|
|
<button class="alpha-nav-btn" data-nav="tarot-card" data-card-name="${row.cardName}" data-trump-number="${row.trumpNumber ?? ""}">${displayCardName} ↗</button>
|
|||
|
|
${row.hebrewLetterId ? `<button class="alpha-nav-btn" data-nav="alphabet" data-alphabet="hebrew" data-hebrew-letter-id="${row.hebrewLetterId}">${label} ↗</button>` : ""}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}).join("");
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Major Arcana Windows</strong>
|
|||
|
|
<div class="cal-item-stack">${list}</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderDecanTarotCard(month) {
|
|||
|
|
const selectedDay = api.getSelectedDayFilterContext(month);
|
|||
|
|
const allRows = api.buildDecanTarotRowsForMonth(month);
|
|||
|
|
const rows = selectedDay
|
|||
|
|
? allRows.filter((row) => selectedDay.entries.some((entry) => {
|
|||
|
|
const targetDate = entry.gregorianDate;
|
|||
|
|
if (!(targetDate instanceof Date) || Number.isNaN(targetDate.getTime())) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const targetMonth = targetDate.getMonth() + 1;
|
|||
|
|
const targetDayNo = targetDate.getDate();
|
|||
|
|
return api.isMonthDayInRange(
|
|||
|
|
targetMonth,
|
|||
|
|
targetDayNo,
|
|||
|
|
row.startMonth,
|
|||
|
|
row.startDay,
|
|||
|
|
row.endMonth,
|
|||
|
|
row.endDay
|
|||
|
|
);
|
|||
|
|
}))
|
|||
|
|
: allRows;
|
|||
|
|
|
|||
|
|
if (!rows.length) {
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Decan Tarot Windows</strong>
|
|||
|
|
<div class="planet-text">No decan tarot windows for this month.</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const list = rows.map((row) => {
|
|||
|
|
const displayCardName = api.getDisplayTarotName(row.cardName);
|
|||
|
|
return `
|
|||
|
|
<div class="cal-item-row">
|
|||
|
|
<div class="cal-item-head">
|
|||
|
|
<span class="cal-item-name">${row.signSymbol} ${row.signName} · Decan ${row.decanIndex}</span>
|
|||
|
|
<span class="planet-list-meta">${row.startDegree}°–${row.endDegree}° · ${row.dateRange}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="alpha-nav-btns">
|
|||
|
|
<button class="alpha-nav-btn" data-nav="tarot-card" data-card-name="${row.cardName}">${displayCardName} ↗</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}).join("");
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Decan Tarot Windows</strong>
|
|||
|
|
<div class="cal-item-stack">${list}</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderDayLinksCard(month) {
|
|||
|
|
const rows = api.getMonthDayLinkRows(month);
|
|||
|
|
if (!rows.length) {
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const selectedContext = api.getSelectedDayFilterContext(month);
|
|||
|
|
const selectedDaySet = selectedContext?.dayNumbers || new Set();
|
|||
|
|
const selectedDays = selectedContext?.entries?.map((entry) => entry.dayNumber) || [];
|
|||
|
|
const selectedSummary = selectedDays.length ? selectedDays.join(", ") : "";
|
|||
|
|
|
|||
|
|
const links = rows.map((row) => {
|
|||
|
|
if (!row.isResolved) {
|
|||
|
|
return `<span class="planet-list-meta">${row.day}</span>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const isSelected = selectedDaySet.has(Number(row.day));
|
|||
|
|
return `<button class="alpha-nav-btn${isSelected ? " is-selected" : ""}" data-nav="calendar-day" data-day-number="${row.day}" data-gregorian-date="${row.gregorianDate}" aria-pressed="${isSelected ? "true" : "false"}" title="Filter this month by day ${row.day}">${row.day}</button>`;
|
|||
|
|
}).join("");
|
|||
|
|
|
|||
|
|
const clearButton = selectedContext
|
|||
|
|
? '<button class="alpha-nav-btn" data-nav="calendar-day-clear" type="button">Show All Days</button>'
|
|||
|
|
: "";
|
|||
|
|
|
|||
|
|
const helperText = selectedContext
|
|||
|
|
? `<div class="planet-list-meta">Filtered to days: ${selectedSummary}</div>`
|
|||
|
|
: "";
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Day Links</strong>
|
|||
|
|
<div class="planet-text">Filter this month to events, holidays, and data connected to a specific day.</div>
|
|||
|
|
${helperText}
|
|||
|
|
<div class="alpha-nav-btns">${links}</div>
|
|||
|
|
${clearButton ? `<div class="alpha-nav-btns">${clearButton}</div>` : ""}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderHebrewMonthDetail(month) {
|
|||
|
|
const currentState = getState();
|
|||
|
|
const gregorianStartDate = api.getGregorianReferenceDateForCalendarMonth(month);
|
|||
|
|
const factsRows = [
|
|||
|
|
["Hebrew Name", month.nativeName || "--"],
|
|||
|
|
["Month Order", month.leapYearOnly ? `${month.order} (leap year only)` : String(month.order)],
|
|||
|
|
["Gregorian Reference Year", String(currentState.selectedYear)],
|
|||
|
|
["Month Start (Gregorian)", api.formatGregorianReferenceDate(gregorianStartDate)],
|
|||
|
|
["Days", month.daysVariant ? `${month.days}–${month.daysVariant} (varies)` : String(month.days || "--")],
|
|||
|
|
["Season", month.season || "--"],
|
|||
|
|
["Zodiac Sign", api.cap(month.zodiacSign) || "--"],
|
|||
|
|
["Tribe of Israel", month.tribe || "--"],
|
|||
|
|
["Sense", month.sense || "--"],
|
|||
|
|
["Hebrew Letter", month.hebrewLetter || "--"]
|
|||
|
|
].map(([dt, dd]) => `<dt>${dt}</dt><dd>${dd}</dd>`).join("");
|
|||
|
|
|
|||
|
|
const monthOrder = Number(month?.order);
|
|||
|
|
const navButtons = buildAssociationButtons({
|
|||
|
|
...(month?.associations || {}),
|
|||
|
|
...(Number.isFinite(monthOrder) ? { numberValue: Math.trunc(monthOrder) } : {})
|
|||
|
|
});
|
|||
|
|
const connectionsCard = navButtons
|
|||
|
|
? `<div class="planet-meta-card"><strong>Connections</strong>${navButtons}</div>`
|
|||
|
|
: "";
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-grid">
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Month Facts</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<dl class="alpha-dl">${factsRows}</dl>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
${connectionsCard}
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>About ${month.name}</strong>
|
|||
|
|
<div class="planet-text">${month.description || "--"}</div>
|
|||
|
|
</div>
|
|||
|
|
${renderDayLinksCard(month)}
|
|||
|
|
${renderHolidaysCard(month, "Holiday Repository")}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderIslamicMonthDetail(month) {
|
|||
|
|
const currentState = getState();
|
|||
|
|
const gregorianStartDate = api.getGregorianReferenceDateForCalendarMonth(month);
|
|||
|
|
const factsRows = [
|
|||
|
|
["Arabic Name", month.nativeName || "--"],
|
|||
|
|
["Month Order", String(month.order)],
|
|||
|
|
["Gregorian Reference Year", String(currentState.selectedYear)],
|
|||
|
|
["Month Start (Gregorian)", api.formatGregorianReferenceDate(gregorianStartDate)],
|
|||
|
|
["Meaning", month.meaning || "--"],
|
|||
|
|
["Days", month.daysVariant ? `${month.days}–${month.daysVariant} (varies)` : String(month.days || "--")],
|
|||
|
|
["Sacred Month", month.sacred ? "Yes - warfare prohibited" : "No"]
|
|||
|
|
].map(([dt, dd]) => `<dt>${dt}</dt><dd>${dd}</dd>`).join("");
|
|||
|
|
|
|||
|
|
const monthOrder = Number(month?.order);
|
|||
|
|
const hasNumberLink = Number.isFinite(monthOrder) && monthOrder >= 0;
|
|||
|
|
const navButtons = hasNumberLink
|
|||
|
|
? buildAssociationButtons({ numberValue: Math.trunc(monthOrder) })
|
|||
|
|
: "";
|
|||
|
|
const connectionsCard = hasNumberLink
|
|||
|
|
? `<div class="planet-meta-card"><strong>Connections</strong>${navButtons}</div>`
|
|||
|
|
: "";
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-grid">
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Month Facts</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<dl class="alpha-dl">${factsRows}</dl>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
${connectionsCard}
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>About ${month.name}</strong>
|
|||
|
|
<div class="planet-text">${month.description || "--"}</div>
|
|||
|
|
</div>
|
|||
|
|
${renderDayLinksCard(month)}
|
|||
|
|
${renderHolidaysCard(month, "Holiday Repository")}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildWheelDeityButtons(deities) {
|
|||
|
|
const buttons = [];
|
|||
|
|
(Array.isArray(deities) ? deities : []).forEach((rawName) => {
|
|||
|
|
const cleanName = String(rawName || "").replace(/\s*\/.*$/, "").replace(/\s*\(.*\)$/, "").trim();
|
|||
|
|
const godId = api.findGodIdByName(cleanName) || api.findGodIdByName(rawName);
|
|||
|
|
if (!godId) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const god = getState().godsById?.get(godId);
|
|||
|
|
const label = god?.name || cleanName;
|
|||
|
|
buttons.push(`<button class="alpha-nav-btn" data-nav="god" data-god-id="${godId}" data-god-name="${label}">${label} ↗</button>`);
|
|||
|
|
});
|
|||
|
|
return buttons;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderWheelMonthDetail(month) {
|
|||
|
|
const currentState = getState();
|
|||
|
|
const gregorianStartDate = api.getGregorianReferenceDateForCalendarMonth(month);
|
|||
|
|
const assoc = month?.associations;
|
|||
|
|
const themes = Array.isArray(assoc?.themes) ? assoc.themes.join(", ") : "--";
|
|||
|
|
const deities = Array.isArray(assoc?.deities) ? assoc.deities.join(", ") : "--";
|
|||
|
|
const colors = Array.isArray(assoc?.colors) ? assoc.colors.join(", ") : "--";
|
|||
|
|
const herbs = Array.isArray(assoc?.herbs) ? assoc.herbs.join(", ") : "--";
|
|||
|
|
|
|||
|
|
const factsRows = [
|
|||
|
|
["Date", month.date || "--"],
|
|||
|
|
["Type", api.cap(month.type) || "--"],
|
|||
|
|
["Gregorian Reference Year", String(currentState.selectedYear)],
|
|||
|
|
["Start (Gregorian)", api.formatGregorianReferenceDate(gregorianStartDate)],
|
|||
|
|
["Season", month.season || "--"],
|
|||
|
|
["Element", api.cap(month.element) || "--"],
|
|||
|
|
["Direction", assoc?.direction || "--"]
|
|||
|
|
].map(([dt, dd]) => `<dt>${dt}</dt><dd>${dd}</dd>`).join("");
|
|||
|
|
|
|||
|
|
const assocRows = [
|
|||
|
|
["Themes", themes],
|
|||
|
|
["Deities", deities],
|
|||
|
|
["Colors", colors],
|
|||
|
|
["Herbs", herbs]
|
|||
|
|
].map(([dt, dd]) => `<dt>${dt}</dt><dd class="planet-text">${dd}</dd>`).join("");
|
|||
|
|
|
|||
|
|
const deityButtons = buildWheelDeityButtons(assoc?.deities);
|
|||
|
|
const deityLinksCard = deityButtons.length
|
|||
|
|
? `<div class="planet-meta-card"><strong>Linked Deities</strong><div class="alpha-nav-btns">${deityButtons.join("")}</div></div>`
|
|||
|
|
: "";
|
|||
|
|
|
|||
|
|
const monthOrder = Number(month?.order);
|
|||
|
|
const hasNumberLink = Number.isFinite(monthOrder) && monthOrder >= 0;
|
|||
|
|
const numberButtons = hasNumberLink
|
|||
|
|
? buildAssociationButtons({ numberValue: Math.trunc(monthOrder) })
|
|||
|
|
: "";
|
|||
|
|
const numberLinksCard = hasNumberLink
|
|||
|
|
? `<div class="planet-meta-card"><strong>Connections</strong>${numberButtons}</div>`
|
|||
|
|
: "";
|
|||
|
|
|
|||
|
|
return `
|
|||
|
|
<div class="planet-meta-grid">
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Sabbat Facts</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<dl class="alpha-dl">${factsRows}</dl>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>About ${month.name}</strong>
|
|||
|
|
<div class="planet-text">${month.description || "--"}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="planet-meta-card">
|
|||
|
|
<strong>Associations</strong>
|
|||
|
|
<div class="planet-text">
|
|||
|
|
<dl class="alpha-dl">${assocRows}</dl>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
${renderDayLinksCard(month)}
|
|||
|
|
${numberLinksCard}
|
|||
|
|
${deityLinksCard}
|
|||
|
|
${renderHolidaysCard(month, "Holiday Repository")}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
}));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (navType === "calendar-day" && button.dataset.dayNumber) {
|
|||
|
|
const month = api.getSelectedMonth();
|
|||
|
|
const dayNumber = Number(button.dataset.dayNumber);
|
|||
|
|
if (!month || !Number.isFinite(dayNumber)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
api.toggleDayFilterEntry(month, dayNumber, button.dataset.gregorianDate);
|
|||
|
|
renderDetail(api.getElements());
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (navType === "calendar-day-range" && button.dataset.rangeStart && button.dataset.rangeEnd) {
|
|||
|
|
const month = api.getSelectedMonth();
|
|||
|
|
if (!month) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
api.toggleDayRangeFilter(month, Number(button.dataset.rangeStart), Number(button.dataset.rangeEnd));
|
|||
|
|
renderDetail(api.getElements());
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (navType === "calendar-day-clear") {
|
|||
|
|
api.clearSelectedDayFilter();
|
|||
|
|
renderDetail(api.getElements());
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderDetail(elements) {
|
|||
|
|
const { detailNameEl, detailSubEl, detailBodyEl } = elements || {};
|
|||
|
|
if (!detailBodyEl || !detailNameEl || !detailSubEl) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const month = api.getSelectedMonth();
|
|||
|
|
if (!month) {
|
|||
|
|
detailNameEl.textContent = "--";
|
|||
|
|
detailSubEl.textContent = "Select a month to explore";
|
|||
|
|
detailBodyEl.innerHTML = "";
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
detailNameEl.textContent = month.name || month.id;
|
|||
|
|
|
|||
|
|
const currentState = getState();
|
|||
|
|
if (currentState.selectedCalendar === "gregorian") {
|
|||
|
|
detailSubEl.textContent = `${api.parseMonthRange(month)} · ${month.coreTheme || "Month correspondences"}`;
|
|||
|
|
detailBodyEl.innerHTML = `
|
|||
|
|
<div class="planet-meta-grid">
|
|||
|
|
${renderFactsCard(month)}
|
|||
|
|
${renderDayLinksCard(month)}
|
|||
|
|
${renderAssociationsCard(month)}
|
|||
|
|
${renderMajorArcanaCard(month)}
|
|||
|
|
${renderDecanTarotCard(month)}
|
|||
|
|
${renderEventsCard(month)}
|
|||
|
|
${renderHolidaysCard(month, "Holiday Repository")}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
} else if (currentState.selectedCalendar === "hebrew") {
|
|||
|
|
detailSubEl.textContent = api.getMonthSubtitle(month);
|
|||
|
|
detailBodyEl.innerHTML = renderHebrewMonthDetail(month);
|
|||
|
|
} else if (currentState.selectedCalendar === "islamic") {
|
|||
|
|
detailSubEl.textContent = api.getMonthSubtitle(month);
|
|||
|
|
detailBodyEl.innerHTML = renderIslamicMonthDetail(month);
|
|||
|
|
} else {
|
|||
|
|
detailSubEl.textContent = api.getMonthSubtitle(month);
|
|||
|
|
detailBodyEl.innerHTML = renderWheelMonthDetail(month);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
attachNavHandlers(detailBodyEl);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
window.TarotCalendarDetail = {
|
|||
|
|
init,
|
|||
|
|
renderDetail,
|
|||
|
|
attachNavHandlers
|
|||
|
|
};
|
|||
|
|
})();
|