added overlay function for tarot cards

This commit is contained in:
2026-03-08 03:52:25 -07:00
parent 84b340d7d1
commit 78abb582dd
17 changed files with 4050 additions and 1175 deletions

View File

@@ -12,6 +12,25 @@
filteredCards: [],
searchQuery: "",
selectedCardId: "",
houseFocusMode: false,
houseTopCardsVisible: true,
houseTopInfoModes: {
hebrew: true,
planet: true,
zodiac: true,
trump: true,
path: true
},
houseBottomCardsVisible: true,
houseBottomInfoModes: {
zodiac: true,
decan: true,
month: true,
ruler: true,
date: false
},
houseExportInProgress: false,
houseExportFormat: "png",
magickDataset: null,
referenceData: null,
monthRefsByCardId: new Map(),
@@ -248,10 +267,34 @@
tarotDetailIChingEl: document.getElementById("tarot-detail-iching"),
tarotDetailCalendarEl: document.getElementById("tarot-detail-calendar"),
tarotKabPathEl: document.getElementById("tarot-kab-path"),
tarotHouseOfCardsEl: document.getElementById("tarot-house-of-cards")
tarotHouseOfCardsEl: document.getElementById("tarot-house-of-cards"),
tarotBrowseViewEl: document.getElementById("tarot-browse-view"),
tarotHouseTopCardsVisibleEl: document.getElementById("tarot-house-top-cards-visible"),
tarotHouseTopInfoHebrewEl: document.getElementById("tarot-house-top-info-hebrew"),
tarotHouseTopInfoPlanetEl: document.getElementById("tarot-house-top-info-planet"),
tarotHouseTopInfoZodiacEl: document.getElementById("tarot-house-top-info-zodiac"),
tarotHouseTopInfoTrumpEl: document.getElementById("tarot-house-top-info-trump"),
tarotHouseTopInfoPathEl: document.getElementById("tarot-house-top-info-path"),
tarotHouseBottomCardsVisibleEl: document.getElementById("tarot-house-bottom-cards-visible"),
tarotHouseBottomInfoZodiacEl: document.getElementById("tarot-house-bottom-info-zodiac"),
tarotHouseBottomInfoDecanEl: document.getElementById("tarot-house-bottom-info-decan"),
tarotHouseBottomInfoMonthEl: document.getElementById("tarot-house-bottom-info-month"),
tarotHouseBottomInfoRulerEl: document.getElementById("tarot-house-bottom-info-ruler"),
tarotHouseBottomInfoDateEl: document.getElementById("tarot-house-bottom-info-date"),
tarotHouseFocusToggleEl: document.getElementById("tarot-house-focus-toggle"),
tarotHouseExportEl: document.getElementById("tarot-house-export"),
tarotHouseExportWebpEl: document.getElementById("tarot-house-export-webp")
};
}
function setHouseBottomInfoCheckboxState(checkbox, enabled) {
if (!checkbox) {
return;
}
checkbox.checked = Boolean(enabled);
checkbox.disabled = Boolean(state.houseExportInProgress);
}
function normalizeRelationId(value) {
return String(value || "")
.trim()
@@ -485,6 +528,76 @@
tarotHouseUi.render?.(elements);
}
function syncHouseControls(elements) {
if (elements?.tarotBrowseViewEl) {
elements.tarotBrowseViewEl.classList.toggle("is-house-focus", Boolean(state.houseFocusMode));
}
if (elements?.tarotHouseTopCardsVisibleEl) {
elements.tarotHouseTopCardsVisibleEl.checked = Boolean(state.houseTopCardsVisible);
elements.tarotHouseTopCardsVisibleEl.disabled = Boolean(state.houseExportInProgress);
}
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoHebrewEl, state.houseTopInfoModes.hebrew);
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoPlanetEl, state.houseTopInfoModes.planet);
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoZodiacEl, state.houseTopInfoModes.zodiac);
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoTrumpEl, state.houseTopInfoModes.trump);
setHouseBottomInfoCheckboxState(elements?.tarotHouseTopInfoPathEl, state.houseTopInfoModes.path);
if (elements?.tarotHouseBottomCardsVisibleEl) {
elements.tarotHouseBottomCardsVisibleEl.checked = Boolean(state.houseBottomCardsVisible);
elements.tarotHouseBottomCardsVisibleEl.disabled = Boolean(state.houseExportInProgress);
}
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoZodiacEl, state.houseBottomInfoModes.zodiac);
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoDecanEl, state.houseBottomInfoModes.decan);
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoMonthEl, state.houseBottomInfoModes.month);
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoRulerEl, state.houseBottomInfoModes.ruler);
setHouseBottomInfoCheckboxState(elements?.tarotHouseBottomInfoDateEl, state.houseBottomInfoModes.date);
if (elements?.tarotHouseFocusToggleEl) {
elements.tarotHouseFocusToggleEl.setAttribute("aria-pressed", state.houseFocusMode ? "true" : "false");
elements.tarotHouseFocusToggleEl.textContent = state.houseFocusMode ? "Show Full Tarot" : "Focus House";
}
if (elements?.tarotHouseExportEl) {
elements.tarotHouseExportEl.disabled = Boolean(state.houseExportInProgress);
elements.tarotHouseExportEl.textContent = state.houseExportInProgress ? "Exporting..." : "Export PNG";
}
if (elements?.tarotHouseExportWebpEl) {
const supportsWebp = tarotHouseUi.isExportFormatSupported?.("webp") === true;
elements.tarotHouseExportWebpEl.disabled = Boolean(state.houseExportInProgress) || !supportsWebp;
elements.tarotHouseExportWebpEl.hidden = !supportsWebp;
elements.tarotHouseExportWebpEl.textContent = state.houseExportInProgress && state.houseExportFormat === "webp"
? "Exporting..."
: "Export WebP";
if (supportsWebp) {
elements.tarotHouseExportWebpEl.title = "Smaller file size, but not guaranteed lossless like PNG.";
}
}
}
async function exportHouseOfCards(elements, format = "png") {
if (state.houseExportInProgress) {
return;
}
state.houseExportInProgress = true;
state.houseExportFormat = format;
syncHouseControls(elements);
try {
await tarotHouseUi.exportImage?.(format);
} catch (error) {
window.alert(error instanceof Error ? error.message : "Unable to export the House of Cards image.");
} finally {
state.houseExportInProgress = false;
state.houseExportFormat = "png";
syncHouseControls(elements);
}
}
function buildTypeLabel(card) {
return tarotCardDerivationsUi.buildTypeLabel(card);
}
@@ -566,6 +679,35 @@
renderDetail(card, elements);
}
function scrollCardIntoView(cardIdToReveal, elements) {
elements?.tarotCardListEl
?.querySelector(`[data-card-id="${cardIdToReveal}"]`)
?.scrollIntoView({ block: "nearest" });
}
function buildLightboxCardRequestById(cardIdToResolve) {
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
if (!card) {
return null;
}
const src = typeof resolveTarotCardImage === "function"
? resolveTarotCardImage(card.name)
: "";
if (!src) {
return null;
}
const label = getDisplayCardName(card) || card.name || "Tarot card enlarged image";
return {
src,
altText: label,
label,
cardId: card.id,
compareDetails: tarotDetailRenderer.buildCompareDetails?.(card) || []
};
}
function renderList(elements) {
if (!elements?.tarotCardListEl) {
return;
@@ -613,8 +755,32 @@
clearChildren,
normalizeTarotCardLookupName,
selectCardById,
openCardLightbox: (src, altText, options = {}) => {
const cardId = String(options?.cardId || "").trim();
const primaryCardRequest = cardId ? buildLightboxCardRequestById(cardId) : null;
window.TarotUiLightbox?.open?.({
src: primaryCardRequest?.src || src,
altText: primaryCardRequest?.altText || altText || "Tarot card enlarged image",
label: primaryCardRequest?.label || altText || "Tarot card enlarged image",
cardId: primaryCardRequest?.cardId || cardId,
compareDetails: primaryCardRequest?.compareDetails || [],
allowOverlayCompare: true,
sequenceIds: state.cards.map((card) => card.id),
resolveCardById: buildLightboxCardRequestById,
onSelectCardId: (nextCardId) => {
const latestElements = getElements();
selectCardById(nextCardId, latestElements);
scrollCardIntoView(nextCardId, latestElements);
}
});
},
isHouseFocusMode: () => state.houseFocusMode,
getCards: () => state.cards,
getSelectedCardId: () => state.selectedCardId
getSelectedCardId: () => state.selectedCardId,
getHouseTopCardsVisible: () => state.houseTopCardsVisible,
getHouseTopInfoModes: () => ({ ...state.houseTopInfoModes }),
getHouseBottomCardsVisible: () => state.houseBottomCardsVisible,
getHouseBottomInfoModes: () => ({ ...state.houseBottomInfoModes })
});
const elements = getElements();
@@ -623,6 +789,7 @@
state.monthRefsByCardId = buildMonthReferencesByCard(referenceData, state.cards);
state.courtCardByDecanId = buildCourtCardByDecanId(state.cards);
renderHouseOfCards(elements);
syncHouseControls(elements);
if (state.selectedCardId) {
const selected = state.cards.find((card) => card.id === state.selectedCardId);
if (selected) {
@@ -652,6 +819,7 @@
state.filteredCards = [...cards];
renderList(elements);
renderHouseOfCards(elements);
syncHouseControls(elements);
if (cards.length > 0) {
selectCardById(cards[0].id, elements);
@@ -695,6 +863,75 @@
});
}
if (elements.tarotHouseFocusToggleEl) {
elements.tarotHouseFocusToggleEl.addEventListener("click", () => {
state.houseFocusMode = !state.houseFocusMode;
syncHouseControls(elements);
});
}
if (elements.tarotHouseTopCardsVisibleEl) {
elements.tarotHouseTopCardsVisibleEl.addEventListener("change", () => {
state.houseTopCardsVisible = Boolean(elements.tarotHouseTopCardsVisibleEl.checked);
renderHouseOfCards(elements);
syncHouseControls(elements);
});
}
[
[elements.tarotHouseTopInfoHebrewEl, "hebrew"],
[elements.tarotHouseTopInfoPlanetEl, "planet"],
[elements.tarotHouseTopInfoZodiacEl, "zodiac"],
[elements.tarotHouseTopInfoTrumpEl, "trump"],
[elements.tarotHouseTopInfoPathEl, "path"]
].forEach(([checkbox, key]) => {
if (!checkbox) {
return;
}
checkbox.addEventListener("change", () => {
state.houseTopInfoModes[key] = Boolean(checkbox.checked);
renderHouseOfCards(elements);
syncHouseControls(elements);
});
});
if (elements.tarotHouseBottomCardsVisibleEl) {
elements.tarotHouseBottomCardsVisibleEl.addEventListener("change", () => {
state.houseBottomCardsVisible = Boolean(elements.tarotHouseBottomCardsVisibleEl.checked);
renderHouseOfCards(elements);
syncHouseControls(elements);
});
}
[
[elements.tarotHouseBottomInfoZodiacEl, "zodiac"],
[elements.tarotHouseBottomInfoDecanEl, "decan"],
[elements.tarotHouseBottomInfoMonthEl, "month"],
[elements.tarotHouseBottomInfoRulerEl, "ruler"],
[elements.tarotHouseBottomInfoDateEl, "date"]
].forEach(([checkbox, key]) => {
if (!checkbox) {
return;
}
checkbox.addEventListener("change", () => {
state.houseBottomInfoModes[key] = Boolean(checkbox.checked);
renderHouseOfCards(elements);
syncHouseControls(elements);
});
});
if (elements.tarotHouseExportEl) {
elements.tarotHouseExportEl.addEventListener("click", () => {
exportHouseOfCards(elements, "png");
});
}
if (elements.tarotHouseExportWebpEl) {
elements.tarotHouseExportWebpEl.addEventListener("click", () => {
exportHouseOfCards(elements, "webp");
});
}
if (elements.tarotDetailImageEl) {
elements.tarotDetailImageEl.addEventListener("click", () => {
const src = elements.tarotDetailImageEl.getAttribute("src") || "";
@@ -714,8 +951,7 @@
const card = state.cards.find(c => c.arcana === "Major" && c.number === trumpNumber);
if (!card) return;
selectCardById(card.id, el);
const listItem = el.tarotCardListEl?.querySelector(`[data-card-id="${card.id}"]`);
listItem?.scrollIntoView({ block: "nearest" });
scrollCardIntoView(card.id, el);
}
function selectCardByName(name) {
@@ -725,9 +961,7 @@
const card = state.cards.find((entry) => normalizeTarotCardLookupName(entry.name) === needle);
if (!card) return;
selectCardById(card.id, el);
el.tarotCardListEl
?.querySelector(`[data-card-id="${card.id}"]`)
?.scrollIntoView({ block: "nearest" });
scrollCardIntoView(card.id, el);
}
window.TarotSectionUi = {