added thumbs generation for performation and also added a new deck format for registration
This commit is contained in:
@@ -98,6 +98,14 @@
|
||||
swords: ["swords"],
|
||||
disks: ["disks", "pentacles", "coins"]
|
||||
};
|
||||
const defaultPipRankOrder = ["Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
|
||||
const defaultThumbnailConfig = {
|
||||
root: "thumbs",
|
||||
width: 240,
|
||||
height: 360,
|
||||
fit: "inside",
|
||||
quality: 82
|
||||
};
|
||||
|
||||
const DECK_REGISTRY_PATH = "asset/tarot deck/decks.json";
|
||||
|
||||
@@ -105,6 +113,7 @@
|
||||
|
||||
const manifestCache = new Map();
|
||||
const cardBackCache = new Map();
|
||||
const cardBackThumbnailCache = new Map();
|
||||
let activeDeckId = DEFAULT_DECK_ID;
|
||||
|
||||
function canonicalMajorName(cardName) {
|
||||
@@ -278,6 +287,56 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeThumbnailConfig(rawConfig, fallbackRoot = "") {
|
||||
if (rawConfig === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rawConfig || typeof rawConfig !== "object") {
|
||||
const root = String(fallbackRoot || "").trim();
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultThumbnailConfig,
|
||||
root
|
||||
};
|
||||
}
|
||||
|
||||
const root = String(rawConfig.root || fallbackRoot || defaultThumbnailConfig.root).trim();
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
root,
|
||||
width: Number.isInteger(Number(rawConfig.width)) && Number(rawConfig.width) > 0
|
||||
? Number(rawConfig.width)
|
||||
: defaultThumbnailConfig.width,
|
||||
height: Number.isInteger(Number(rawConfig.height)) && Number(rawConfig.height) > 0
|
||||
? Number(rawConfig.height)
|
||||
: defaultThumbnailConfig.height,
|
||||
fit: String(rawConfig.fit || defaultThumbnailConfig.fit).trim() || defaultThumbnailConfig.fit,
|
||||
quality: Number.isInteger(Number(rawConfig.quality)) && Number(rawConfig.quality) >= 1 && Number(rawConfig.quality) <= 100
|
||||
? Number(rawConfig.quality)
|
||||
: defaultThumbnailConfig.quality
|
||||
};
|
||||
}
|
||||
|
||||
function resolveDeckThumbnailPath(manifest, relativePath) {
|
||||
if (!manifest || !manifest.thumbnails || manifest.thumbnails === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedPath = String(relativePath || "").trim().replace(/^\.\//, "");
|
||||
if (!normalizedPath || isRemoteAssetPath(normalizedPath) || normalizedPath.startsWith("/")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toDeckAssetPath(manifest, `${manifest.thumbnails.root}/${normalizedPath}`) || null;
|
||||
}
|
||||
|
||||
function readManifestJsonSync(path) {
|
||||
try {
|
||||
const request = new XMLHttpRequest();
|
||||
@@ -314,7 +373,8 @@
|
||||
label: String(entry?.label || id),
|
||||
basePath,
|
||||
manifestPath,
|
||||
cardBackPath: String(entry?.cardBackPath || "").trim()
|
||||
cardBackPath: String(entry?.cardBackPath || "").trim(),
|
||||
thumbnailRoot: String(entry?.thumbnailRoot || "").trim()
|
||||
};
|
||||
});
|
||||
|
||||
@@ -385,6 +445,7 @@
|
||||
basePath: String(source.basePath || "").replace(/\/$/, ""),
|
||||
cardBack: String(rawManifest.cardBack || "").trim(),
|
||||
cardBackPath: String(source.cardBackPath || "").trim(),
|
||||
thumbnails: normalizeThumbnailConfig(rawManifest.thumbnails, source.thumbnailRoot),
|
||||
majors: rawManifest.majors || {},
|
||||
minors: rawManifest.minors || {},
|
||||
nameOverrides,
|
||||
@@ -418,7 +479,13 @@
|
||||
return normalizedManifest;
|
||||
}
|
||||
|
||||
function getRankIndex(minorRule, parsedMinor) {
|
||||
function getRankOrder(minorRule, fallbackRankOrder = []) {
|
||||
const explicitRankOrder = Array.isArray(minorRule?.rankOrder) ? minorRule.rankOrder : [];
|
||||
const rankOrderSource = explicitRankOrder.length ? explicitRankOrder : fallbackRankOrder;
|
||||
return rankOrderSource.map((entry) => String(entry || "").trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function getRankIndex(minorRule, parsedMinor, fallbackRankOrder = []) {
|
||||
if (!minorRule || !parsedMinor) {
|
||||
return null;
|
||||
}
|
||||
@@ -434,7 +501,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const rankOrder = Array.isArray(minorRule.rankOrder) ? minorRule.rankOrder : [];
|
||||
const rankOrder = getRankOrder(minorRule, fallbackRankOrder);
|
||||
for (let i = 0; i < rankOrder.length; i += 1) {
|
||||
const candidate = String(rankOrder[i] || "").toLowerCase();
|
||||
if (candidate && (candidate === lowerRankWord || candidate === lowerRankKey)) {
|
||||
@@ -445,6 +512,34 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveMinorNumberTemplateGroup(groupRule, parsedMinor, fallbackRankOrder = []) {
|
||||
if (!groupRule || typeof groupRule !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rankIndex = getRankIndex(groupRule, parsedMinor, fallbackRankOrder);
|
||||
if (!Number.isInteger(rankIndex) || rankIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const suitBaseRaw = Number(groupRule?.suitBase?.[parsedMinor.suitId]);
|
||||
if (!Number.isFinite(suitBaseRaw)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const numberPad = Number.isInteger(groupRule.numberPad) ? groupRule.numberPad : 2;
|
||||
const cardNumber = String(suitBaseRaw + rankIndex).padStart(numberPad, "0");
|
||||
const template = String(groupRule.template || "{number}.png");
|
||||
|
||||
return applyTemplate(template, {
|
||||
number: cardNumber,
|
||||
suitId: parsedMinor.suitId,
|
||||
rank: parsedMinor.rankWord,
|
||||
rankKey: parsedMinor.rankKey,
|
||||
index: rankIndex
|
||||
});
|
||||
}
|
||||
|
||||
function resolveMajorFile(manifest, canonicalName) {
|
||||
const majorRule = manifest?.majors;
|
||||
if (!majorRule || typeof majorRule !== "object") {
|
||||
@@ -487,6 +582,14 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
if (minorRule.mode === "split-number-template") {
|
||||
if (Number.isFinite(parsedMinor.pipValue)) {
|
||||
return resolveMinorNumberTemplateGroup(minorRule.smalls, parsedMinor, defaultPipRankOrder);
|
||||
}
|
||||
|
||||
return resolveMinorNumberTemplateGroup(minorRule.courts, parsedMinor);
|
||||
}
|
||||
|
||||
const rankIndex = getRankIndex(minorRule, parsedMinor);
|
||||
if (!Number.isInteger(rankIndex) || rankIndex < 0) {
|
||||
return null;
|
||||
@@ -555,8 +658,7 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveWithDeck(deckId, cardName) {
|
||||
const manifest = getDeckManifest(deckId);
|
||||
function resolveCardRelativePath(manifest, cardName) {
|
||||
if (!manifest) {
|
||||
return null;
|
||||
}
|
||||
@@ -564,7 +666,7 @@
|
||||
const canonical = canonicalMajorName(cardName);
|
||||
const majorFile = resolveMajorFile(manifest, canonical);
|
||||
if (majorFile) {
|
||||
return `${manifest.basePath}/${majorFile}`;
|
||||
return majorFile;
|
||||
}
|
||||
|
||||
const parsedMinor = parseMinorCard(cardName);
|
||||
@@ -572,12 +674,25 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
const minorFile = resolveMinorFile(manifest, parsedMinor);
|
||||
if (!minorFile) {
|
||||
return resolveMinorFile(manifest, parsedMinor);
|
||||
}
|
||||
|
||||
function resolveWithDeck(deckId, cardName, variant = "full") {
|
||||
const manifest = getDeckManifest(deckId);
|
||||
if (!manifest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${manifest.basePath}/${minorFile}`;
|
||||
const relativePath = resolveCardRelativePath(manifest, cardName);
|
||||
if (!relativePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (variant === "thumbnail") {
|
||||
return resolveDeckThumbnailPath(manifest, relativePath) || `${manifest.basePath}/${relativePath}`;
|
||||
}
|
||||
|
||||
return `${manifest.basePath}/${relativePath}`;
|
||||
}
|
||||
|
||||
function resolveTarotCardImage(cardName) {
|
||||
@@ -589,6 +704,16 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveTarotCardThumbnail(cardName, optionsOrDeckId) {
|
||||
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
||||
const thumbnailPath = resolveWithDeck(resolvedDeckId, cardName, "thumbnail");
|
||||
if (thumbnailPath) {
|
||||
return encodeURI(thumbnailPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveTarotCardBackImage(optionsOrDeckId) {
|
||||
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
||||
|
||||
@@ -608,6 +733,22 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveTarotCardBackThumbnail(optionsOrDeckId) {
|
||||
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
||||
|
||||
if (cardBackThumbnailCache.has(resolvedDeckId)) {
|
||||
const cachedPath = cardBackThumbnailCache.get(resolvedDeckId);
|
||||
return cachedPath ? encodeURI(cachedPath) : null;
|
||||
}
|
||||
|
||||
const manifest = getDeckManifest(resolvedDeckId);
|
||||
const relativeBackPath = String(manifest?.cardBack || manifest?.cardBackPath || "").trim();
|
||||
const thumbnailPath = resolveDeckThumbnailPath(manifest, relativeBackPath) || resolveDeckCardBackPath(manifest);
|
||||
cardBackThumbnailCache.set(resolvedDeckId, thumbnailPath || null);
|
||||
|
||||
return thumbnailPath ? encodeURI(thumbnailPath) : null;
|
||||
}
|
||||
|
||||
function resolveDisplayNameWithDeck(deckId, cardName, trumpNumber) {
|
||||
const manifest = getDeckManifest(deckId);
|
||||
const fallbackName = String(cardName || "").trim();
|
||||
@@ -712,7 +853,9 @@
|
||||
|
||||
window.TarotCardImages = {
|
||||
resolveTarotCardImage,
|
||||
resolveTarotCardThumbnail,
|
||||
resolveTarotCardBackImage,
|
||||
resolveTarotCardBackThumbnail,
|
||||
getTarotCardDisplayName,
|
||||
getTarotCardSearchAliases,
|
||||
setActiveDeck,
|
||||
|
||||
@@ -767,6 +767,7 @@
|
||||
overflow: hidden;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
contain: layout paint;
|
||||
transform-origin: center;
|
||||
transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
|
||||
}
|
||||
@@ -803,6 +804,12 @@
|
||||
height: var(--tarot-house-card-height);
|
||||
object-fit: cover;
|
||||
background: #09090b;
|
||||
opacity: 0;
|
||||
transition: opacity 140ms ease;
|
||||
}
|
||||
|
||||
.tarot-house-card-image.is-loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tarot-house-card-text-face {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"use strict";
|
||||
|
||||
const { DAY_IN_MS, getMoonPhaseName, getDecanForDate } = window.TarotCalc || {};
|
||||
const { resolveTarotCardImage, getTarotCardDisplayName } = window.TarotCardImages || {};
|
||||
const { resolveTarotCardImage, resolveTarotCardThumbnail, getTarotCardDisplayName } = window.TarotCardImages || {};
|
||||
|
||||
let nowLightboxOverlayEl = null;
|
||||
let nowLightboxImageEl = null;
|
||||
@@ -207,7 +207,7 @@
|
||||
imageEl.style.cursor = "zoom-in";
|
||||
imageEl.title = "Click to enlarge";
|
||||
imageEl.addEventListener("click", () => {
|
||||
const src = imageEl.getAttribute("src");
|
||||
const src = imageEl.dataset.fullSrc || imageEl.getAttribute("src");
|
||||
if (!src || imageEl.style.display === "none") {
|
||||
return;
|
||||
}
|
||||
@@ -489,23 +489,32 @@
|
||||
|
||||
bindNowCardLightbox(imageEl);
|
||||
|
||||
if (!cardName || typeof resolveTarotCardImage !== "function") {
|
||||
if (!cardName || (typeof resolveTarotCardThumbnail !== "function" && typeof resolveTarotCardImage !== "function")) {
|
||||
imageEl.style.display = "none";
|
||||
imageEl.removeAttribute("src");
|
||||
delete imageEl.dataset.fullSrc;
|
||||
return;
|
||||
}
|
||||
|
||||
const src = resolveTarotCardImage(cardName);
|
||||
if (!src) {
|
||||
const fullSrc = typeof resolveTarotCardImage === "function"
|
||||
? resolveTarotCardImage(cardName)
|
||||
: null;
|
||||
const thumbnailSrc = typeof resolveTarotCardThumbnail === "function"
|
||||
? (resolveTarotCardThumbnail(cardName) || fullSrc)
|
||||
: fullSrc;
|
||||
if (!thumbnailSrc) {
|
||||
imageEl.style.display = "none";
|
||||
imageEl.removeAttribute("src");
|
||||
delete imageEl.dataset.fullSrc;
|
||||
return;
|
||||
}
|
||||
|
||||
imageEl.src = src;
|
||||
imageEl.src = thumbnailSrc;
|
||||
imageEl.dataset.fullSrc = fullSrc || thumbnailSrc;
|
||||
const displayName = getDisplayTarotName(cardName, trumpNumber);
|
||||
imageEl.alt = `${fallbackLabel}: ${displayName}`;
|
||||
imageEl.style.display = "block";
|
||||
imageEl.decoding = "async";
|
||||
}
|
||||
|
||||
window.NowUiHelpers = {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
getMonthRefsByCardId,
|
||||
getMagickDataset,
|
||||
resolveTarotCardImage,
|
||||
resolveTarotCardThumbnail,
|
||||
getDisplayCardName,
|
||||
buildTypeLabel,
|
||||
clearChildren,
|
||||
@@ -203,9 +204,9 @@
|
||||
}
|
||||
|
||||
const cardDisplayName = getDisplayCardName(card);
|
||||
const imageUrl = typeof resolveTarotCardImage === "function"
|
||||
? resolveTarotCardImage(card.name)
|
||||
: null;
|
||||
const imageUrl = typeof resolveTarotCardThumbnail === "function"
|
||||
? resolveTarotCardThumbnail(card.name)
|
||||
: (typeof resolveTarotCardImage === "function" ? resolveTarotCardImage(card.name) : null);
|
||||
|
||||
if (elements.tarotDetailImageEl) {
|
||||
if (imageUrl) {
|
||||
@@ -214,6 +215,7 @@
|
||||
elements.tarotDetailImageEl.style.display = "block";
|
||||
elements.tarotDetailImageEl.style.cursor = "zoom-in";
|
||||
elements.tarotDetailImageEl.title = "Click to enlarge";
|
||||
elements.tarotDetailImageEl.decoding = "async";
|
||||
} else {
|
||||
elements.tarotDetailImageEl.removeAttribute("src");
|
||||
elements.tarotDetailImageEl.alt = "";
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
const config = {
|
||||
resolveTarotCardImage: null,
|
||||
resolveTarotCardThumbnail: null,
|
||||
getDisplayCardName: (card) => card?.name || "",
|
||||
clearChildren: () => {},
|
||||
normalizeTarotCardLookupName: (value) => String(value || "").trim().toLowerCase(),
|
||||
@@ -59,6 +60,8 @@
|
||||
getHouseBottomInfoModes: () => ({})
|
||||
};
|
||||
|
||||
let houseImageObserver = null;
|
||||
|
||||
function init(nextConfig = {}) {
|
||||
Object.assign(config, nextConfig || {});
|
||||
}
|
||||
@@ -597,6 +600,73 @@
|
||||
return faceEl;
|
||||
}
|
||||
|
||||
function disconnectHouseImageObserver() {
|
||||
if (!houseImageObserver) {
|
||||
return;
|
||||
}
|
||||
|
||||
houseImageObserver.disconnect();
|
||||
houseImageObserver = null;
|
||||
}
|
||||
|
||||
function hydrateHouseCardImage(image) {
|
||||
if (!(image instanceof HTMLImageElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSrc = String(image.dataset.src || "").trim();
|
||||
if (!nextSrc || image.dataset.imageHydrated === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
image.dataset.imageHydrated = "true";
|
||||
image.classList.add("is-loading");
|
||||
image.src = nextSrc;
|
||||
}
|
||||
|
||||
function getHouseImageObserver(elements) {
|
||||
const root = elements?.tarotHouseOfCardsEl?.closest(".tarot-section-house-top") || null;
|
||||
if (!root || typeof IntersectionObserver !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (houseImageObserver) {
|
||||
return houseImageObserver;
|
||||
}
|
||||
|
||||
houseImageObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = entry.target;
|
||||
observer.unobserve(target);
|
||||
hydrateHouseCardImage(target);
|
||||
});
|
||||
}, {
|
||||
root,
|
||||
rootMargin: "160px 0px",
|
||||
threshold: 0.01
|
||||
});
|
||||
|
||||
return houseImageObserver;
|
||||
}
|
||||
|
||||
function registerHouseCardImage(image, elements) {
|
||||
if (!(image instanceof HTMLImageElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = getHouseImageObserver(elements);
|
||||
if (!observer) {
|
||||
hydrateHouseCardImage(image);
|
||||
return;
|
||||
}
|
||||
|
||||
observer.observe(image);
|
||||
}
|
||||
|
||||
function createHouseCardButton(card, elements) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
@@ -620,16 +690,30 @@
|
||||
button.title = labelText ? `${cardDisplayName || card.name} - ${labelText}` : (cardDisplayName || card.name);
|
||||
button.setAttribute("aria-label", labelText ? `${cardDisplayName || card.name}, ${labelText}` : (cardDisplayName || card.name));
|
||||
button.dataset.houseCardId = card.id;
|
||||
const imageUrl = typeof config.resolveTarotCardImage === "function"
|
||||
? config.resolveTarotCardImage(card.name)
|
||||
: null;
|
||||
const imageUrl = typeof config.resolveTarotCardThumbnail === "function"
|
||||
? config.resolveTarotCardThumbnail(card.name)
|
||||
: (typeof config.resolveTarotCardImage === "function" ? config.resolveTarotCardImage(card.name) : null);
|
||||
|
||||
if (showImage && imageUrl) {
|
||||
const image = document.createElement("img");
|
||||
image.className = "tarot-house-card-image";
|
||||
image.src = imageUrl;
|
||||
image.alt = cardDisplayName || card.name;
|
||||
image.alt = "";
|
||||
image.setAttribute("aria-hidden", "true");
|
||||
image.loading = "lazy";
|
||||
image.decoding = "async";
|
||||
image.fetchPriority = config.isHouseFocusMode?.() === true ? "auto" : "low";
|
||||
image.dataset.src = imageUrl;
|
||||
image.addEventListener("load", () => {
|
||||
image.classList.remove("is-loading");
|
||||
image.classList.add("is-loaded");
|
||||
}, { once: true });
|
||||
image.addEventListener("error", () => {
|
||||
image.classList.remove("is-loading");
|
||||
image.classList.remove("is-loaded");
|
||||
image.dataset.imageHydrated = "false";
|
||||
});
|
||||
button.appendChild(image);
|
||||
registerHouseCardImage(image, elements);
|
||||
} else if (showImage) {
|
||||
const fallback = document.createElement("span");
|
||||
fallback.className = "tarot-house-card-fallback";
|
||||
@@ -1095,6 +1179,7 @@
|
||||
}
|
||||
|
||||
const cards = config.getCards();
|
||||
disconnectHouseImageObserver();
|
||||
config.clearChildren(elements.tarotHouseOfCardsEl);
|
||||
const cardLookupMap = getCardLookupMap(cards);
|
||||
|
||||
|
||||
@@ -152,7 +152,11 @@
|
||||
|
||||
const normalizedSpread = normalizeTarotSpread(activeTarotSpread);
|
||||
const isCeltic = normalizedSpread === "celtic-cross";
|
||||
const cardBackImageSrc = String(window.TarotCardImages?.resolveTarotCardBackImage?.() || "").trim();
|
||||
const cardBackImageSrc = String(
|
||||
window.TarotCardImages?.resolveTarotCardBackThumbnail?.()
|
||||
|| window.TarotCardImages?.resolveTarotCardBackImage?.()
|
||||
|| ""
|
||||
).trim();
|
||||
|
||||
if (!activeTarotSpreadDraw.length) {
|
||||
regenerateTarotSpreadDraw();
|
||||
@@ -188,7 +192,8 @@
|
||||
tarotSpreadBoardEl.innerHTML = activeTarotSpreadDraw.map((entry, index) => {
|
||||
const position = entry.position;
|
||||
const card = entry.card;
|
||||
const imgSrc = window.TarotCardImages?.resolveTarotCardImage?.(card.name);
|
||||
const imgSrc = window.TarotCardImages?.resolveTarotCardThumbnail?.(card.name)
|
||||
|| window.TarotCardImages?.resolveTarotCardImage?.(card.name);
|
||||
const isRevealed = Boolean(entry.revealed);
|
||||
const cardBackAttr = cardBackImageSrc
|
||||
? ` data-card-back-src="${escapeHtml(cardBackImageSrc)}"`
|
||||
@@ -203,10 +208,10 @@
|
||||
let faceMarkup = "";
|
||||
if (isRevealed) {
|
||||
faceMarkup = imgSrc
|
||||
? `<img class="spread-card-img" src="${imgSrc}" alt="${escapeHtml(card.name)}" loading="lazy">`
|
||||
? `<img class="spread-card-img" src="${imgSrc}" alt="${escapeHtml(card.name)}" loading="lazy" decoding="async">`
|
||||
: `<div class="spread-card-placeholder">${escapeHtml(card.name)}</div>`;
|
||||
} else if (cardBackImageSrc) {
|
||||
faceMarkup = '<img class="spread-card-back-img" src="' + cardBackImageSrc + '" alt="Face-down tarot card" loading="lazy">';
|
||||
faceMarkup = '<img class="spread-card-back-img" src="' + cardBackImageSrc + '" alt="Face-down tarot card" loading="lazy" decoding="async">';
|
||||
} else {
|
||||
faceMarkup = '<div class="spread-card-back-fallback">CARD BACK</div>';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(function () {
|
||||
const { resolveTarotCardImage, getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
||||
const { resolveTarotCardImage, resolveTarotCardThumbnail, getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
||||
const tarotHouseUi = window.TarotHouseUi || {};
|
||||
const tarotRelationsUi = window.TarotRelationsUi || {};
|
||||
const tarotCardDerivations = window.TarotCardDerivations || {};
|
||||
@@ -339,6 +339,7 @@
|
||||
getMonthRefsByCardId: () => state.monthRefsByCardId,
|
||||
getMagickDataset: () => state.magickDataset,
|
||||
resolveTarotCardImage,
|
||||
resolveTarotCardThumbnail,
|
||||
getDisplayCardName,
|
||||
buildTypeLabel,
|
||||
clearChildren,
|
||||
@@ -751,6 +752,7 @@
|
||||
|
||||
tarotHouseUi.init?.({
|
||||
resolveTarotCardImage,
|
||||
resolveTarotCardThumbnail,
|
||||
getDisplayCardName,
|
||||
clearChildren,
|
||||
normalizeTarotCardLookupName,
|
||||
@@ -934,11 +936,16 @@
|
||||
|
||||
if (elements.tarotDetailImageEl) {
|
||||
elements.tarotDetailImageEl.addEventListener("click", () => {
|
||||
const src = elements.tarotDetailImageEl.getAttribute("src") || "";
|
||||
if (!src || elements.tarotDetailImageEl.style.display === "none") {
|
||||
if (elements.tarotDetailImageEl.style.display === "none" || !state.selectedCardId) {
|
||||
return;
|
||||
}
|
||||
window.TarotUiLightbox?.open?.(src, elements.tarotDetailImageEl.alt || "Tarot card enlarged image");
|
||||
|
||||
const request = buildLightboxCardRequestById(state.selectedCardId);
|
||||
if (!request?.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.TarotUiLightbox?.open?.(request);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user