added compare deck to house of card focus
This commit is contained in:
@@ -695,8 +695,9 @@
|
|||||||
return `${manifest.basePath}/${relativePath}`;
|
return `${manifest.basePath}/${relativePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTarotCardImage(cardName) {
|
function resolveTarotCardImage(cardName, optionsOrDeckId) {
|
||||||
const activePath = resolveWithDeck(activeDeckId, cardName);
|
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
|
||||||
|
const activePath = resolveWithDeck(resolvedDeckId, cardName);
|
||||||
if (activePath) {
|
if (activePath) {
|
||||||
return encodeURI(activePath);
|
return encodeURI(activePath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
let helpButtonEl = null;
|
let helpButtonEl = null;
|
||||||
let helpPanelEl = null;
|
let helpPanelEl = null;
|
||||||
let compareButtonEl = null;
|
let compareButtonEl = null;
|
||||||
|
let deckCompareButtonEl = null;
|
||||||
|
let deckComparePanelEl = null;
|
||||||
|
let deckCompareMessageEl = null;
|
||||||
|
let deckCompareDeckListEl = null;
|
||||||
let zoomControlEl = null;
|
let zoomControlEl = null;
|
||||||
let zoomSliderEl = null;
|
let zoomSliderEl = null;
|
||||||
let zoomValueEl = null;
|
let zoomValueEl = null;
|
||||||
@@ -17,6 +21,7 @@
|
|||||||
let frameEl = null;
|
let frameEl = null;
|
||||||
let baseLayerEl = null;
|
let baseLayerEl = null;
|
||||||
let overlayLayerEl = null;
|
let overlayLayerEl = null;
|
||||||
|
let compareGridEl = null;
|
||||||
let imageEl = null;
|
let imageEl = null;
|
||||||
let overlayImageEl = null;
|
let overlayImageEl = null;
|
||||||
let primaryInfoEl = null;
|
let primaryInfoEl = null;
|
||||||
@@ -27,6 +32,7 @@
|
|||||||
let secondaryTitleEl = null;
|
let secondaryTitleEl = null;
|
||||||
let secondaryGroupsEl = null;
|
let secondaryGroupsEl = null;
|
||||||
let secondaryHintEl = null;
|
let secondaryHintEl = null;
|
||||||
|
let compareGridSlots = [];
|
||||||
let zoomed = false;
|
let zoomed = false;
|
||||||
let previousFocusedEl = null;
|
let previousFocusedEl = null;
|
||||||
|
|
||||||
@@ -39,11 +45,22 @@
|
|||||||
const lightboxState = {
|
const lightboxState = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
compareMode: false,
|
compareMode: false,
|
||||||
|
deckCompareMode: false,
|
||||||
allowOverlayCompare: false,
|
allowOverlayCompare: false,
|
||||||
|
allowDeckCompare: false,
|
||||||
primaryCard: null,
|
primaryCard: null,
|
||||||
secondaryCard: null,
|
secondaryCard: null,
|
||||||
|
activeDeckId: "",
|
||||||
|
activeDeckLabel: "",
|
||||||
|
availableCompareDecks: [],
|
||||||
|
selectedCompareDeckIds: [],
|
||||||
|
deckCompareCards: [],
|
||||||
|
maxCompareDecks: 2,
|
||||||
|
deckComparePickerOpen: false,
|
||||||
|
deckCompareMessage: "",
|
||||||
sequenceIds: [],
|
sequenceIds: [],
|
||||||
resolveCardById: null,
|
resolveCardById: null,
|
||||||
|
resolveDeckCardById: null,
|
||||||
onSelectCardId: null,
|
onSelectCardId: null,
|
||||||
overlayOpacity: LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY,
|
overlayOpacity: LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY,
|
||||||
zoomScale: LIGHTBOX_ZOOM_SCALE,
|
zoomScale: LIGHTBOX_ZOOM_SCALE,
|
||||||
@@ -113,10 +130,93 @@
|
|||||||
altText: String(normalized.altText || label).trim() || label,
|
altText: String(normalized.altText || label).trim() || label,
|
||||||
label,
|
label,
|
||||||
cardId: String(normalized.cardId || "").trim(),
|
cardId: String(normalized.cardId || "").trim(),
|
||||||
|
deckId: String(normalized.deckId || "").trim(),
|
||||||
|
deckLabel: String(normalized.deckLabel || normalized.deckId || "").trim(),
|
||||||
|
missingReason: String(normalized.missingReason || "").trim(),
|
||||||
compareDetails: normalizeCompareDetails(normalized.compareDetails)
|
compareDetails: normalizeCompareDetails(normalized.compareDetails)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeDeckOptions(deckOptions) {
|
||||||
|
if (!Array.isArray(deckOptions)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return deckOptions
|
||||||
|
.map((deck) => ({
|
||||||
|
id: String(deck?.id || "").trim(),
|
||||||
|
label: String(deck?.label || deck?.id || "").trim()
|
||||||
|
}))
|
||||||
|
.filter((deck) => deck.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeckLabel(deckId) {
|
||||||
|
const normalizedDeckId = String(deckId || "").trim();
|
||||||
|
if (!normalizedDeckId) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedDeckId === lightboxState.activeDeckId && lightboxState.activeDeckLabel) {
|
||||||
|
return lightboxState.activeDeckLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = lightboxState.availableCompareDecks.find((deck) => deck.id === normalizedDeckId);
|
||||||
|
return match?.label || normalizedDeckId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDeckCardRequest(cardId, deckId) {
|
||||||
|
if (!cardId || !deckId || typeof lightboxState.resolveDeckCardById !== "function") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = lightboxState.resolveDeckCardById(cardId, deckId);
|
||||||
|
if (!resolved) {
|
||||||
|
return normalizeCardRequest({
|
||||||
|
cardId,
|
||||||
|
deckId,
|
||||||
|
deckLabel: getDeckLabel(deckId),
|
||||||
|
label: lightboxState.primaryCard?.label || "Tarot card",
|
||||||
|
altText: lightboxState.primaryCard?.altText || "Tarot card",
|
||||||
|
missingReason: "Card image unavailable for this deck."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeCardRequest({
|
||||||
|
...resolved,
|
||||||
|
cardId,
|
||||||
|
deckId,
|
||||||
|
deckLabel: resolved.deckLabel || getDeckLabel(deckId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncDeckCompareCards() {
|
||||||
|
if (!lightboxState.deckCompareMode || !lightboxState.primaryCard?.cardId) {
|
||||||
|
lightboxState.deckCompareCards = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightboxState.deckCompareCards = lightboxState.selectedCompareDeckIds
|
||||||
|
.slice(0, lightboxState.maxCompareDecks)
|
||||||
|
.map((deckId) => resolveDeckCardRequest(lightboxState.primaryCard.cardId, deckId))
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasDeckCompareCards() {
|
||||||
|
return lightboxState.deckCompareMode && lightboxState.deckCompareCards.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreLightboxFocus() {
|
||||||
|
if (!overlayEl || !lightboxState.isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (overlayEl && lightboxState.isOpen) {
|
||||||
|
overlayEl.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function resolveCardRequestById(cardId) {
|
function resolveCardRequestById(cardId) {
|
||||||
if (!cardId || typeof lightboxState.resolveCardById !== "function") {
|
if (!cardId || typeof lightboxState.resolveCardById !== "function") {
|
||||||
return null;
|
return null;
|
||||||
@@ -145,6 +245,77 @@
|
|||||||
syncOpacityControl();
|
syncOpacityControl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearDeckCompareState() {
|
||||||
|
lightboxState.deckCompareMode = false;
|
||||||
|
lightboxState.selectedCompareDeckIds = [];
|
||||||
|
lightboxState.deckCompareCards = [];
|
||||||
|
lightboxState.deckComparePickerOpen = false;
|
||||||
|
lightboxState.deckCompareMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDeckCompareMode(deckIds, preservePanel = true) {
|
||||||
|
const uniqueDeckIds = [...new Set((Array.isArray(deckIds) ? deckIds : []).map((deckId) => String(deckId || "").trim()).filter(Boolean))]
|
||||||
|
.filter((deckId) => deckId !== lightboxState.activeDeckId)
|
||||||
|
.slice(0, lightboxState.maxCompareDecks);
|
||||||
|
|
||||||
|
lightboxState.selectedCompareDeckIds = uniqueDeckIds;
|
||||||
|
lightboxState.deckCompareMode = uniqueDeckIds.length > 0;
|
||||||
|
lightboxState.deckCompareMessage = "";
|
||||||
|
|
||||||
|
if (!lightboxState.deckCompareMode) {
|
||||||
|
lightboxState.deckCompareCards = [];
|
||||||
|
if (!preservePanel) {
|
||||||
|
lightboxState.deckComparePickerOpen = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightboxState.compareMode = false;
|
||||||
|
clearSecondaryCard();
|
||||||
|
syncDeckCompareCards();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDeckCompareSelection(deckId) {
|
||||||
|
const normalizedDeckId = String(deckId || "").trim();
|
||||||
|
if (!normalizedDeckId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextSelection = [...lightboxState.selectedCompareDeckIds];
|
||||||
|
const existingIndex = nextSelection.indexOf(normalizedDeckId);
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
nextSelection.splice(existingIndex, 1);
|
||||||
|
updateDeckCompareMode(nextSelection);
|
||||||
|
applyComparePresentation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextSelection.length >= lightboxState.maxCompareDecks) {
|
||||||
|
lightboxState.deckCompareMessage = `You can compare up to ${lightboxState.maxCompareDecks} decks at once.`;
|
||||||
|
applyComparePresentation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSelection.push(normalizedDeckId);
|
||||||
|
updateDeckCompareMode(nextSelection);
|
||||||
|
applyComparePresentation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDeckComparePanel() {
|
||||||
|
if (!lightboxState.allowDeckCompare) {
|
||||||
|
lightboxState.deckComparePickerOpen = true;
|
||||||
|
lightboxState.deckCompareMessage = "Add another registered deck to use deck compare.";
|
||||||
|
applyComparePresentation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightboxState.deckComparePickerOpen = !lightboxState.deckComparePickerOpen;
|
||||||
|
lightboxState.deckCompareMessage = lightboxState.availableCompareDecks.length
|
||||||
|
? ""
|
||||||
|
: "Add another registered deck to use deck compare.";
|
||||||
|
applyComparePresentation();
|
||||||
|
}
|
||||||
|
|
||||||
function setOverlayOpacity(value) {
|
function setOverlayOpacity(value) {
|
||||||
const opacity = clampOverlayOpacity(value);
|
const opacity = clampOverlayOpacity(value);
|
||||||
lightboxState.overlayOpacity = opacity;
|
lightboxState.overlayOpacity = opacity;
|
||||||
@@ -164,11 +335,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateImageCursor() {
|
function updateImageCursor() {
|
||||||
|
const nextCursor = zoomed ? "zoom-out" : "zoom-in";
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
compareGridSlots.forEach((slot) => {
|
||||||
|
if (slot?.imageEl) {
|
||||||
|
slot.imageEl.style.cursor = nextCursor;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!imageEl) {
|
if (!imageEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imageEl.style.cursor = zoomed ? "zoom-out" : "zoom-in";
|
imageEl.style.cursor = nextCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRotationTransform(rotated) {
|
function buildRotationTransform(rotated) {
|
||||||
@@ -186,6 +367,15 @@
|
|||||||
function applyTransformOrigins(originX = lightboxState.zoomOriginX, originY = lightboxState.zoomOriginY) {
|
function applyTransformOrigins(originX = lightboxState.zoomOriginX, originY = lightboxState.zoomOriginY) {
|
||||||
const nextOrigin = `${originX}% ${originY}%`;
|
const nextOrigin = `${originX}% ${originY}%`;
|
||||||
|
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
compareGridSlots.forEach((slot) => {
|
||||||
|
if (slot?.imageEl) {
|
||||||
|
slot.imageEl.style.transformOrigin = nextOrigin;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (baseLayerEl) {
|
if (baseLayerEl) {
|
||||||
baseLayerEl.style.transformOrigin = nextOrigin;
|
baseLayerEl.style.transformOrigin = nextOrigin;
|
||||||
}
|
}
|
||||||
@@ -200,6 +390,20 @@
|
|||||||
const showPrimaryRotation = isPrimaryRotationActive();
|
const showPrimaryRotation = isPrimaryRotationActive();
|
||||||
const showOverlayRotation = isOverlayRotationActive();
|
const showOverlayRotation = isOverlayRotationActive();
|
||||||
|
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
compareGridSlots.forEach((slot) => {
|
||||||
|
if (!slot?.imageEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.imageEl.style.transform = `scale(${activeZoomScale}) ${buildRotationTransform(lightboxState.primaryRotated)}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
applyTransformOrigins();
|
||||||
|
updateImageCursor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (baseLayerEl) {
|
if (baseLayerEl) {
|
||||||
baseLayerEl.style.transform = `scale(${activeZoomScale})`;
|
baseLayerEl.style.transform = `scale(${activeZoomScale})`;
|
||||||
}
|
}
|
||||||
@@ -309,6 +513,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
lightboxState.primaryRotated = !lightboxState.primaryRotated;
|
||||||
|
applyZoomTransform();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (lightboxState.compareMode && hasSecondaryCard()) {
|
if (lightboxState.compareMode && hasSecondaryCard()) {
|
||||||
lightboxState.overlayRotated = !lightboxState.overlayRotated;
|
lightboxState.overlayRotated = !lightboxState.overlayRotated;
|
||||||
} else {
|
} else {
|
||||||
@@ -384,6 +594,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncComparePanels() {
|
function syncComparePanels() {
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
renderComparePanel(primaryInfoEl, primaryTitleEl, primaryGroupsEl, primaryHintEl, null, "", "", false);
|
||||||
|
renderComparePanel(secondaryInfoEl, secondaryTitleEl, secondaryGroupsEl, secondaryHintEl, null, "", "", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isComparing = lightboxState.compareMode;
|
const isComparing = lightboxState.compareMode;
|
||||||
const overlaySelected = hasSecondaryCard();
|
const overlaySelected = hasSecondaryCard();
|
||||||
const showPrimaryPanel = Boolean(lightboxState.isOpen && lightboxState.allowOverlayCompare && lightboxState.primaryCard?.label && !zoomed);
|
const showPrimaryPanel = Boolean(lightboxState.isOpen && lightboxState.allowOverlayCompare && lightboxState.primaryCard?.label && !zoomed);
|
||||||
@@ -421,10 +637,144 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
opacityControlEl.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
opacityControlEl.style.display = lightboxState.compareMode && hasSecondaryCard() && !zoomed ? "flex" : "none";
|
opacityControlEl.style.display = lightboxState.compareMode && hasSecondaryCard() && !zoomed ? "flex" : "none";
|
||||||
setOverlayOpacity(lightboxState.overlayOpacity);
|
setOverlayOpacity(lightboxState.overlayOpacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncDeckComparePicker() {
|
||||||
|
if (!deckCompareButtonEl || !deckComparePanelEl || !deckCompareMessageEl || !deckCompareDeckListEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canShowButton = lightboxState.isOpen && !zoomed && !lightboxState.compareMode;
|
||||||
|
deckCompareButtonEl.style.display = canShowButton && (lightboxState.allowDeckCompare || lightboxState.availableCompareDecks.length === 0)
|
||||||
|
? "inline-flex"
|
||||||
|
: "none";
|
||||||
|
deckCompareButtonEl.textContent = lightboxState.selectedCompareDeckIds.length
|
||||||
|
? `Compare (${lightboxState.selectedCompareDeckIds.length})`
|
||||||
|
: "Compare";
|
||||||
|
deckCompareButtonEl.setAttribute("aria-pressed", lightboxState.deckComparePickerOpen ? "true" : "false");
|
||||||
|
|
||||||
|
if (!lightboxState.deckComparePickerOpen || zoomed || lightboxState.compareMode) {
|
||||||
|
deckComparePanelEl.style.display = "none";
|
||||||
|
deckCompareDeckListEl.replaceChildren();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deckComparePanelEl.style.display = "flex";
|
||||||
|
deckCompareMessageEl.textContent = lightboxState.deckCompareMessage
|
||||||
|
|| (lightboxState.availableCompareDecks.length
|
||||||
|
? `Choose up to ${lightboxState.maxCompareDecks} extra decks.`
|
||||||
|
: "Add another registered deck to use deck compare.");
|
||||||
|
|
||||||
|
deckCompareDeckListEl.replaceChildren();
|
||||||
|
|
||||||
|
if (!lightboxState.availableCompareDecks.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightboxState.availableCompareDecks.forEach((deck) => {
|
||||||
|
const isSelected = lightboxState.selectedCompareDeckIds.includes(deck.id);
|
||||||
|
const isDisabled = !isSelected && lightboxState.selectedCompareDeckIds.length >= lightboxState.maxCompareDecks;
|
||||||
|
const deckButtonEl = document.createElement("button");
|
||||||
|
deckButtonEl.type = "button";
|
||||||
|
deckButtonEl.textContent = deck.label;
|
||||||
|
deckButtonEl.disabled = isDisabled;
|
||||||
|
deckButtonEl.setAttribute("aria-pressed", isSelected ? "true" : "false");
|
||||||
|
deckButtonEl.style.padding = "10px 12px";
|
||||||
|
deckButtonEl.style.borderRadius = "12px";
|
||||||
|
deckButtonEl.style.border = isSelected
|
||||||
|
? "1px solid rgba(148, 163, 184, 0.7)"
|
||||||
|
: "1px solid rgba(148, 163, 184, 0.22)";
|
||||||
|
deckButtonEl.style.background = isSelected ? "rgba(30, 41, 59, 0.92)" : "rgba(15, 23, 42, 0.58)";
|
||||||
|
deckButtonEl.style.color = isDisabled ? "rgba(148, 163, 184, 0.52)" : "#f8fafc";
|
||||||
|
deckButtonEl.style.cursor = isDisabled ? "not-allowed" : "pointer";
|
||||||
|
deckButtonEl.style.font = "600 12px/1.3 sans-serif";
|
||||||
|
deckButtonEl.addEventListener("click", () => {
|
||||||
|
toggleDeckCompareSelection(deck.id);
|
||||||
|
restoreLightboxFocus();
|
||||||
|
});
|
||||||
|
deckCompareDeckListEl.appendChild(deckButtonEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lightboxState.selectedCompareDeckIds.length) {
|
||||||
|
const clearButtonEl = document.createElement("button");
|
||||||
|
clearButtonEl.type = "button";
|
||||||
|
clearButtonEl.textContent = "Clear Compare";
|
||||||
|
clearButtonEl.style.marginTop = "4px";
|
||||||
|
clearButtonEl.style.padding = "9px 12px";
|
||||||
|
clearButtonEl.style.borderRadius = "12px";
|
||||||
|
clearButtonEl.style.border = "1px solid rgba(248, 250, 252, 0.16)";
|
||||||
|
clearButtonEl.style.background = "rgba(15, 23, 42, 0.44)";
|
||||||
|
clearButtonEl.style.color = "rgba(248, 250, 252, 0.92)";
|
||||||
|
clearButtonEl.style.cursor = "pointer";
|
||||||
|
clearButtonEl.style.font = "600 12px/1.3 sans-serif";
|
||||||
|
clearButtonEl.addEventListener("click", () => {
|
||||||
|
updateDeckCompareMode([]);
|
||||||
|
applyComparePresentation();
|
||||||
|
restoreLightboxFocus();
|
||||||
|
});
|
||||||
|
deckCompareDeckListEl.appendChild(clearButtonEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDeckCompareGrid() {
|
||||||
|
if (!compareGridEl || !compareGridSlots.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lightboxState.deckCompareMode) {
|
||||||
|
compareGridEl.style.display = "none";
|
||||||
|
compareGridSlots.forEach((slot) => {
|
||||||
|
slot.slotEl.style.display = "none";
|
||||||
|
slot.imageEl.removeAttribute("src");
|
||||||
|
slot.imageEl.alt = "Tarot compare image";
|
||||||
|
slot.imageEl.style.display = "none";
|
||||||
|
slot.fallbackEl.style.display = "none";
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleCards = [lightboxState.primaryCard, ...lightboxState.deckCompareCards].filter(Boolean);
|
||||||
|
compareGridEl.style.display = "grid";
|
||||||
|
compareGridEl.style.gridTemplateColumns = `repeat(${Math.max(1, visibleCards.length)}, minmax(0, 1fr))`;
|
||||||
|
|
||||||
|
compareGridSlots.forEach((slot, index) => {
|
||||||
|
const cardRequest = visibleCards[index] || null;
|
||||||
|
if (!cardRequest) {
|
||||||
|
slot.slotEl.style.display = "none";
|
||||||
|
slot.imageEl.removeAttribute("src");
|
||||||
|
slot.imageEl.style.display = "none";
|
||||||
|
slot.fallbackEl.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.slotEl.style.display = "flex";
|
||||||
|
slot.badgeEl.textContent = cardRequest.deckLabel || (index === 0 ? "Active Deck" : "Compare Deck");
|
||||||
|
slot.cardLabelEl.textContent = cardRequest.label || "Tarot card";
|
||||||
|
|
||||||
|
if (cardRequest.src) {
|
||||||
|
slot.imageEl.src = cardRequest.src;
|
||||||
|
slot.imageEl.alt = cardRequest.altText || cardRequest.label || "Tarot compare image";
|
||||||
|
slot.imageEl.style.display = "block";
|
||||||
|
slot.fallbackEl.style.display = "none";
|
||||||
|
} else {
|
||||||
|
slot.imageEl.removeAttribute("src");
|
||||||
|
slot.imageEl.alt = "";
|
||||||
|
slot.imageEl.style.display = "none";
|
||||||
|
slot.fallbackEl.textContent = cardRequest.missingReason || "Card image unavailable for this deck.";
|
||||||
|
slot.fallbackEl.style.display = "block";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
applyZoomTransform();
|
||||||
|
}
|
||||||
|
|
||||||
function syncHelpUi() {
|
function syncHelpUi() {
|
||||||
if (!helpButtonEl || !helpPanelEl) {
|
if (!helpButtonEl || !helpPanelEl) {
|
||||||
return;
|
return;
|
||||||
@@ -452,15 +802,45 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyComparePresentation() {
|
function applyComparePresentation() {
|
||||||
if (!overlayEl || !backdropEl || !toolbarEl || !stageEl || !frameEl || !imageEl || !overlayImageEl || !compareButtonEl) {
|
if (!overlayEl || !backdropEl || !toolbarEl || !stageEl || !frameEl || !imageEl || !overlayImageEl || !compareButtonEl || !deckCompareButtonEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
compareButtonEl.hidden = zoomed || !lightboxState.allowOverlayCompare || (lightboxState.compareMode && !hasSecondaryCard());
|
compareButtonEl.hidden = zoomed
|
||||||
|
|| lightboxState.deckCompareMode
|
||||||
|
|| !lightboxState.allowOverlayCompare
|
||||||
|
|| (lightboxState.compareMode && !hasSecondaryCard());
|
||||||
compareButtonEl.textContent = lightboxState.compareMode ? "Done Overlay" : "Overlay";
|
compareButtonEl.textContent = lightboxState.compareMode ? "Done Overlay" : "Overlay";
|
||||||
syncHelpUi();
|
syncHelpUi();
|
||||||
syncZoomControl();
|
syncZoomControl();
|
||||||
syncOpacityControl();
|
syncOpacityControl();
|
||||||
|
syncDeckComparePicker();
|
||||||
|
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
overlayEl.style.pointerEvents = "none";
|
||||||
|
backdropEl.style.display = "block";
|
||||||
|
backdropEl.style.pointerEvents = "auto";
|
||||||
|
backdropEl.style.background = "rgba(0, 0, 0, 0.88)";
|
||||||
|
toolbarEl.style.top = "24px";
|
||||||
|
toolbarEl.style.right = "24px";
|
||||||
|
toolbarEl.style.left = "auto";
|
||||||
|
stageEl.style.top = "0";
|
||||||
|
stageEl.style.right = "0";
|
||||||
|
stageEl.style.bottom = "0";
|
||||||
|
stageEl.style.left = "0";
|
||||||
|
stageEl.style.width = "auto";
|
||||||
|
stageEl.style.height = "auto";
|
||||||
|
stageEl.style.transform = "none";
|
||||||
|
stageEl.style.pointerEvents = "auto";
|
||||||
|
frameEl.style.display = "none";
|
||||||
|
primaryInfoEl.style.display = "none";
|
||||||
|
secondaryInfoEl.style.display = "none";
|
||||||
|
renderDeckCompareGrid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frameEl.style.display = "block";
|
||||||
|
compareGridEl.style.display = "none";
|
||||||
|
|
||||||
if (!lightboxState.compareMode) {
|
if (!lightboxState.compareMode) {
|
||||||
overlayEl.style.pointerEvents = "none";
|
overlayEl.style.pointerEvents = "none";
|
||||||
@@ -603,12 +983,12 @@
|
|||||||
applyZoomTransform();
|
applyZoomTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateZoomOrigin(clientX, clientY) {
|
function updateZoomOrigin(clientX, clientY, targetImage = imageEl) {
|
||||||
if (!zoomed || !imageEl) {
|
if (!zoomed || !targetImage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = imageEl.getBoundingClientRect();
|
const rect = targetImage.getBoundingClientRect();
|
||||||
if (!rect.width || !rect.height) {
|
if (!rect.width || !rect.height) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -620,14 +1000,14 @@
|
|||||||
applyTransformOrigins();
|
applyTransformOrigins();
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPointOnCard(clientX, clientY) {
|
function isPointOnCard(clientX, clientY, targetImage = imageEl) {
|
||||||
if (!imageEl) {
|
if (!targetImage) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = imageEl.getBoundingClientRect();
|
const rect = targetImage.getBoundingClientRect();
|
||||||
const naturalWidth = imageEl.naturalWidth;
|
const naturalWidth = targetImage.naturalWidth;
|
||||||
const naturalHeight = imageEl.naturalHeight;
|
const naturalHeight = targetImage.naturalHeight;
|
||||||
|
|
||||||
if (!rect.width || !rect.height || !naturalWidth || !naturalHeight) {
|
if (!rect.width || !rect.height || !naturalWidth || !naturalHeight) {
|
||||||
return true;
|
return true;
|
||||||
@@ -721,6 +1101,7 @@
|
|||||||
"Click card: toggle zoom at the clicked point",
|
"Click card: toggle zoom at the clicked point",
|
||||||
"Left / Right: move cards, or move overlay card in compare mode",
|
"Left / Right: move cards, or move overlay card in compare mode",
|
||||||
"Overlay: pick a second card to compare",
|
"Overlay: pick a second card to compare",
|
||||||
|
"Compare: show the same card from up to two other registered decks",
|
||||||
"Space: swap base and overlay cards",
|
"Space: swap base and overlay cards",
|
||||||
"R: rotate base card, or rotate overlay card in compare mode",
|
"R: rotate base card, or rotate overlay card in compare mode",
|
||||||
"+ / -: zoom in or out in steps",
|
"+ / -: zoom in or out in steps",
|
||||||
@@ -768,6 +1149,18 @@
|
|||||||
compareButtonEl.style.cursor = "pointer";
|
compareButtonEl.style.cursor = "pointer";
|
||||||
compareButtonEl.style.backdropFilter = "blur(12px)";
|
compareButtonEl.style.backdropFilter = "blur(12px)";
|
||||||
|
|
||||||
|
deckCompareButtonEl = document.createElement("button");
|
||||||
|
deckCompareButtonEl.type = "button";
|
||||||
|
deckCompareButtonEl.textContent = "Compare";
|
||||||
|
deckCompareButtonEl.style.border = "1px solid rgba(255, 255, 255, 0.2)";
|
||||||
|
deckCompareButtonEl.style.background = "rgba(15, 23, 42, 0.84)";
|
||||||
|
deckCompareButtonEl.style.color = "#f8fafc";
|
||||||
|
deckCompareButtonEl.style.borderRadius = "999px";
|
||||||
|
deckCompareButtonEl.style.padding = "10px 14px";
|
||||||
|
deckCompareButtonEl.style.font = "600 13px/1.1 sans-serif";
|
||||||
|
deckCompareButtonEl.style.cursor = "pointer";
|
||||||
|
deckCompareButtonEl.style.backdropFilter = "blur(12px)";
|
||||||
|
|
||||||
zoomControlEl = document.createElement("label");
|
zoomControlEl = document.createElement("label");
|
||||||
zoomControlEl.style.display = "flex";
|
zoomControlEl.style.display = "flex";
|
||||||
zoomControlEl.style.alignItems = "center";
|
zoomControlEl.style.alignItems = "center";
|
||||||
@@ -830,7 +1223,40 @@
|
|||||||
|
|
||||||
opacityControlEl.append(opacityTextEl, opacitySliderEl, opacityValueEl);
|
opacityControlEl.append(opacityTextEl, opacitySliderEl, opacityValueEl);
|
||||||
|
|
||||||
toolbarEl.append(compareButtonEl, zoomControlEl, opacityControlEl);
|
deckComparePanelEl = document.createElement("div");
|
||||||
|
deckComparePanelEl.style.position = "fixed";
|
||||||
|
deckComparePanelEl.style.top = "24px";
|
||||||
|
deckComparePanelEl.style.right = "176px";
|
||||||
|
deckComparePanelEl.style.display = "none";
|
||||||
|
deckComparePanelEl.style.flexDirection = "column";
|
||||||
|
deckComparePanelEl.style.gap = "10px";
|
||||||
|
deckComparePanelEl.style.width = "min(280px, calc(100vw - 48px))";
|
||||||
|
deckComparePanelEl.style.padding = "14px 16px";
|
||||||
|
deckComparePanelEl.style.borderRadius = "18px";
|
||||||
|
deckComparePanelEl.style.background = "rgba(2, 6, 23, 0.88)";
|
||||||
|
deckComparePanelEl.style.border = "1px solid rgba(148, 163, 184, 0.16)";
|
||||||
|
deckComparePanelEl.style.color = "#f8fafc";
|
||||||
|
deckComparePanelEl.style.boxShadow = "0 16px 42px rgba(0, 0, 0, 0.34)";
|
||||||
|
deckComparePanelEl.style.backdropFilter = "blur(12px)";
|
||||||
|
deckComparePanelEl.style.pointerEvents = "auto";
|
||||||
|
deckComparePanelEl.style.zIndex = "2";
|
||||||
|
|
||||||
|
const deckCompareTitleEl = document.createElement("div");
|
||||||
|
deckCompareTitleEl.textContent = "Compare Registered Decks";
|
||||||
|
deckCompareTitleEl.style.font = "700 13px/1.3 sans-serif";
|
||||||
|
|
||||||
|
deckCompareMessageEl = document.createElement("div");
|
||||||
|
deckCompareMessageEl.style.font = "500 12px/1.4 sans-serif";
|
||||||
|
deckCompareMessageEl.style.color = "rgba(226, 232, 240, 0.84)";
|
||||||
|
|
||||||
|
deckCompareDeckListEl = document.createElement("div");
|
||||||
|
deckCompareDeckListEl.style.display = "flex";
|
||||||
|
deckCompareDeckListEl.style.flexDirection = "column";
|
||||||
|
deckCompareDeckListEl.style.gap = "8px";
|
||||||
|
|
||||||
|
deckComparePanelEl.append(deckCompareTitleEl, deckCompareMessageEl, deckCompareDeckListEl);
|
||||||
|
|
||||||
|
toolbarEl.append(compareButtonEl, deckCompareButtonEl, zoomControlEl, opacityControlEl);
|
||||||
|
|
||||||
stageEl = document.createElement("div");
|
stageEl = document.createElement("div");
|
||||||
stageEl.style.position = "fixed";
|
stageEl.style.position = "fixed";
|
||||||
@@ -866,6 +1292,100 @@
|
|||||||
overlayLayerEl.style.transition = "transform 120ms ease-out";
|
overlayLayerEl.style.transition = "transform 120ms ease-out";
|
||||||
overlayLayerEl.style.pointerEvents = "none";
|
overlayLayerEl.style.pointerEvents = "none";
|
||||||
|
|
||||||
|
compareGridEl = document.createElement("div");
|
||||||
|
compareGridEl.style.position = "absolute";
|
||||||
|
compareGridEl.style.inset = "0";
|
||||||
|
compareGridEl.style.display = "none";
|
||||||
|
compareGridEl.style.gridTemplateColumns = "repeat(1, minmax(0, 1fr))";
|
||||||
|
compareGridEl.style.gap = "14px";
|
||||||
|
compareGridEl.style.alignItems = "stretch";
|
||||||
|
compareGridEl.style.padding = "76px 24px 24px";
|
||||||
|
compareGridEl.style.boxSizing = "border-box";
|
||||||
|
|
||||||
|
function createCompareGridSlot() {
|
||||||
|
const slotEl = document.createElement("div");
|
||||||
|
slotEl.style.display = "none";
|
||||||
|
slotEl.style.flexDirection = "column";
|
||||||
|
slotEl.style.minWidth = "0";
|
||||||
|
slotEl.style.minHeight = "0";
|
||||||
|
slotEl.style.borderRadius = "22px";
|
||||||
|
slotEl.style.background = "rgba(11, 15, 26, 0.76)";
|
||||||
|
slotEl.style.border = "1px solid rgba(148, 163, 184, 0.12)";
|
||||||
|
slotEl.style.boxShadow = "0 24px 64px rgba(0, 0, 0, 0.36)";
|
||||||
|
slotEl.style.overflow = "hidden";
|
||||||
|
|
||||||
|
const headerEl = document.createElement("div");
|
||||||
|
headerEl.style.display = "flex";
|
||||||
|
headerEl.style.alignItems = "center";
|
||||||
|
headerEl.style.justifyContent = "space-between";
|
||||||
|
headerEl.style.gap = "10px";
|
||||||
|
headerEl.style.padding = "10px 12px";
|
||||||
|
headerEl.style.background = "rgba(15, 23, 42, 0.72)";
|
||||||
|
headerEl.style.borderBottom = "1px solid rgba(148, 163, 184, 0.1)";
|
||||||
|
|
||||||
|
const badgeEl = document.createElement("span");
|
||||||
|
badgeEl.style.font = "700 11px/1.2 sans-serif";
|
||||||
|
badgeEl.style.letterSpacing = "0.08em";
|
||||||
|
badgeEl.style.textTransform = "uppercase";
|
||||||
|
badgeEl.style.color = "#f8fafc";
|
||||||
|
|
||||||
|
const cardLabelEl = document.createElement("span");
|
||||||
|
cardLabelEl.style.font = "500 11px/1.3 sans-serif";
|
||||||
|
cardLabelEl.style.color = "rgba(226, 232, 240, 0.84)";
|
||||||
|
cardLabelEl.style.textAlign = "right";
|
||||||
|
cardLabelEl.style.whiteSpace = "nowrap";
|
||||||
|
cardLabelEl.style.overflow = "hidden";
|
||||||
|
cardLabelEl.style.textOverflow = "ellipsis";
|
||||||
|
|
||||||
|
headerEl.append(badgeEl, cardLabelEl);
|
||||||
|
|
||||||
|
const mediaEl = document.createElement("div");
|
||||||
|
mediaEl.style.position = "relative";
|
||||||
|
mediaEl.style.flex = "1 1 auto";
|
||||||
|
mediaEl.style.minHeight = "0";
|
||||||
|
mediaEl.style.display = "flex";
|
||||||
|
mediaEl.style.alignItems = "center";
|
||||||
|
mediaEl.style.justifyContent = "center";
|
||||||
|
mediaEl.style.padding = "16px";
|
||||||
|
mediaEl.style.background = "rgba(2, 6, 23, 0.4)";
|
||||||
|
mediaEl.style.overflow = "hidden";
|
||||||
|
|
||||||
|
const compareImageEl = document.createElement("img");
|
||||||
|
compareImageEl.alt = "Tarot compare image";
|
||||||
|
compareImageEl.style.width = "100%";
|
||||||
|
compareImageEl.style.height = "100%";
|
||||||
|
compareImageEl.style.objectFit = "contain";
|
||||||
|
compareImageEl.style.cursor = "zoom-in";
|
||||||
|
compareImageEl.style.transform = "scale(1) rotate(0deg)";
|
||||||
|
compareImageEl.style.transformOrigin = "center center";
|
||||||
|
compareImageEl.style.transition = "transform 120ms ease-out";
|
||||||
|
compareImageEl.style.userSelect = "none";
|
||||||
|
|
||||||
|
const fallbackEl = document.createElement("div");
|
||||||
|
fallbackEl.style.display = "none";
|
||||||
|
fallbackEl.style.maxWidth = "260px";
|
||||||
|
fallbackEl.style.padding = "16px";
|
||||||
|
fallbackEl.style.textAlign = "center";
|
||||||
|
fallbackEl.style.font = "600 13px/1.45 sans-serif";
|
||||||
|
fallbackEl.style.color = "rgba(226, 232, 240, 0.88)";
|
||||||
|
|
||||||
|
mediaEl.append(compareImageEl, fallbackEl);
|
||||||
|
slotEl.append(headerEl, mediaEl);
|
||||||
|
|
||||||
|
return {
|
||||||
|
slotEl,
|
||||||
|
badgeEl,
|
||||||
|
cardLabelEl,
|
||||||
|
imageEl: compareImageEl,
|
||||||
|
fallbackEl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
compareGridSlots = [createCompareGridSlot(), createCompareGridSlot(), createCompareGridSlot()];
|
||||||
|
compareGridSlots.forEach((slot) => {
|
||||||
|
compareGridEl.appendChild(slot.slotEl);
|
||||||
|
});
|
||||||
|
|
||||||
imageEl = document.createElement("img");
|
imageEl = document.createElement("img");
|
||||||
imageEl.alt = "Tarot card enlarged image";
|
imageEl.alt = "Tarot card enlarged image";
|
||||||
imageEl.style.width = "100%";
|
imageEl.style.width = "100%";
|
||||||
@@ -939,8 +1459,8 @@
|
|||||||
baseLayerEl.appendChild(imageEl);
|
baseLayerEl.appendChild(imageEl);
|
||||||
overlayLayerEl.appendChild(overlayImageEl);
|
overlayLayerEl.appendChild(overlayImageEl);
|
||||||
frameEl.append(baseLayerEl, overlayLayerEl);
|
frameEl.append(baseLayerEl, overlayLayerEl);
|
||||||
stageEl.append(frameEl, primaryInfoEl, secondaryInfoEl);
|
stageEl.append(frameEl, compareGridEl, primaryInfoEl, secondaryInfoEl);
|
||||||
overlayEl.append(backdropEl, stageEl, toolbarEl, helpButtonEl, helpPanelEl);
|
overlayEl.append(backdropEl, stageEl, toolbarEl, deckComparePanelEl, helpButtonEl, helpPanelEl);
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
if (!overlayEl || !imageEl || !overlayImageEl) {
|
if (!overlayEl || !imageEl || !overlayImageEl) {
|
||||||
@@ -949,15 +1469,28 @@
|
|||||||
|
|
||||||
lightboxState.isOpen = false;
|
lightboxState.isOpen = false;
|
||||||
lightboxState.compareMode = false;
|
lightboxState.compareMode = false;
|
||||||
|
lightboxState.deckCompareMode = false;
|
||||||
lightboxState.allowOverlayCompare = false;
|
lightboxState.allowOverlayCompare = false;
|
||||||
|
lightboxState.allowDeckCompare = false;
|
||||||
lightboxState.primaryCard = null;
|
lightboxState.primaryCard = null;
|
||||||
lightboxState.secondaryCard = null;
|
lightboxState.secondaryCard = null;
|
||||||
|
lightboxState.activeDeckId = "";
|
||||||
|
lightboxState.activeDeckLabel = "";
|
||||||
|
lightboxState.availableCompareDecks = [];
|
||||||
|
lightboxState.selectedCompareDeckIds = [];
|
||||||
|
lightboxState.deckCompareCards = [];
|
||||||
|
lightboxState.maxCompareDecks = 2;
|
||||||
|
lightboxState.deckComparePickerOpen = false;
|
||||||
|
lightboxState.deckCompareMessage = "";
|
||||||
lightboxState.sequenceIds = [];
|
lightboxState.sequenceIds = [];
|
||||||
lightboxState.resolveCardById = null;
|
lightboxState.resolveCardById = null;
|
||||||
|
lightboxState.resolveDeckCardById = null;
|
||||||
lightboxState.onSelectCardId = null;
|
lightboxState.onSelectCardId = null;
|
||||||
lightboxState.overlayOpacity = LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY;
|
lightboxState.overlayOpacity = LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY;
|
||||||
lightboxState.zoomScale = LIGHTBOX_ZOOM_SCALE;
|
lightboxState.zoomScale = LIGHTBOX_ZOOM_SCALE;
|
||||||
lightboxState.helpOpen = false;
|
lightboxState.helpOpen = false;
|
||||||
|
lightboxState.primaryRotated = false;
|
||||||
|
lightboxState.overlayRotated = false;
|
||||||
overlayEl.style.display = "none";
|
overlayEl.style.display = "none";
|
||||||
overlayEl.setAttribute("aria-hidden", "true");
|
overlayEl.setAttribute("aria-hidden", "true");
|
||||||
imageEl.removeAttribute("src");
|
imageEl.removeAttribute("src");
|
||||||
@@ -969,6 +1502,8 @@
|
|||||||
syncHelpUi();
|
syncHelpUi();
|
||||||
syncComparePanels();
|
syncComparePanels();
|
||||||
syncOpacityControl();
|
syncOpacityControl();
|
||||||
|
syncDeckComparePicker();
|
||||||
|
renderDeckCompareGrid();
|
||||||
|
|
||||||
if (previousFocusedEl instanceof HTMLElement) {
|
if (previousFocusedEl instanceof HTMLElement) {
|
||||||
previousFocusedEl.focus({ preventScroll: true });
|
previousFocusedEl.focus({ preventScroll: true });
|
||||||
@@ -1054,7 +1589,11 @@
|
|||||||
imageEl.src = nextCard.src;
|
imageEl.src = nextCard.src;
|
||||||
imageEl.alt = nextCard.altText;
|
imageEl.alt = nextCard.altText;
|
||||||
resetZoom();
|
resetZoom();
|
||||||
|
if (lightboxState.deckCompareMode) {
|
||||||
|
syncDeckCompareCards();
|
||||||
|
} else {
|
||||||
clearSecondaryCard();
|
clearSecondaryCard();
|
||||||
|
}
|
||||||
if (typeof lightboxState.onSelectCardId === "function") {
|
if (typeof lightboxState.onSelectCardId === "function") {
|
||||||
lightboxState.onSelectCardId(nextCard.cardId);
|
lightboxState.onSelectCardId(nextCard.cardId);
|
||||||
}
|
}
|
||||||
@@ -1124,6 +1663,10 @@
|
|||||||
toggleCompareMode();
|
toggleCompareMode();
|
||||||
restoreLightboxFocus();
|
restoreLightboxFocus();
|
||||||
});
|
});
|
||||||
|
deckCompareButtonEl.addEventListener("click", () => {
|
||||||
|
toggleDeckComparePanel();
|
||||||
|
restoreLightboxFocus();
|
||||||
|
});
|
||||||
zoomSliderEl.addEventListener("input", () => {
|
zoomSliderEl.addEventListener("input", () => {
|
||||||
setZoomScale(Number(zoomSliderEl.value) / 100);
|
setZoomScale(Number(zoomSliderEl.value) / 100);
|
||||||
});
|
});
|
||||||
@@ -1170,6 +1713,39 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
compareGridSlots.forEach((slot) => {
|
||||||
|
slot.imageEl.addEventListener("click", (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!isPointOnCard(event.clientX, event.clientY, slot.imageEl)) {
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!zoomed) {
|
||||||
|
zoomed = true;
|
||||||
|
applyZoomTransform();
|
||||||
|
updateZoomOrigin(event.clientX, event.clientY, slot.imageEl);
|
||||||
|
applyComparePresentation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetZoom();
|
||||||
|
applyComparePresentation();
|
||||||
|
});
|
||||||
|
|
||||||
|
slot.imageEl.addEventListener("mousemove", (event) => {
|
||||||
|
updateZoomOrigin(event.clientX, event.clientY, slot.imageEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
slot.imageEl.addEventListener("mouseleave", () => {
|
||||||
|
if (zoomed) {
|
||||||
|
lightboxState.zoomOriginX = 50;
|
||||||
|
lightboxState.zoomOriginY = 50;
|
||||||
|
applyTransformOrigins();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
close();
|
close();
|
||||||
@@ -1268,6 +1844,11 @@
|
|||||||
&& request.sequenceIds.length > 1
|
&& request.sequenceIds.length > 1
|
||||||
&& typeof request.resolveCardById === "function"
|
&& typeof request.resolveCardById === "function"
|
||||||
);
|
);
|
||||||
|
const canDeckCompare = Boolean(
|
||||||
|
request.allowDeckCompare
|
||||||
|
&& normalizedPrimary.cardId
|
||||||
|
&& typeof request.resolveDeckCardById === "function"
|
||||||
|
);
|
||||||
|
|
||||||
if (lightboxState.isOpen && lightboxState.compareMode && lightboxState.allowOverlayCompare && canCompare && normalizedPrimary.cardId) {
|
if (lightboxState.isOpen && lightboxState.compareMode && lightboxState.allowOverlayCompare && canCompare && normalizedPrimary.cardId) {
|
||||||
if (normalizedPrimary.cardId === lightboxState.primaryCard?.cardId) {
|
if (normalizedPrimary.cardId === lightboxState.primaryCard?.cardId) {
|
||||||
@@ -1279,16 +1860,33 @@
|
|||||||
|
|
||||||
lightboxState.isOpen = true;
|
lightboxState.isOpen = true;
|
||||||
lightboxState.compareMode = false;
|
lightboxState.compareMode = false;
|
||||||
|
lightboxState.deckCompareMode = false;
|
||||||
lightboxState.allowOverlayCompare = canCompare;
|
lightboxState.allowOverlayCompare = canCompare;
|
||||||
|
lightboxState.allowDeckCompare = canDeckCompare;
|
||||||
lightboxState.primaryCard = normalizedPrimary;
|
lightboxState.primaryCard = normalizedPrimary;
|
||||||
|
lightboxState.activeDeckId = String(request.activeDeckId || normalizedPrimary.deckId || "").trim();
|
||||||
|
lightboxState.activeDeckLabel = String(request.activeDeckLabel || normalizedPrimary.deckLabel || lightboxState.activeDeckId).trim();
|
||||||
|
lightboxState.availableCompareDecks = canDeckCompare
|
||||||
|
? normalizeDeckOptions(request.availableCompareDecks).filter((deck) => deck.id !== lightboxState.activeDeckId)
|
||||||
|
: [];
|
||||||
|
lightboxState.selectedCompareDeckIds = [];
|
||||||
|
lightboxState.deckCompareCards = [];
|
||||||
|
lightboxState.maxCompareDecks = Number.isInteger(Number(request.maxCompareDecks)) && Number(request.maxCompareDecks) > 0
|
||||||
|
? Number(request.maxCompareDecks)
|
||||||
|
: 2;
|
||||||
|
lightboxState.deckComparePickerOpen = false;
|
||||||
|
lightboxState.deckCompareMessage = "";
|
||||||
lightboxState.sequenceIds = canCompare ? [...request.sequenceIds] : [];
|
lightboxState.sequenceIds = canCompare ? [...request.sequenceIds] : [];
|
||||||
lightboxState.resolveCardById = canCompare ? request.resolveCardById : null;
|
lightboxState.resolveCardById = canCompare ? request.resolveCardById : null;
|
||||||
|
lightboxState.resolveDeckCardById = canDeckCompare ? request.resolveDeckCardById : null;
|
||||||
lightboxState.onSelectCardId = canCompare && typeof request.onSelectCardId === "function"
|
lightboxState.onSelectCardId = canCompare && typeof request.onSelectCardId === "function"
|
||||||
? request.onSelectCardId
|
? request.onSelectCardId
|
||||||
: null;
|
: null;
|
||||||
lightboxState.overlayOpacity = LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY;
|
lightboxState.overlayOpacity = LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY;
|
||||||
lightboxState.zoomScale = LIGHTBOX_ZOOM_SCALE;
|
lightboxState.zoomScale = LIGHTBOX_ZOOM_SCALE;
|
||||||
lightboxState.helpOpen = false;
|
lightboxState.helpOpen = false;
|
||||||
|
lightboxState.primaryRotated = false;
|
||||||
|
lightboxState.overlayRotated = false;
|
||||||
|
|
||||||
imageEl.src = normalizedPrimary.src;
|
imageEl.src = normalizedPrimary.src;
|
||||||
imageEl.alt = normalizedPrimary.altText;
|
imageEl.alt = normalizedPrimary.altText;
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const { resolveTarotCardImage, resolveTarotCardThumbnail, getTarotCardDisplayName, getTarotCardSearchAliases } = window.TarotCardImages || {};
|
const {
|
||||||
|
resolveTarotCardImage,
|
||||||
|
resolveTarotCardThumbnail,
|
||||||
|
getTarotCardDisplayName,
|
||||||
|
getTarotCardSearchAliases,
|
||||||
|
getDeckOptions,
|
||||||
|
getActiveDeck
|
||||||
|
} = window.TarotCardImages || {};
|
||||||
const tarotHouseUi = window.TarotHouseUi || {};
|
const tarotHouseUi = window.TarotHouseUi || {};
|
||||||
const tarotRelationsUi = window.TarotRelationsUi || {};
|
const tarotRelationsUi = window.TarotRelationsUi || {};
|
||||||
const tarotCardDerivations = window.TarotCardDerivations || {};
|
const tarotCardDerivations = window.TarotCardDerivations || {};
|
||||||
@@ -686,29 +693,60 @@
|
|||||||
?.scrollIntoView({ block: "nearest" });
|
?.scrollIntoView({ block: "nearest" });
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLightboxCardRequestById(cardIdToResolve) {
|
function getRegisteredDeckOptionMap() {
|
||||||
|
const entries = typeof getDeckOptions === "function" ? getDeckOptions() : [];
|
||||||
|
return new Map(
|
||||||
|
(Array.isArray(entries) ? entries : [])
|
||||||
|
.map((entry) => ({
|
||||||
|
id: String(entry?.id || "").trim(),
|
||||||
|
label: String(entry?.label || entry?.id || "").trim()
|
||||||
|
}))
|
||||||
|
.filter((entry) => entry.id)
|
||||||
|
.map((entry) => [entry.id, entry])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegisteredDeckList() {
|
||||||
|
return Array.from(getRegisteredDeckOptionMap().values());
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve = "") {
|
||||||
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
||||||
if (!card) {
|
if (!card) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolvedDeckId = String(deckIdToResolve || getActiveDeck?.() || "").trim();
|
||||||
|
const trumpNumber = Number.isFinite(Number(card?.number)) ? Number(card.number) : undefined;
|
||||||
|
const deckOptions = resolvedDeckId ? { deckId: resolvedDeckId, trumpNumber } : { trumpNumber };
|
||||||
const src = typeof resolveTarotCardImage === "function"
|
const src = typeof resolveTarotCardImage === "function"
|
||||||
? resolveTarotCardImage(card.name)
|
? resolveTarotCardImage(card.name, deckOptions)
|
||||||
: "";
|
: "";
|
||||||
if (!src) {
|
const deckMeta = resolvedDeckId ? getRegisteredDeckOptionMap().get(resolvedDeckId) : null;
|
||||||
return null;
|
const label = (typeof getTarotCardDisplayName === "function"
|
||||||
}
|
? getTarotCardDisplayName(card.name, deckOptions)
|
||||||
|
: "") || getDisplayCardName(card) || card.name || "Tarot card enlarged image";
|
||||||
|
|
||||||
const label = getDisplayCardName(card) || card.name || "Tarot card enlarged image";
|
|
||||||
return {
|
return {
|
||||||
src,
|
src,
|
||||||
altText: label,
|
altText: label,
|
||||||
label,
|
label,
|
||||||
cardId: card.id,
|
cardId: card.id,
|
||||||
|
deckId: resolvedDeckId,
|
||||||
|
deckLabel: deckMeta?.label || resolvedDeckId,
|
||||||
compareDetails: tarotDetailRenderer.buildCompareDetails?.(card) || []
|
compareDetails: tarotDetailRenderer.buildCompareDetails?.(card) || []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildLightboxCardRequestById(cardIdToResolve) {
|
||||||
|
const request = buildDeckLightboxCardRequest(cardIdToResolve, getActiveDeck?.() || "");
|
||||||
|
if (!request?.src) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
function renderList(elements) {
|
function renderList(elements) {
|
||||||
if (!elements?.tarotCardListEl) {
|
if (!elements?.tarotCardListEl) {
|
||||||
return;
|
return;
|
||||||
@@ -760,15 +798,25 @@
|
|||||||
openCardLightbox: (src, altText, options = {}) => {
|
openCardLightbox: (src, altText, options = {}) => {
|
||||||
const cardId = String(options?.cardId || "").trim();
|
const cardId = String(options?.cardId || "").trim();
|
||||||
const primaryCardRequest = cardId ? buildLightboxCardRequestById(cardId) : null;
|
const primaryCardRequest = cardId ? buildLightboxCardRequestById(cardId) : null;
|
||||||
|
const activeDeckId = String(getActiveDeck?.() || primaryCardRequest?.deckId || "").trim();
|
||||||
|
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeDeckId);
|
||||||
window.TarotUiLightbox?.open?.({
|
window.TarotUiLightbox?.open?.({
|
||||||
src: primaryCardRequest?.src || src,
|
src: primaryCardRequest?.src || src,
|
||||||
altText: primaryCardRequest?.altText || altText || "Tarot card enlarged image",
|
altText: primaryCardRequest?.altText || altText || "Tarot card enlarged image",
|
||||||
label: primaryCardRequest?.label || altText || "Tarot card enlarged image",
|
label: primaryCardRequest?.label || altText || "Tarot card enlarged image",
|
||||||
cardId: primaryCardRequest?.cardId || cardId,
|
cardId: primaryCardRequest?.cardId || cardId,
|
||||||
|
deckId: primaryCardRequest?.deckId || activeDeckId,
|
||||||
|
deckLabel: primaryCardRequest?.deckLabel || "",
|
||||||
compareDetails: primaryCardRequest?.compareDetails || [],
|
compareDetails: primaryCardRequest?.compareDetails || [],
|
||||||
allowOverlayCompare: true,
|
allowOverlayCompare: true,
|
||||||
|
allowDeckCompare: Boolean(cardId),
|
||||||
|
activeDeckId,
|
||||||
|
activeDeckLabel: primaryCardRequest?.deckLabel || "",
|
||||||
|
availableCompareDecks,
|
||||||
|
maxCompareDecks: 2,
|
||||||
sequenceIds: state.cards.map((card) => card.id),
|
sequenceIds: state.cards.map((card) => card.id),
|
||||||
resolveCardById: buildLightboxCardRequestById,
|
resolveCardById: buildLightboxCardRequestById,
|
||||||
|
resolveDeckCardById: buildDeckLightboxCardRequest,
|
||||||
onSelectCardId: (nextCardId) => {
|
onSelectCardId: (nextCardId) => {
|
||||||
const latestElements = getElements();
|
const latestElements = getElements();
|
||||||
selectCardById(nextCardId, latestElements);
|
selectCardById(nextCardId, latestElements);
|
||||||
|
|||||||
Reference in New Issue
Block a user