(function () { "use strict"; let overlayEl = null; let backdropEl = null; let toolbarEl = null; let helpButtonEl = null; let helpPanelEl = null; let compareButtonEl = null; let zoomControlEl = null; let zoomSliderEl = null; let zoomValueEl = null; let opacityControlEl = null; let opacitySliderEl = null; let opacityValueEl = null; let stageEl = null; let frameEl = null; let baseLayerEl = null; let overlayLayerEl = null; let imageEl = null; let overlayImageEl = null; let primaryInfoEl = null; let primaryTitleEl = null; let primaryGroupsEl = null; let primaryHintEl = null; let secondaryInfoEl = null; let secondaryTitleEl = null; let secondaryGroupsEl = null; let secondaryHintEl = null; let zoomed = false; let previousFocusedEl = null; const LIGHTBOX_ZOOM_SCALE = 6.66; const LIGHTBOX_ZOOM_STEP = 0.1; const LIGHTBOX_PAN_STEP = 4; const LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY = 0.5; const LIGHTBOX_COMPARE_SEQUENCE_STEP_KEYS = new Set(["ArrowLeft", "ArrowRight"]); const lightboxState = { isOpen: false, compareMode: false, allowOverlayCompare: false, primaryCard: null, secondaryCard: null, sequenceIds: [], resolveCardById: null, onSelectCardId: null, overlayOpacity: LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY, zoomScale: LIGHTBOX_ZOOM_SCALE, helpOpen: false, primaryRotated: false, overlayRotated: false, zoomOriginX: 50, zoomOriginY: 50 }; function hasSecondaryCard() { return Boolean(lightboxState.secondaryCard?.src); } function clampOverlayOpacity(value) { const numericValue = Number(value); if (!Number.isFinite(numericValue)) { return LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY; } return Math.min(1, Math.max(0.05, numericValue)); } function clampZoomScale(value) { const numericValue = Number(value); if (!Number.isFinite(numericValue)) { return 1; } return Math.min(LIGHTBOX_ZOOM_SCALE, Math.max(1, numericValue)); } function normalizeCompareDetails(compareDetails) { if (!Array.isArray(compareDetails)) { return []; } return compareDetails .map((group) => ({ title: String(group?.title || "").trim(), items: Array.isArray(group?.items) ? [...new Set(group.items.map((item) => String(item || "").trim()).filter(Boolean))] : [] })) .filter((group) => group.title && group.items.length); } function normalizeOpenRequest(srcOrOptions, altText, extraOptions) { if (srcOrOptions && typeof srcOrOptions === "object" && !Array.isArray(srcOrOptions)) { return { ...srcOrOptions }; } return { ...(extraOptions || {}), src: srcOrOptions, altText }; } function normalizeCardRequest(request) { const normalized = normalizeOpenRequest(request); const label = String(normalized.label || normalized.altText || "Tarot card enlarged image").trim() || "Tarot card enlarged image"; return { src: String(normalized.src || "").trim(), altText: String(normalized.altText || label).trim() || label, label, cardId: String(normalized.cardId || "").trim(), compareDetails: normalizeCompareDetails(normalized.compareDetails) }; } function resolveCardRequestById(cardId) { if (!cardId || typeof lightboxState.resolveCardById !== "function") { return null; } const resolved = lightboxState.resolveCardById(cardId); if (!resolved) { return null; } return normalizeCardRequest({ ...resolved, cardId }); } function clearSecondaryCard() { lightboxState.secondaryCard = null; if (overlayImageEl) { overlayImageEl.removeAttribute("src"); overlayImageEl.alt = ""; overlayImageEl.style.display = "none"; } syncComparePanels(); syncOpacityControl(); } function setOverlayOpacity(value) { const opacity = clampOverlayOpacity(value); lightboxState.overlayOpacity = opacity; if (overlayImageEl) { overlayImageEl.style.opacity = String(opacity); } if (opacitySliderEl) { opacitySliderEl.value = String(Math.round(opacity * 100)); opacitySliderEl.disabled = !lightboxState.compareMode || !hasSecondaryCard(); } if (opacityValueEl) { opacityValueEl.textContent = `${Math.round(opacity * 100)}%`; } } function updateImageCursor() { if (!imageEl) { return; } imageEl.style.cursor = zoomed ? "zoom-out" : "zoom-in"; } function buildRotationTransform(rotated) { return rotated ? "rotate(180deg)" : "rotate(0deg)"; } function isPrimaryRotationActive() { return !lightboxState.compareMode && lightboxState.primaryRotated; } function isOverlayRotationActive() { return lightboxState.compareMode && hasSecondaryCard() && lightboxState.overlayRotated; } function applyTransformOrigins(originX = lightboxState.zoomOriginX, originY = lightboxState.zoomOriginY) { const nextOrigin = `${originX}% ${originY}%`; if (baseLayerEl) { baseLayerEl.style.transformOrigin = nextOrigin; } if (overlayLayerEl) { overlayLayerEl.style.transformOrigin = nextOrigin; } } function applyZoomTransform() { const activeZoomScale = zoomed ? lightboxState.zoomScale : 1; const showPrimaryRotation = isPrimaryRotationActive(); const showOverlayRotation = isOverlayRotationActive(); if (baseLayerEl) { baseLayerEl.style.transform = `scale(${activeZoomScale})`; } if (overlayLayerEl) { overlayLayerEl.style.transform = `scale(${activeZoomScale})`; } if (imageEl) { imageEl.style.transform = buildRotationTransform(showPrimaryRotation); } if (overlayImageEl) { overlayImageEl.style.transform = buildRotationTransform(showOverlayRotation); } applyTransformOrigins(); updateImageCursor(); } function setZoomScale(value) { const zoomScale = clampZoomScale(value); lightboxState.zoomScale = zoomScale; if (zoomSliderEl) { zoomSliderEl.value = String(Math.round(zoomScale * 100)); } if (zoomValueEl) { zoomValueEl.textContent = `${Math.round(zoomScale * 100)}%`; } if (lightboxState.isOpen) { applyComparePresentation(); return; } applyZoomTransform(); } function isZoomInKey(event) { return event.key === "+" || event.key === "=" || event.code === "NumpadAdd"; } function isZoomOutKey(event) { return event.key === "-" || event.key === "_" || event.code === "NumpadSubtract"; } function isRotateKey(event) { return event.code === "KeyR" || String(event.key || "").toLowerCase() === "r"; } function stepZoom(direction) { if (!lightboxState.isOpen) { return; } const activeScale = zoomed ? lightboxState.zoomScale : 1; const nextScale = clampZoomScale(activeScale + (direction * LIGHTBOX_ZOOM_STEP)); zoomed = nextScale > 1; setZoomScale(nextScale); if (!zoomed && imageEl) { lightboxState.zoomOriginX = 50; lightboxState.zoomOriginY = 50; } if (!zoomed && overlayImageEl) { lightboxState.zoomOriginX = 50; lightboxState.zoomOriginY = 50; } } function isPanUpKey(event) { return event.code === "KeyW" || String(event.key || "").toLowerCase() === "w"; } function isPanLeftKey(event) { return event.code === "KeyA" || String(event.key || "").toLowerCase() === "a"; } function isPanDownKey(event) { return event.code === "KeyS" || String(event.key || "").toLowerCase() === "s"; } function isPanRightKey(event) { return event.code === "KeyD" || String(event.key || "").toLowerCase() === "d"; } function stepPan(deltaX, deltaY) { if (!lightboxState.isOpen || !zoomed || !imageEl) { return; } lightboxState.zoomOriginX = Math.min(100, Math.max(0, lightboxState.zoomOriginX + deltaX)); lightboxState.zoomOriginY = Math.min(100, Math.max(0, lightboxState.zoomOriginY + deltaY)); applyTransformOrigins(); } function toggleRotation() { if (!lightboxState.isOpen) { return; } if (lightboxState.compareMode && hasSecondaryCard()) { lightboxState.overlayRotated = !lightboxState.overlayRotated; } else { lightboxState.primaryRotated = !lightboxState.primaryRotated; } applyZoomTransform(); } function createCompareGroupElement(group) { const sectionEl = document.createElement("section"); sectionEl.style.display = "flex"; sectionEl.style.flexDirection = "column"; sectionEl.style.gap = "5px"; sectionEl.style.paddingTop = "8px"; sectionEl.style.borderTop = "1px solid rgba(148, 163, 184, 0.14)"; const titleEl = document.createElement("div"); titleEl.textContent = group.title; titleEl.style.font = "600 10px/1.2 sans-serif"; titleEl.style.letterSpacing = "0.1em"; titleEl.style.textTransform = "uppercase"; titleEl.style.color = "rgba(148, 163, 184, 0.92)"; const valuesEl = document.createElement("div"); valuesEl.style.display = "flex"; valuesEl.style.flexDirection = "column"; valuesEl.style.gap = "3px"; group.items.forEach((item) => { const itemEl = document.createElement("div"); itemEl.textContent = item; itemEl.style.font = "500 12px/1.35 sans-serif"; itemEl.style.color = "#f8fafc"; valuesEl.appendChild(itemEl); }); sectionEl.append(titleEl, valuesEl); return sectionEl; } function renderComparePanel(panelEl, titleEl, groupsEl, hintEl, cardRequest, roleLabel, hintText, isVisible) { if (!panelEl || !titleEl || !groupsEl || !hintEl) { return; } if (!isVisible || !cardRequest?.label) { panelEl.style.display = "none"; titleEl.textContent = ""; hintEl.textContent = ""; groupsEl.replaceChildren(); return; } panelEl.style.display = "flex"; titleEl.textContent = `${roleLabel}: ${cardRequest.label}`; groupsEl.replaceChildren(); if (Array.isArray(cardRequest.compareDetails) && cardRequest.compareDetails.length) { cardRequest.compareDetails.forEach((group) => { groupsEl.appendChild(createCompareGroupElement(group)); }); } else { const emptyEl = document.createElement("div"); emptyEl.textContent = "No compare metadata available."; emptyEl.style.font = "500 12px/1.35 sans-serif"; emptyEl.style.color = "rgba(226, 232, 240, 0.8)"; groupsEl.appendChild(emptyEl); } hintEl.textContent = hintText; hintEl.style.display = hintText ? "block" : "none"; } function syncComparePanels() { const isComparing = lightboxState.compareMode; const overlaySelected = hasSecondaryCard(); const showPrimaryPanel = Boolean(lightboxState.isOpen && lightboxState.allowOverlayCompare && lightboxState.primaryCard?.label && !zoomed); const showSecondaryPanel = Boolean(isComparing && overlaySelected && lightboxState.secondaryCard?.label && !zoomed); renderComparePanel( primaryInfoEl, primaryTitleEl, primaryGroupsEl, primaryHintEl, lightboxState.primaryCard, "Base", isComparing ? (overlaySelected ? "Use Left and Right arrows to move through the overlaid card." : "Click another House card behind the dock, or use Left and Right arrows to pick the first overlay card.") : "Use Left and Right arrows to move through cards. Click Overlay to compare.", showPrimaryPanel ); renderComparePanel( secondaryInfoEl, secondaryTitleEl, secondaryGroupsEl, secondaryHintEl, lightboxState.secondaryCard, "Overlay", overlaySelected ? "Use Left and Right arrows to swap the overlay card." : "", showSecondaryPanel ); } function syncOpacityControl() { if (!opacityControlEl) { return; } opacityControlEl.style.display = lightboxState.compareMode && hasSecondaryCard() && !zoomed ? "flex" : "none"; setOverlayOpacity(lightboxState.overlayOpacity); } function syncHelpUi() { if (!helpButtonEl || !helpPanelEl) { return; } const canShow = lightboxState.isOpen && !zoomed; helpButtonEl.style.display = canShow ? "inline-flex" : "none"; helpPanelEl.style.display = canShow && lightboxState.helpOpen ? "flex" : "none"; helpButtonEl.textContent = lightboxState.helpOpen ? "Hide Help" : "Help"; } function syncZoomControl() { if (!zoomControlEl) { return; } zoomControlEl.style.display = lightboxState.isOpen && !zoomed ? "flex" : "none"; if (zoomSliderEl) { zoomSliderEl.value = String(Math.round(lightboxState.zoomScale * 100)); } if (zoomValueEl) { zoomValueEl.textContent = `${Math.round(lightboxState.zoomScale * 100)}%`; } } function applyComparePresentation() { if (!overlayEl || !backdropEl || !toolbarEl || !stageEl || !frameEl || !imageEl || !overlayImageEl || !compareButtonEl) { return; } compareButtonEl.hidden = zoomed || !lightboxState.allowOverlayCompare || (lightboxState.compareMode && !hasSecondaryCard()); compareButtonEl.textContent = lightboxState.compareMode ? "Done Overlay" : "Overlay"; syncHelpUi(); syncZoomControl(); syncOpacityControl(); if (!lightboxState.compareMode) { overlayEl.style.pointerEvents = "none"; backdropEl.style.display = "block"; backdropEl.style.pointerEvents = "auto"; backdropEl.style.background = "rgba(0, 0, 0, 0.82)"; 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.position = "relative"; frameEl.style.width = "100%"; frameEl.style.height = "100%"; frameEl.style.maxWidth = "none"; frameEl.style.maxHeight = "none"; frameEl.style.borderRadius = "0"; frameEl.style.background = "transparent"; frameEl.style.boxShadow = "none"; frameEl.style.overflow = "hidden"; primaryInfoEl.style.left = "auto"; primaryInfoEl.style.right = "18px"; primaryInfoEl.style.top = "50%"; primaryInfoEl.style.bottom = "auto"; primaryInfoEl.style.width = "clamp(220px, 20vw, 320px)"; primaryInfoEl.style.transform = "translateY(-50%)"; imageEl.style.width = "100%"; imageEl.style.height = "100%"; imageEl.style.maxWidth = "none"; imageEl.style.maxHeight = "none"; imageEl.style.objectFit = "contain"; overlayImageEl.style.display = "none"; secondaryInfoEl.style.display = "none"; syncComparePanels(); applyZoomTransform(); return; } overlayEl.style.pointerEvents = "none"; backdropEl.style.display = "none"; backdropEl.style.pointerEvents = "none"; toolbarEl.style.top = "18px"; toolbarEl.style.right = "18px"; toolbarEl.style.left = "auto"; stageEl.style.pointerEvents = "auto"; frameEl.style.position = "relative"; frameEl.style.width = "100%"; frameEl.style.height = "100%"; frameEl.style.maxWidth = "none"; frameEl.style.maxHeight = "none"; frameEl.style.overflow = "hidden"; imageEl.style.width = "100%"; imageEl.style.height = "100%"; imageEl.style.maxWidth = "none"; imageEl.style.maxHeight = "none"; imageEl.style.objectFit = "contain"; updateImageCursor(); if (zoomed && hasSecondaryCard()) { 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"; frameEl.style.borderRadius = "0"; frameEl.style.background = "transparent"; frameEl.style.boxShadow = "none"; } else if (!hasSecondaryCard()) { stageEl.style.top = "auto"; stageEl.style.right = "18px"; stageEl.style.bottom = "18px"; stageEl.style.left = "auto"; stageEl.style.width = "clamp(180px, 18vw, 280px)"; stageEl.style.height = "min(44vh, 520px)"; stageEl.style.transform = "none"; frameEl.style.borderRadius = "22px"; frameEl.style.background = "rgba(13, 13, 20, 0.9)"; frameEl.style.boxShadow = "0 24px 64px rgba(0, 0, 0, 0.5)"; primaryInfoEl.style.left = "auto"; primaryInfoEl.style.right = "calc(100% + 16px)"; primaryInfoEl.style.top = "50%"; primaryInfoEl.style.bottom = "auto"; primaryInfoEl.style.width = "clamp(220px, 22vw, 320px)"; primaryInfoEl.style.transform = "translateY(-50%)"; } else { stageEl.style.top = "50%"; stageEl.style.right = "auto"; stageEl.style.bottom = "auto"; stageEl.style.left = "50%"; stageEl.style.width = "min(44vw, 560px)"; stageEl.style.height = "min(92vh, 1400px)"; stageEl.style.transform = "translate(-50%, -50%)"; frameEl.style.borderRadius = "28px"; frameEl.style.background = "rgba(11, 15, 26, 0.88)"; frameEl.style.boxShadow = "0 30px 90px rgba(0, 0, 0, 0.56)"; primaryInfoEl.style.left = "auto"; primaryInfoEl.style.right = "calc(100% + 10px)"; primaryInfoEl.style.top = "50%"; primaryInfoEl.style.bottom = "auto"; primaryInfoEl.style.width = "clamp(180px, 15vw, 220px)"; primaryInfoEl.style.transform = "translateY(-50%)"; secondaryInfoEl.style.left = "calc(100% + 10px)"; secondaryInfoEl.style.right = "auto"; secondaryInfoEl.style.top = "50%"; secondaryInfoEl.style.bottom = "auto"; secondaryInfoEl.style.width = "clamp(180px, 15vw, 220px)"; secondaryInfoEl.style.transform = "translateY(-50%)"; } if (hasSecondaryCard()) { overlayImageEl.style.display = "block"; } else { overlayImageEl.style.display = "none"; secondaryInfoEl.style.display = "none"; } syncComparePanels(); applyZoomTransform(); setOverlayOpacity(lightboxState.overlayOpacity); } function resetZoom() { if (!imageEl && !overlayImageEl) { return; } lightboxState.zoomOriginX = 50; lightboxState.zoomOriginY = 50; applyTransformOrigins(); zoomed = false; applyZoomTransform(); } function updateZoomOrigin(clientX, clientY) { if (!zoomed || !imageEl) { return; } const rect = imageEl.getBoundingClientRect(); if (!rect.width || !rect.height) { return; } const x = Math.min(100, Math.max(0, ((clientX - rect.left) / rect.width) * 100)); const y = Math.min(100, Math.max(0, ((clientY - rect.top) / rect.height) * 100)); lightboxState.zoomOriginX = x; lightboxState.zoomOriginY = y; applyTransformOrigins(); } function isPointOnCard(clientX, clientY) { if (!imageEl) { return false; } const rect = imageEl.getBoundingClientRect(); const naturalWidth = imageEl.naturalWidth; const naturalHeight = imageEl.naturalHeight; if (!rect.width || !rect.height || !naturalWidth || !naturalHeight) { return true; } const frameAspect = rect.width / rect.height; const imageAspect = naturalWidth / naturalHeight; let renderWidth = rect.width; let renderHeight = rect.height; if (imageAspect > frameAspect) { renderHeight = rect.width / imageAspect; } else { renderWidth = rect.height * imageAspect; } const left = rect.left + (rect.width - renderWidth) / 2; const top = rect.top + (rect.height - renderHeight) / 2; const right = left + renderWidth; const bottom = top + renderHeight; return clientX >= left && clientX <= right && clientY >= top && clientY <= bottom; } function ensure() { if (overlayEl && imageEl && overlayImageEl) { return; } overlayEl = document.createElement("div"); overlayEl.setAttribute("aria-hidden", "true"); overlayEl.setAttribute("role", "dialog"); overlayEl.setAttribute("aria-modal", "true"); overlayEl.tabIndex = -1; overlayEl.style.position = "fixed"; overlayEl.style.inset = "0"; overlayEl.style.display = "none"; overlayEl.style.zIndex = "9999"; overlayEl.style.pointerEvents = "none"; helpButtonEl = document.createElement("button"); helpButtonEl.type = "button"; helpButtonEl.textContent = "Help"; helpButtonEl.style.position = "fixed"; helpButtonEl.style.top = "24px"; helpButtonEl.style.left = "24px"; helpButtonEl.style.display = "none"; helpButtonEl.style.alignItems = "center"; helpButtonEl.style.justifyContent = "center"; helpButtonEl.style.border = "1px solid rgba(255, 255, 255, 0.2)"; helpButtonEl.style.background = "rgba(15, 23, 42, 0.84)"; helpButtonEl.style.color = "#f8fafc"; helpButtonEl.style.borderRadius = "999px"; helpButtonEl.style.padding = "10px 14px"; helpButtonEl.style.font = "600 13px/1.1 sans-serif"; helpButtonEl.style.cursor = "pointer"; helpButtonEl.style.backdropFilter = "blur(12px)"; helpButtonEl.style.pointerEvents = "auto"; helpButtonEl.style.zIndex = "2"; helpPanelEl = document.createElement("div"); helpPanelEl.style.position = "fixed"; helpPanelEl.style.top = "72px"; helpPanelEl.style.left = "24px"; helpPanelEl.style.display = "none"; helpPanelEl.style.flexDirection = "column"; helpPanelEl.style.gap = "8px"; helpPanelEl.style.width = "min(320px, calc(100vw - 48px))"; helpPanelEl.style.padding = "14px 16px"; helpPanelEl.style.borderRadius = "18px"; helpPanelEl.style.background = "rgba(2, 6, 23, 0.88)"; helpPanelEl.style.border = "1px solid rgba(148, 163, 184, 0.16)"; helpPanelEl.style.color = "#f8fafc"; helpPanelEl.style.boxShadow = "0 16px 42px rgba(0, 0, 0, 0.34)"; helpPanelEl.style.backdropFilter = "blur(12px)"; helpPanelEl.style.pointerEvents = "auto"; helpPanelEl.style.zIndex = "2"; const helpTitleEl = document.createElement("div"); helpTitleEl.textContent = "Lightbox Shortcuts"; helpTitleEl.style.font = "700 13px/1.3 sans-serif"; const helpListEl = document.createElement("div"); helpListEl.style.display = "flex"; helpListEl.style.flexDirection = "column"; helpListEl.style.gap = "6px"; helpListEl.style.font = "500 12px/1.4 sans-serif"; helpListEl.style.color = "rgba(226, 232, 240, 0.92)"; [ "Click card: toggle zoom at the clicked point", "Left / Right: move cards, or move overlay card in compare mode", "Overlay: pick a second card to compare", "Space: swap base and overlay cards", "R: rotate base card, or rotate overlay card in compare mode", "+ / -: zoom in or out in steps", "W A S D: pan while zoomed", "Escape or backdrop click: close" ].forEach((line) => { const lineEl = document.createElement("div"); lineEl.textContent = line; helpListEl.appendChild(lineEl); }); helpPanelEl.append(helpTitleEl, helpListEl); backdropEl = document.createElement("button"); backdropEl.type = "button"; backdropEl.setAttribute("aria-label", "Close enlarged tarot card"); backdropEl.style.position = "absolute"; backdropEl.style.inset = "0"; backdropEl.style.border = "none"; backdropEl.style.padding = "0"; backdropEl.style.margin = "0"; backdropEl.style.background = "rgba(0, 0, 0, 0.82)"; backdropEl.style.cursor = "pointer"; toolbarEl = document.createElement("div"); toolbarEl.style.position = "fixed"; toolbarEl.style.top = "24px"; toolbarEl.style.right = "24px"; toolbarEl.style.display = "flex"; toolbarEl.style.flexDirection = "column"; toolbarEl.style.alignItems = "flex-end"; toolbarEl.style.gap = "8px"; toolbarEl.style.pointerEvents = "auto"; toolbarEl.style.zIndex = "2"; compareButtonEl = document.createElement("button"); compareButtonEl.type = "button"; compareButtonEl.textContent = "Overlay"; compareButtonEl.style.border = "1px solid rgba(255, 255, 255, 0.2)"; compareButtonEl.style.background = "rgba(15, 23, 42, 0.84)"; compareButtonEl.style.color = "#f8fafc"; compareButtonEl.style.borderRadius = "999px"; compareButtonEl.style.padding = "10px 14px"; compareButtonEl.style.font = "600 13px/1.1 sans-serif"; compareButtonEl.style.cursor = "pointer"; compareButtonEl.style.backdropFilter = "blur(12px)"; zoomControlEl = document.createElement("label"); zoomControlEl.style.display = "flex"; zoomControlEl.style.alignItems = "center"; zoomControlEl.style.gap = "8px"; zoomControlEl.style.padding = "10px 14px"; zoomControlEl.style.border = "1px solid rgba(255, 255, 255, 0.2)"; zoomControlEl.style.borderRadius = "999px"; zoomControlEl.style.background = "rgba(15, 23, 42, 0.84)"; zoomControlEl.style.color = "#f8fafc"; zoomControlEl.style.font = "600 12px/1.1 sans-serif"; zoomControlEl.style.backdropFilter = "blur(12px)"; const zoomTextEl = document.createElement("span"); zoomTextEl.textContent = "Zoom"; zoomSliderEl = document.createElement("input"); zoomSliderEl.type = "range"; zoomSliderEl.min = "100"; zoomSliderEl.max = String(Math.round(LIGHTBOX_ZOOM_SCALE * 100)); zoomSliderEl.step = "10"; zoomSliderEl.value = String(Math.round(LIGHTBOX_ZOOM_SCALE * 100)); zoomSliderEl.style.width = "110px"; zoomSliderEl.style.cursor = "pointer"; zoomValueEl = document.createElement("span"); zoomValueEl.textContent = `${Math.round(LIGHTBOX_ZOOM_SCALE * 100)}%`; zoomValueEl.style.minWidth = "42px"; zoomValueEl.style.textAlign = "right"; zoomControlEl.append(zoomTextEl, zoomSliderEl, zoomValueEl); opacityControlEl = document.createElement("label"); opacityControlEl.style.display = "none"; opacityControlEl.style.alignItems = "center"; opacityControlEl.style.gap = "8px"; opacityControlEl.style.padding = "10px 14px"; opacityControlEl.style.border = "1px solid rgba(255, 255, 255, 0.2)"; opacityControlEl.style.borderRadius = "999px"; opacityControlEl.style.background = "rgba(15, 23, 42, 0.84)"; opacityControlEl.style.color = "#f8fafc"; opacityControlEl.style.font = "600 12px/1.1 sans-serif"; opacityControlEl.style.backdropFilter = "blur(12px)"; const opacityTextEl = document.createElement("span"); opacityTextEl.textContent = "Overlay"; opacitySliderEl = document.createElement("input"); opacitySliderEl.type = "range"; opacitySliderEl.min = "5"; opacitySliderEl.max = "100"; opacitySliderEl.step = "5"; opacitySliderEl.value = String(Math.round(LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY * 100)); opacitySliderEl.style.width = "110px"; opacitySliderEl.style.cursor = "pointer"; opacityValueEl = document.createElement("span"); opacityValueEl.textContent = `${Math.round(LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY * 100)}%`; opacityValueEl.style.minWidth = "34px"; opacityValueEl.style.textAlign = "right"; opacityControlEl.append(opacityTextEl, opacitySliderEl, opacityValueEl); toolbarEl.append(compareButtonEl, zoomControlEl, opacityControlEl); stageEl = document.createElement("div"); stageEl.style.position = "fixed"; stageEl.style.top = "0"; stageEl.style.right = "0"; stageEl.style.bottom = "0"; stageEl.style.left = "0"; stageEl.style.pointerEvents = "auto"; stageEl.style.overflow = "visible"; stageEl.style.transition = "top 220ms ease, right 220ms ease, bottom 220ms ease, left 220ms ease, width 220ms ease, height 220ms ease, transform 220ms ease"; stageEl.style.transform = "none"; stageEl.style.zIndex = "1"; frameEl = document.createElement("div"); frameEl.style.position = "relative"; frameEl.style.width = "100%"; frameEl.style.height = "100%"; frameEl.style.overflow = "hidden"; frameEl.style.transition = "border-radius 220ms ease, background 220ms ease, box-shadow 220ms ease"; baseLayerEl = document.createElement("div"); baseLayerEl.style.position = "absolute"; baseLayerEl.style.inset = "0"; baseLayerEl.style.transform = "scale(1)"; baseLayerEl.style.transformOrigin = "50% 50%"; baseLayerEl.style.transition = "transform 120ms ease-out"; overlayLayerEl = document.createElement("div"); overlayLayerEl.style.position = "absolute"; overlayLayerEl.style.inset = "0"; overlayLayerEl.style.transform = "scale(1)"; overlayLayerEl.style.transformOrigin = "50% 50%"; overlayLayerEl.style.transition = "transform 120ms ease-out"; overlayLayerEl.style.pointerEvents = "none"; imageEl = document.createElement("img"); imageEl.alt = "Tarot card enlarged image"; imageEl.style.width = "100%"; imageEl.style.height = "100%"; imageEl.style.objectFit = "contain"; imageEl.style.cursor = "zoom-in"; imageEl.style.transform = "rotate(0deg)"; imageEl.style.transformOrigin = "center center"; imageEl.style.transition = "transform 120ms ease-out, opacity 180ms ease"; imageEl.style.userSelect = "none"; overlayImageEl = document.createElement("img"); overlayImageEl.alt = "Tarot card overlay image"; overlayImageEl.style.width = "100%"; overlayImageEl.style.height = "100%"; overlayImageEl.style.objectFit = "contain"; overlayImageEl.style.opacity = String(LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY); overlayImageEl.style.pointerEvents = "none"; overlayImageEl.style.display = "none"; overlayImageEl.style.transform = "rotate(0deg)"; overlayImageEl.style.transformOrigin = "center center"; overlayImageEl.style.transition = "opacity 180ms ease"; function createInfoPanel() { const panelEl = document.createElement("div"); panelEl.style.position = "absolute"; panelEl.style.display = "none"; panelEl.style.flexDirection = "column"; panelEl.style.gap = "10px"; panelEl.style.padding = "14px 16px"; panelEl.style.borderRadius = "18px"; panelEl.style.background = "rgba(2, 6, 23, 0.8)"; panelEl.style.border = "1px solid rgba(148, 163, 184, 0.16)"; panelEl.style.color = "#f8fafc"; panelEl.style.backdropFilter = "blur(12px)"; panelEl.style.boxShadow = "0 16px 42px rgba(0, 0, 0, 0.34)"; panelEl.style.transition = "opacity 180ms ease, transform 180ms ease"; panelEl.style.transform = "translateY(-50%)"; panelEl.style.pointerEvents = "none"; panelEl.style.maxHeight = "min(78vh, 760px)"; panelEl.style.overflowY = "auto"; const titleEl = document.createElement("div"); titleEl.style.font = "700 13px/1.3 sans-serif"; titleEl.style.color = "#f8fafc"; const groupsEl = document.createElement("div"); groupsEl.style.display = "flex"; groupsEl.style.flexDirection = "column"; groupsEl.style.gap = "0"; const hintEl = document.createElement("div"); hintEl.style.font = "500 11px/1.35 sans-serif"; hintEl.style.color = "rgba(226, 232, 240, 0.82)"; panelEl.append(titleEl, groupsEl, hintEl); return { panelEl, titleEl, groupsEl, hintEl }; } const primaryPanel = createInfoPanel(); primaryInfoEl = primaryPanel.panelEl; primaryTitleEl = primaryPanel.titleEl; primaryGroupsEl = primaryPanel.groupsEl; primaryHintEl = primaryPanel.hintEl; const secondaryPanel = createInfoPanel(); secondaryInfoEl = secondaryPanel.panelEl; secondaryTitleEl = secondaryPanel.titleEl; secondaryGroupsEl = secondaryPanel.groupsEl; secondaryHintEl = secondaryPanel.hintEl; baseLayerEl.appendChild(imageEl); overlayLayerEl.appendChild(overlayImageEl); frameEl.append(baseLayerEl, overlayLayerEl); stageEl.append(frameEl, primaryInfoEl, secondaryInfoEl); overlayEl.append(backdropEl, stageEl, toolbarEl, helpButtonEl, helpPanelEl); const close = () => { if (!overlayEl || !imageEl || !overlayImageEl) { return; } lightboxState.isOpen = false; lightboxState.compareMode = false; lightboxState.allowOverlayCompare = false; lightboxState.primaryCard = null; lightboxState.secondaryCard = null; lightboxState.sequenceIds = []; lightboxState.resolveCardById = null; lightboxState.onSelectCardId = null; lightboxState.overlayOpacity = LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY; lightboxState.zoomScale = LIGHTBOX_ZOOM_SCALE; lightboxState.helpOpen = false; overlayEl.style.display = "none"; overlayEl.setAttribute("aria-hidden", "true"); imageEl.removeAttribute("src"); imageEl.alt = "Tarot card enlarged image"; overlayImageEl.removeAttribute("src"); overlayImageEl.alt = ""; overlayImageEl.style.display = "none"; resetZoom(); syncHelpUi(); syncComparePanels(); syncOpacityControl(); if (previousFocusedEl instanceof HTMLElement) { previousFocusedEl.focus({ preventScroll: true }); } previousFocusedEl = null; }; function toggleCompareMode() { if (!lightboxState.allowOverlayCompare || !lightboxState.primaryCard) { return; } lightboxState.compareMode = !lightboxState.compareMode; if (!lightboxState.compareMode) { clearSecondaryCard(); } applyComparePresentation(); } function setSecondaryCard(cardRequest, syncSelection = false) { const normalizedCard = normalizeCardRequest(cardRequest); if (!normalizedCard.src || !normalizedCard.cardId || normalizedCard.cardId === lightboxState.primaryCard?.cardId) { return false; } lightboxState.secondaryCard = normalizedCard; overlayImageEl.src = normalizedCard.src; overlayImageEl.alt = normalizedCard.altText; overlayImageEl.style.display = "block"; overlayImageEl.style.opacity = String(lightboxState.overlayOpacity); if (syncSelection && typeof lightboxState.onSelectCardId === "function") { lightboxState.onSelectCardId(normalizedCard.cardId); } applyComparePresentation(); return true; } function stepSecondaryCard(direction) { const sequence = Array.isArray(lightboxState.sequenceIds) ? lightboxState.sequenceIds : []; if (!lightboxState.compareMode || sequence.length < 2 || typeof lightboxState.resolveCardById !== "function") { return; } const anchorId = lightboxState.secondaryCard?.cardId || lightboxState.primaryCard?.cardId; const startIndex = sequence.indexOf(anchorId); if (startIndex < 0) { return; } for (let offset = 1; offset <= sequence.length; offset += 1) { const nextIndex = (startIndex + direction * offset + sequence.length) % sequence.length; const nextCardId = sequence[nextIndex]; if (!nextCardId || nextCardId === lightboxState.primaryCard?.cardId) { continue; } const nextCard = resolveCardRequestById(nextCardId); if (nextCard && setSecondaryCard(nextCard, true)) { break; } } } function stepPrimaryCard(direction) { const sequence = Array.isArray(lightboxState.sequenceIds) ? lightboxState.sequenceIds : []; if (lightboxState.compareMode || sequence.length < 2 || typeof lightboxState.resolveCardById !== "function") { return; } const startIndex = sequence.indexOf(lightboxState.primaryCard?.cardId); if (startIndex < 0) { return; } const nextIndex = (startIndex + direction + sequence.length) % sequence.length; const nextCardId = sequence[nextIndex]; const nextCard = resolveCardRequestById(nextCardId); if (!nextCard?.src) { return; } lightboxState.primaryCard = nextCard; imageEl.src = nextCard.src; imageEl.alt = nextCard.altText; resetZoom(); clearSecondaryCard(); if (typeof lightboxState.onSelectCardId === "function") { lightboxState.onSelectCardId(nextCard.cardId); } applyComparePresentation(); } function swapCompareCards() { if (!lightboxState.compareMode || !lightboxState.primaryCard?.src || !lightboxState.secondaryCard?.src) { return; } const nextPrimaryCard = lightboxState.secondaryCard; const nextSecondaryCard = lightboxState.primaryCard; lightboxState.primaryCard = nextPrimaryCard; lightboxState.secondaryCard = nextSecondaryCard; imageEl.src = nextPrimaryCard.src; imageEl.alt = nextPrimaryCard.altText; overlayImageEl.src = nextSecondaryCard.src; overlayImageEl.alt = nextSecondaryCard.altText; overlayImageEl.style.display = "block"; overlayImageEl.style.opacity = String(lightboxState.overlayOpacity); if (typeof lightboxState.onSelectCardId === "function") { lightboxState.onSelectCardId(nextPrimaryCard.cardId); } applyComparePresentation(); } function shouldIgnoreGlobalKeydown(event) { const target = event.target; if (!(target instanceof HTMLElement)) { return false; } if (!overlayEl?.contains(target)) { return false; } return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target instanceof HTMLButtonElement; } function restoreLightboxFocus() { if (!overlayEl || !lightboxState.isOpen) { return; } requestAnimationFrame(() => { if (overlayEl && lightboxState.isOpen) { overlayEl.focus({ preventScroll: true }); } }); } backdropEl.addEventListener("click", close); helpButtonEl.addEventListener("click", () => { lightboxState.helpOpen = !lightboxState.helpOpen; syncHelpUi(); restoreLightboxFocus(); }); compareButtonEl.addEventListener("click", () => { toggleCompareMode(); restoreLightboxFocus(); }); zoomSliderEl.addEventListener("input", () => { setZoomScale(Number(zoomSliderEl.value) / 100); }); zoomSliderEl.addEventListener("change", restoreLightboxFocus); zoomSliderEl.addEventListener("pointerup", restoreLightboxFocus); opacitySliderEl.addEventListener("input", () => { setOverlayOpacity(Number(opacitySliderEl.value) / 100); }); opacitySliderEl.addEventListener("change", restoreLightboxFocus); opacitySliderEl.addEventListener("pointerup", restoreLightboxFocus); imageEl.addEventListener("click", (event) => { event.stopPropagation(); if (!isPointOnCard(event.clientX, event.clientY)) { if (lightboxState.compareMode) { return; } close(); return; } if (!zoomed) { zoomed = true; applyZoomTransform(); updateZoomOrigin(event.clientX, event.clientY); applyComparePresentation(); return; } resetZoom(); applyComparePresentation(); }); imageEl.addEventListener("mousemove", (event) => { updateZoomOrigin(event.clientX, event.clientY); }); imageEl.addEventListener("mouseleave", () => { if (zoomed) { lightboxState.zoomOriginX = 50; lightboxState.zoomOriginY = 50; applyTransformOrigins(); } }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { close(); return; } if (shouldIgnoreGlobalKeydown(event)) { return; } if (lightboxState.isOpen && event.code === "Space" && lightboxState.compareMode && hasSecondaryCard()) { event.preventDefault(); swapCompareCards(); return; } if (lightboxState.isOpen && isRotateKey(event)) { event.preventDefault(); toggleRotation(); return; } if (lightboxState.isOpen && isZoomInKey(event)) { event.preventDefault(); stepZoom(1); return; } if (lightboxState.isOpen && isZoomOutKey(event)) { event.preventDefault(); stepZoom(-1); return; } if (lightboxState.isOpen && zoomed && isPanUpKey(event)) { event.preventDefault(); stepPan(0, -LIGHTBOX_PAN_STEP); return; } if (lightboxState.isOpen && zoomed && isPanLeftKey(event)) { event.preventDefault(); stepPan(-LIGHTBOX_PAN_STEP, 0); return; } if (lightboxState.isOpen && zoomed && isPanDownKey(event)) { event.preventDefault(); stepPan(0, LIGHTBOX_PAN_STEP); return; } if (lightboxState.isOpen && zoomed && isPanRightKey(event)) { event.preventDefault(); stepPan(LIGHTBOX_PAN_STEP, 0); return; } if (!lightboxState.isOpen || !LIGHTBOX_COMPARE_SEQUENCE_STEP_KEYS.has(event.key)) { return; } event.preventDefault(); if (lightboxState.compareMode) { stepSecondaryCard(event.key === "ArrowRight" ? 1 : -1); return; } stepPrimaryCard(event.key === "ArrowRight" ? 1 : -1); }); document.body.appendChild(overlayEl); overlayEl.closeLightbox = close; overlayEl.setSecondaryCard = setSecondaryCard; overlayEl.applyComparePresentation = applyComparePresentation; } function open(srcOrOptions, altText, extraOptions) { const request = normalizeOpenRequest(srcOrOptions, altText, extraOptions); const normalizedPrimary = normalizeCardRequest(request); if (!normalizedPrimary.src) { return; } ensure(); if (!overlayEl || !imageEl || !overlayImageEl) { return; } const canCompare = Boolean( request.allowOverlayCompare && normalizedPrimary.cardId && Array.isArray(request.sequenceIds) && request.sequenceIds.length > 1 && typeof request.resolveCardById === "function" ); if (lightboxState.isOpen && lightboxState.compareMode && lightboxState.allowOverlayCompare && canCompare && normalizedPrimary.cardId) { if (normalizedPrimary.cardId === lightboxState.primaryCard?.cardId) { return; } overlayEl.setSecondaryCard?.(normalizedPrimary, false); return; } lightboxState.isOpen = true; lightboxState.compareMode = false; lightboxState.allowOverlayCompare = canCompare; lightboxState.primaryCard = normalizedPrimary; lightboxState.sequenceIds = canCompare ? [...request.sequenceIds] : []; lightboxState.resolveCardById = canCompare ? request.resolveCardById : null; lightboxState.onSelectCardId = canCompare && typeof request.onSelectCardId === "function" ? request.onSelectCardId : null; lightboxState.overlayOpacity = LIGHTBOX_COMPARE_DEFAULT_OVERLAY_OPACITY; lightboxState.zoomScale = LIGHTBOX_ZOOM_SCALE; lightboxState.helpOpen = false; imageEl.src = normalizedPrimary.src; imageEl.alt = normalizedPrimary.altText; clearSecondaryCard(); resetZoom(); previousFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null; overlayEl.style.display = "block"; overlayEl.setAttribute("aria-hidden", "false"); overlayEl.applyComparePresentation?.(); overlayEl.focus({ preventScroll: true }); } window.TarotUiLightbox = { ...(window.TarotUiLightbox || {}), open }; })();