refraction almost completed
This commit is contained in:
@@ -37,6 +37,17 @@
|
||||
findGodIdByName: () => null
|
||||
};
|
||||
|
||||
const calendarDetailPanelsUi = window.CalendarDetailPanelsUi || {};
|
||||
|
||||
if (
|
||||
typeof calendarDetailPanelsUi.renderGregorianMonthDetail !== "function"
|
||||
|| typeof calendarDetailPanelsUi.renderHebrewMonthDetail !== "function"
|
||||
|| typeof calendarDetailPanelsUi.renderIslamicMonthDetail !== "function"
|
||||
|| typeof calendarDetailPanelsUi.renderWheelMonthDetail !== "function"
|
||||
) {
|
||||
throw new Error("CalendarDetailPanelsUi module must load before ui-calendar-detail.js");
|
||||
}
|
||||
|
||||
function init(config) {
|
||||
Object.assign(api, config || {});
|
||||
}
|
||||
@@ -413,420 +424,17 @@
|
||||
`;
|
||||
}
|
||||
|
||||
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 getPanelRenderContext(month) {
|
||||
return {
|
||||
month,
|
||||
api,
|
||||
getState,
|
||||
buildAssociationButtons,
|
||||
renderFactsCard,
|
||||
renderAssociationsCard,
|
||||
renderEventsCard,
|
||||
renderHolidaysCard
|
||||
};
|
||||
}
|
||||
|
||||
function attachNavHandlers(detailBodyEl) {
|
||||
@@ -964,28 +572,19 @@
|
||||
detailNameEl.textContent = month.name || month.id;
|
||||
|
||||
const currentState = getState();
|
||||
const panelContext = getPanelRenderContext(month);
|
||||
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>
|
||||
`;
|
||||
detailBodyEl.innerHTML = calendarDetailPanelsUi.renderGregorianMonthDetail(panelContext);
|
||||
} else if (currentState.selectedCalendar === "hebrew") {
|
||||
detailSubEl.textContent = api.getMonthSubtitle(month);
|
||||
detailBodyEl.innerHTML = renderHebrewMonthDetail(month);
|
||||
detailBodyEl.innerHTML = calendarDetailPanelsUi.renderHebrewMonthDetail(panelContext);
|
||||
} else if (currentState.selectedCalendar === "islamic") {
|
||||
detailSubEl.textContent = api.getMonthSubtitle(month);
|
||||
detailBodyEl.innerHTML = renderIslamicMonthDetail(month);
|
||||
detailBodyEl.innerHTML = calendarDetailPanelsUi.renderIslamicMonthDetail(panelContext);
|
||||
} else {
|
||||
detailSubEl.textContent = api.getMonthSubtitle(month);
|
||||
detailBodyEl.innerHTML = renderWheelMonthDetail(month);
|
||||
detailBodyEl.innerHTML = calendarDetailPanelsUi.renderWheelMonthDetail(panelContext);
|
||||
}
|
||||
|
||||
attachNavHandlers(detailBodyEl);
|
||||
|
||||
Reference in New Issue
Block a user