update to frame and tarot times

This commit is contained in:
2026-04-02 22:06:19 -07:00
parent e5d041101f
commit cac243f2cf
9 changed files with 394 additions and 44 deletions

1
app.js
View File

@@ -448,6 +448,7 @@ window.TarotSpreadUi?.init?.({
window.TarotFrameUi?.init?.({ window.TarotFrameUi?.init?.({
ensureTarotSection, ensureTarotSection,
getCards: () => window.TarotSectionUi?.getCards?.() || [], getCards: () => window.TarotSectionUi?.getCards?.() || [],
openCardLightbox: (cardId, options = {}) => window.TarotSectionUi?.openCardLightboxById?.(cardId, options),
getHouseTopCardsVisible: () => window.TarotSectionUi?.getHouseTopCardsVisible?.() !== false, getHouseTopCardsVisible: () => window.TarotSectionUi?.getHouseTopCardsVisible?.() !== false,
getHouseTopInfoModes: () => window.TarotSectionUi?.getHouseTopInfoModes?.() || {}, getHouseTopInfoModes: () => window.TarotSectionUi?.getHouseTopInfoModes?.() || {},
getHouseBottomCardsVisible: () => window.TarotSectionUi?.getHouseBottomCardsVisible?.() !== false, getHouseBottomCardsVisible: () => window.TarotSectionUi?.getHouseBottomCardsVisible?.() !== false,

View File

@@ -1286,6 +1286,8 @@
transition: transform 120ms ease, filter 120ms ease; transition: transform 120ms ease, filter 120ms ease;
-webkit-user-select: none; -webkit-user-select: none;
user-select: none; user-select: none;
touch-action: none;
-webkit-touch-callout: none;
} }
.tarot-frame-card.is-empty { .tarot-frame-card.is-empty {
@@ -1306,6 +1308,17 @@
transform: none; transform: none;
} }
@media (pointer: coarse) {
.tarot-frame-card {
cursor: grab;
}
.tarot-frame-card:hover {
transform: none;
filter: none;
}
}
.tarot-frame-card-image, .tarot-frame-card-image,
.tarot-frame-card-fallback { .tarot-frame-card-fallback {
display: block; display: block;

View File

@@ -212,6 +212,10 @@
const windowDecans = windowDecanIds const windowDecans = windowDecanIds
.map((decanId) => decanById.get(decanId) || null) .map((decanId) => decanById.get(decanId) || null)
.filter(Boolean); .filter(Boolean);
const windowSignIds = tarotDb.courtSignWindows?.[cardName] || [];
const windowSigns = windowSignIds
.map((signId) => signById[signId] || null)
.filter(Boolean);
const explicitWindowRange = buildTokenDateRange( const explicitWindowRange = buildTokenDateRange(
tarotDb.courtDateRanges?.[cardName]?.start, tarotDb.courtDateRanges?.[cardName]?.start,
tarotDb.courtDateRanges?.[cardName]?.end tarotDb.courtDateRanges?.[cardName]?.end
@@ -240,6 +244,24 @@
) )
); );
if (meta?.decan?.tarotMinorArcana) {
dynamicRelations.push(
createRelation(
"tarotCard",
`${meta.signId}-${meta.index}-${rankKey}-${suitKey}-decan-card`,
`Decan card: ${meta.decan.tarotMinorArcana} (${meta.signName} decan ${meta.index})`,
{
cardName: meta.decan.tarotMinorArcana,
decanId: meta.decan.id || `${meta.signId}-${meta.index}`,
signId: meta.signId,
signName: meta.signName,
decanIndex: meta.index,
dateRange: meta.dateRange?.label || null
}
)
);
}
const ruler = planets?.[meta?.decan?.rulerPlanetId] || null; const ruler = planets?.[meta?.decan?.rulerPlanetId] || null;
if (ruler) { if (ruler) {
dynamicRelations.push( dynamicRelations.push(
@@ -290,9 +312,87 @@
} }
}); });
if (windowDecans.length) { windowSigns.forEach((sign) => {
const firstRange = windowDecans[0].dateRange; const signDateRange = buildTokenDateRange(sign?.start, sign?.end);
const lastRange = windowDecans[windowDecans.length - 1].dateRange; const signName = sign?.name || sign?.id || "";
const signSymbol = sign?.symbol || "";
const relatedDecans = Array.isArray(decansBySign?.[sign.id]) ? decansBySign[sign.id] : [];
dynamicRelations.push(
createRelation(
"signWindow",
`${sign.id}-${rankKey}-${suitKey}`,
`Sign window: ${signSymbol} ${signName} (0°30°)${signDateRange ? ` · ${signDateRange.label}` : ""}`.trim(),
{
signId: sign.id,
signName,
signSymbol,
startDegree: 0,
endDegree: 30,
dateStart: signDateRange?.startToken || null,
dateEnd: signDateRange?.endToken || null,
dateRange: signDateRange?.label || null
}
)
);
relatedDecans.forEach((decan) => {
if (!decan?.tarotMinorArcana) {
return;
}
dynamicRelations.push(
createRelation(
"tarotCard",
`${sign.id}-${decan.id || decan.tarotMinorArcana}-${rankKey}-${suitKey}-sign-card`,
`Sign card: ${decan.tarotMinorArcana} (${signName})`,
{
cardName: decan.tarotMinorArcana,
decanId: decan.id || null,
signId: sign.id,
signName,
signSymbol,
dateRange: signDateRange?.label || null
}
)
);
});
if (signDateRange?.start && signDateRange?.end) {
const monthNumbers = listMonthNumbersBetween(signDateRange.start, signDateRange.end);
monthNumbers.forEach((monthNo) => {
const monthId = monthIdByNumber[monthNo];
const monthName = monthNameByNumber[monthNo] || `Month ${monthNo}`;
const monthKey = `${monthId}:${sign.id}:sign-window`;
if (!monthId || monthKeys.has(monthKey)) {
return;
}
monthKeys.add(monthKey);
dynamicRelations.push(
createRelation(
"calendarMonth",
`${monthId}-${sign.id}-${rankKey}-${suitKey}-sign-window`,
`Calendar month: ${monthName} (${signName} sign window)`,
{
monthId,
name: monthName,
monthOrder: monthNo,
signId: sign.id,
signName,
dateRange: signDateRange?.label || null
}
)
);
});
}
});
if (windowDecans.length || windowSigns.length) {
const firstRange = windowDecans.length ? windowDecans[0].dateRange : null;
const lastRange = windowDecans.length ? windowDecans[windowDecans.length - 1].dateRange : null;
const firstSignRange = windowSigns.length ? buildTokenDateRange(windowSigns[0]?.start, windowSigns[0]?.end) : null;
const lastSignRange = windowSigns.length ? buildTokenDateRange(windowSigns[windowSigns.length - 1]?.start, windowSigns[windowSigns.length - 1]?.end) : null;
const fallbackWindowRange = firstRange && lastRange const fallbackWindowRange = firstRange && lastRange
? { ? {
start: firstRange.start, start: firstRange.start,
@@ -301,7 +401,15 @@
endToken: lastRange.endToken, endToken: lastRange.endToken,
label: `${formatMonthDayLabel(firstRange.start)}${formatMonthDayLabel(lastRange.end)}` label: `${formatMonthDayLabel(firstRange.start)}${formatMonthDayLabel(lastRange.end)}`
} }
: null; : (firstSignRange && lastSignRange
? {
start: firstSignRange.start,
end: lastSignRange.end,
startToken: firstSignRange.startToken,
endToken: lastSignRange.endToken,
label: `${formatMonthDayLabel(firstSignRange.start)}${formatMonthDayLabel(lastSignRange.end)}`
}
: null);
const windowRange = explicitWindowRange || fallbackWindowRange; const windowRange = explicitWindowRange || fallbackWindowRange;
const windowLabel = windowRange?.label || "--"; const windowLabel = windowRange?.label || "--";
@@ -314,7 +422,8 @@
dateStart: windowRange?.startToken || null, dateStart: windowRange?.startToken || null,
dateEnd: windowRange?.endToken || null, dateEnd: windowRange?.endToken || null,
dateRange: windowLabel, dateRange: windowLabel,
decanIds: windowDecanIds decanIds: windowDecanIds,
signIds: windowSignIds
} }
) )
); );

View File

@@ -10,6 +10,7 @@
const rankInfo = config?.rankInfo && typeof config.rankInfo === "object" ? config.rankInfo : {}; const rankInfo = config?.rankInfo && typeof config.rankInfo === "object" ? config.rankInfo : {};
const courtInfo = config?.courtInfo && typeof config.courtInfo === "object" ? config.courtInfo : {}; const courtInfo = config?.courtInfo && typeof config.courtInfo === "object" ? config.courtInfo : {};
const courtDecanWindows = config?.courtDecanWindows && typeof config.courtDecanWindows === "object" ? config.courtDecanWindows : {}; const courtDecanWindows = config?.courtDecanWindows && typeof config.courtDecanWindows === "object" ? config.courtDecanWindows : {};
const courtSignWindows = config?.courtSignWindows && typeof config.courtSignWindows === "object" ? config.courtSignWindows : {};
const majorAliases = config?.majorAliases && typeof config.majorAliases === "object" ? config.majorAliases : {}; const majorAliases = config?.majorAliases && typeof config.majorAliases === "object" ? config.majorAliases : {};
const minorNumeralAliases = config?.minorNumeralAliases && typeof config.minorNumeralAliases === "object" ? config.minorNumeralAliases : {}; const minorNumeralAliases = config?.minorNumeralAliases && typeof config.minorNumeralAliases === "object" ? config.minorNumeralAliases : {};
const monthNameByNumber = config?.monthNameByNumber && typeof config.monthNameByNumber === "object" ? config.monthNameByNumber : {}; const monthNameByNumber = config?.monthNameByNumber && typeof config.monthNameByNumber === "object" ? config.monthNameByNumber : {};
@@ -30,6 +31,7 @@
rankInfo: hasDb && db.rankInfo && typeof db.rankInfo === "object" ? db.rankInfo : rankInfo, rankInfo: hasDb && db.rankInfo && typeof db.rankInfo === "object" ? db.rankInfo : rankInfo,
courtInfo: hasDb && db.courtInfo && typeof db.courtInfo === "object" ? db.courtInfo : courtInfo, courtInfo: hasDb && db.courtInfo && typeof db.courtInfo === "object" ? db.courtInfo : courtInfo,
courtDecanWindows: hasDb && db.courtDecanWindows && typeof db.courtDecanWindows === "object" ? db.courtDecanWindows : courtDecanWindows, courtDecanWindows: hasDb && db.courtDecanWindows && typeof db.courtDecanWindows === "object" ? db.courtDecanWindows : courtDecanWindows,
courtSignWindows: hasDb && db.courtSignWindows && typeof db.courtSignWindows === "object" ? db.courtSignWindows : courtSignWindows,
courtDateRanges: hasDb && db.courtDateRanges && typeof db.courtDateRanges === "object" ? db.courtDateRanges : {} courtDateRanges: hasDb && db.courtDateRanges && typeof db.courtDateRanges === "object" ? db.courtDateRanges : {}
}; };
} }

View File

@@ -397,6 +397,13 @@
"Prince of Cups": ["libra-3", "scorpio-1", "scorpio-2"] "Prince of Cups": ["libra-3", "scorpio-1", "scorpio-2"]
}; };
const COURT_SIGN_WINDOWS = {
"Princess of Wands": ["cancer", "leo", "virgo"],
"Princess of Cups": ["libra", "scorpio", "sagittarius"],
"Princess of Swords": ["capricorn", "aquarius", "pisces"],
"Princess of Disks": ["aries", "taurus", "gemini"]
};
const MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const MAJOR_HEBREW_LETTER_ID_BY_CARD = { const MAJOR_HEBREW_LETTER_ID_BY_CARD = {
@@ -474,6 +481,7 @@
rankInfo: RANK_INFO, rankInfo: RANK_INFO,
courtInfo: COURT_INFO, courtInfo: COURT_INFO,
courtDecanWindows: COURT_DECAN_WINDOWS, courtDecanWindows: COURT_DECAN_WINDOWS,
courtSignWindows: COURT_SIGN_WINDOWS,
majorAliases: MAJOR_ALIASES, majorAliases: MAJOR_ALIASES,
minorNumeralAliases: MINOR_NUMERAL_ALIASES, minorNumeralAliases: MINOR_NUMERAL_ALIASES,
monthNameByNumber: MONTH_NAME_BY_NUMBER, monthNameByNumber: MONTH_NAME_BY_NUMBER,

View File

@@ -20,6 +20,152 @@
findSephirahForMinorCard findSephirahForMinorCard
} = dependencies || {}; } = dependencies || {};
function buildDecanRelationKey(signId, decanIndex) {
const normalizedSignId = String(signId || "").trim().toLowerCase();
const normalizedIndex = Number(decanIndex);
if (!normalizedSignId || !Number.isFinite(normalizedIndex)) {
return "";
}
return `${normalizedSignId}-${normalizedIndex}`;
}
function buildSignRelationKey(signId) {
return String(signId || "").trim().toLowerCase();
}
function buildDecanSummaryRelations(relations) {
const decanRelations = (relations || []).filter((relation) => relation?.type === "decan");
const signWindowRelations = (relations || []).filter((relation) => relation?.type === "signWindow");
if (!decanRelations.length && !signWindowRelations.length) {
return [];
}
function normalizeInlineDateRange(value) {
return String(value || "")
.replace(/[–—]/g, " - ")
.replace(/\s*-\s*/g, " - ")
.replace(/\s+/g, " ")
.trim();
}
const rulerByDecanKey = new Map();
const cardByDecanKey = new Map();
const cardsBySignKey = new Map();
(relations || []).forEach((relation) => {
if (relation?.type === "decanRuler") {
const key = buildDecanRelationKey(relation?.data?.signId, relation?.data?.decanIndex);
if (key && !rulerByDecanKey.has(key)) {
rulerByDecanKey.set(key, relation);
}
}
if (relation?.type === "tarotCard") {
const decanKey = buildDecanRelationKey(relation?.data?.signId, relation?.data?.decanIndex);
if (decanKey && !cardByDecanKey.has(decanKey)) {
cardByDecanKey.set(decanKey, relation);
return;
}
const signKey = buildSignRelationKey(relation?.data?.signId);
if (!signKey) {
return;
}
const entries = cardsBySignKey.get(signKey) || [];
entries.push(relation);
cardsBySignKey.set(signKey, entries);
}
});
const decanSummaries = decanRelations.map((relation) => {
const signId = relation?.data?.signId;
const signName = String(relation?.data?.signName || "").trim();
const signSymbol = String(relation?.data?.signSymbol || relation?.data?.symbol || "").trim();
const decanIndex = Number(relation?.data?.index);
const startDegree = Number(relation?.data?.startDegree);
const endDegree = Number(relation?.data?.endDegree);
const dateRange = String(relation?.data?.dateRange || "").trim();
const decanKey = buildDecanRelationKey(signId, decanIndex);
const rulerRelation = rulerByDecanKey.get(decanKey) || null;
const cardRelation = cardByDecanKey.get(decanKey) || null;
const rulerSymbol = String(rulerRelation?.data?.symbol || "").trim();
const rulerName = String(rulerRelation?.data?.name || "").trim();
const rulerLabel = `${rulerSymbol} ${rulerName}`.replace(/\s+/g, " ").trim();
const decanCardName = String(cardRelation?.data?.cardName || "").trim();
const decanCardLabel = decanCardName
? String(getDisplayCardName?.(decanCardName) || decanCardName).trim()
: "";
const signLabel = `${signSymbol} ${signName}`.replace(/\s+/g, " ").trim();
const degreeLabel = Number.isFinite(startDegree) && Number.isFinite(endDegree)
? `(${startDegree}°-${endDegree}°)`
: "";
const dateLabel = normalizeInlineDateRange(dateRange);
const summaryParts = [
rulerLabel,
decanCardLabel,
[signLabel, degreeLabel].filter(Boolean).join(" ").trim(),
dateLabel
].filter(Boolean);
return {
type: decanCardName ? "tarotCard" : "decan",
id: cardRelation?.id || `${decanKey}-summary`,
label: summaryParts.join(": "),
data: {
...(cardRelation?.data || relation?.data || {}),
signId,
signName,
signSymbol,
decanIndex,
dateRange,
cardName: decanCardName || cardRelation?.data?.cardName || ""
},
__key: `decanSummary|${decanKey}|${cardRelation?.data?.cardName || ""}`
};
});
const signSummaries = signWindowRelations.map((relation) => {
const signId = relation?.data?.signId;
const signKey = buildSignRelationKey(signId);
const signName = String(relation?.data?.signName || "").trim();
const signSymbol = String(relation?.data?.signSymbol || relation?.data?.symbol || "").trim();
const startDegree = Number(relation?.data?.startDegree);
const endDegree = Number(relation?.data?.endDegree);
const dateRange = String(relation?.data?.dateRange || "").trim();
const cardLabels = [...new Set((cardsBySignKey.get(signKey) || [])
.map((entry) => String(getDisplayCardName?.(entry?.data?.cardName) || entry?.data?.cardName || "").trim())
.filter(Boolean))];
const signLabel = `${signSymbol} ${signName}`.replace(/\s+/g, " ").trim();
const degreeLabel = Number.isFinite(startDegree) && Number.isFinite(endDegree)
? `(${startDegree}°-${endDegree}°)`
: "";
const dateLabel = normalizeInlineDateRange(dateRange);
const summaryParts = [
cardLabels.join(", "),
[signLabel, degreeLabel].filter(Boolean).join(" ").trim(),
dateLabel
].filter(Boolean);
return {
type: "signWindow",
id: relation?.id || `${signKey}-summary`,
label: summaryParts.join(": "),
data: {
...(relation?.data || {}),
signId,
signName,
signSymbol,
cardNames: cardLabels
},
__key: `signSummary|${signKey}`
};
});
return [...decanSummaries, ...signSummaries];
}
function renderStaticRelationGroup(targetEl, cardEl, relations) { function renderStaticRelationGroup(targetEl, cardEl, relations) {
clearChildren(targetEl); clearChildren(targetEl);
if (!targetEl || !cardEl) return; if (!targetEl || !cardEl) return;
@@ -48,12 +194,20 @@
return true; return true;
}); });
const decanSummaryRelations = buildDecanSummaryRelations(dedupedRelations);
const hasDecanSummaryRelations = decanSummaryRelations.length > 0;
const hasSignWindowRelations = decanSummaryRelations.some((relation) => relation?.type === "signWindow");
const planetRelations = dedupedRelations.filter((relation) => const planetRelations = dedupedRelations.filter((relation) =>
relation.type === "planetCorrespondence" || relation.type === "decanRuler" || relation.type === "planet" relation.type === "planetCorrespondence"
|| relation.type === "planet"
|| (!hasDecanSummaryRelations && relation.type === "decanRuler")
); );
const zodiacRelations = dedupedRelations.filter((relation) => const zodiacRelations = dedupedRelations.filter((relation) =>
relation.type === "zodiacCorrespondence" || relation.type === "zodiac" || relation.type === "decan" relation.type === "zodiacCorrespondence"
|| relation.type === "zodiac"
|| (!hasDecanSummaryRelations && relation.type === "decan")
); );
const courtDateRelations = dedupedRelations.filter((relation) => relation.type === "courtDateWindow"); const courtDateRelations = dedupedRelations.filter((relation) => relation.type === "courtDateWindow");
@@ -63,9 +217,9 @@
const elementRelations = buildElementRelationsForCard(card, baseElementRelations); const elementRelations = buildElementRelationsForCard(card, baseElementRelations);
const tetragrammatonRelations = buildTetragrammatonRelationsForCard(card); const tetragrammatonRelations = buildTetragrammatonRelationsForCard(card);
const smallCardRulershipRelation = buildSmallCardRulershipRelation(card); const smallCardRulershipRelation = buildSmallCardRulershipRelation(card);
const zodiacRelationsWithRulership = smallCardRulershipRelation const zodiacRelationsWithRulership = hasDecanSummaryRelations
? [...zodiacRelations, smallCardRulershipRelation] ? [...decanSummaryRelations, ...(smallCardRulershipRelation ? [smallCardRulershipRelation] : [])]
: zodiacRelations; : [...zodiacRelations, ...(smallCardRulershipRelation ? [smallCardRulershipRelation] : [])];
const smallCardCourtLinkRelations = buildSmallCardCourtLinkRelations(card, dedupedRelations); const smallCardCourtLinkRelations = buildSmallCardCourtLinkRelations(card, dedupedRelations);
const mergedCourtDateRelations = [...courtDateRelations, ...smallCardCourtLinkRelations]; const mergedCourtDateRelations = [...courtDateRelations, ...smallCardCourtLinkRelations];
const cubeRelations = buildCubeRelationsForCard(card); const cubeRelations = buildCubeRelationsForCard(card);
@@ -166,6 +320,8 @@
elementRelations, elementRelations,
tetragrammatonRelations, tetragrammatonRelations,
zodiacRelationsWithRulership, zodiacRelationsWithRulership,
hasDecanSummaryRelations,
hasSignWindowRelations,
mergedCourtDateRelations, mergedCourtDateRelations,
hebrewRelations, hebrewRelations,
cubeRelations, cubeRelations,
@@ -183,7 +339,10 @@
const groups = [ const groups = [
{ title: "Letter", relations: detailRelations.hebrewRelations }, { title: "Letter", relations: detailRelations.hebrewRelations },
{ title: "Planet / Ruler", relations: detailRelations.planetRelations }, { title: "Planet / Ruler", relations: detailRelations.planetRelations },
{ title: "Sign / Decan", relations: detailRelations.zodiacRelationsWithRulership }, {
title: detailRelations.hasSignWindowRelations ? "Signs" : (detailRelations.hasDecanSummaryRelations ? "Decan" : "Sign / Decan"),
relations: detailRelations.zodiacRelationsWithRulership
},
{ title: "Element", relations: detailRelations.elementRelations }, { title: "Element", relations: detailRelations.elementRelations },
{ title: "Tetragrammaton", relations: detailRelations.tetragrammatonRelations }, { title: "Tetragrammaton", relations: detailRelations.tetragrammatonRelations },
{ title: "Dates", relations: detailRelations.mergedCourtDateRelations }, { title: "Dates", relations: detailRelations.mergedCourtDateRelations },

View File

@@ -192,6 +192,7 @@
let config = { let config = {
ensureTarotSection: null, ensureTarotSection: null,
getCards: () => [], getCards: () => [],
openCardLightbox: () => {},
getHouseTopCardsVisible: () => true, getHouseTopCardsVisible: () => true,
getHouseTopInfoModes: () => ({}), getHouseTopInfoModes: () => ({}),
getHouseBottomCardsVisible: () => true, getHouseBottomCardsVisible: () => true,
@@ -1449,6 +1450,16 @@
return; return;
} }
if (state.drag.sourceButton instanceof HTMLElement && typeof state.drag.sourceButton.releasePointerCapture === "function") {
try {
if (state.drag.sourceButton.hasPointerCapture?.(state.drag.pointerId)) {
state.drag.sourceButton.releasePointerCapture(state.drag.pointerId);
}
} catch (_error) {
// Ignore pointer-capture release failures during cleanup.
}
}
setHoverSlot(""); setHoverSlot("");
getSlotElement(state.drag.sourceSlotId)?.classList.remove("is-drag-source"); getSlotElement(state.drag.sourceSlotId)?.classList.remove("is-drag-source");
if (state.drag.ghostEl instanceof HTMLElement) { if (state.drag.ghostEl instanceof HTMLElement) {
@@ -1482,6 +1493,13 @@
return; return;
} }
if (typeof config.openCardLightbox === "function") {
config.openCardLightbox(getCardId(card), {
onSelectCardId: () => {}
});
return;
}
const deckOptions = resolveDeckOptions(card); const deckOptions = resolveDeckOptions(card);
const src = String( const src = String(
tarotCardImages.resolveTarotCardImage?.(card.name, deckOptions) tarotCardImages.resolveTarotCardImage?.(card.name, deckOptions)
@@ -1522,9 +1540,22 @@
startY: event.clientY, startY: event.clientY,
started: false, started: false,
hoverSlotId: "", hoverSlotId: "",
ghostEl: null ghostEl: null,
sourceButton: cardButton
}; };
if (typeof cardButton.setPointerCapture === "function") {
try {
cardButton.setPointerCapture(event.pointerId);
} catch (_error) {
// Ignore pointer-capture failures and continue with document listeners.
}
}
if (String(event.pointerType || "").toLowerCase() === "touch") {
event.preventDefault();
}
detachPointerListeners(); detachPointerListeners();
document.addEventListener("pointermove", handlePointerMove); document.addEventListener("pointermove", handlePointerMove);
document.addEventListener("pointerup", handlePointerUp); document.addEventListener("pointerup", handlePointerUp);

View File

@@ -795,6 +795,48 @@
return request; return request;
} }
function openCardLightboxById(cardIdToOpen, options = {}) {
const normalizedCardId = String(cardIdToOpen || "").trim();
if (!normalizedCardId) {
return;
}
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId);
if (!primaryCardRequest?.src) {
return;
}
const activeDeckId = String(getActiveDeck?.() || primaryCardRequest.deckId || "").trim();
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeDeckId);
const onSelectCardId = typeof options?.onSelectCardId === "function"
? options.onSelectCardId
: (nextCardId) => {
const latestElements = getElements();
selectCardById(nextCardId, latestElements);
scrollCardIntoView(nextCardId, latestElements);
};
window.TarotUiLightbox?.open?.({
src: primaryCardRequest.src,
altText: primaryCardRequest.altText,
label: primaryCardRequest.label,
cardId: primaryCardRequest.cardId,
deckId: primaryCardRequest.deckId || activeDeckId,
deckLabel: primaryCardRequest.deckLabel || "",
compareDetails: primaryCardRequest.compareDetails || [],
allowOverlayCompare: true,
allowDeckCompare: true,
activeDeckId,
activeDeckLabel: primaryCardRequest.deckLabel || "",
availableCompareDecks,
maxCompareDecks: 2,
sequenceIds: state.cards.map((card) => card.id),
resolveCardById: buildLightboxCardRequestById,
resolveDeckCardById: buildDeckLightboxCardRequest,
onSelectCardId
});
}
function renderList(elements) { function renderList(elements) {
if (!elements?.tarotCardListEl) { if (!elements?.tarotCardListEl) {
return; return;
@@ -858,31 +900,15 @@
selectCardById, selectCardById,
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; if (cardId) {
const activeDeckId = String(getActiveDeck?.() || primaryCardRequest?.deckId || "").trim(); openCardLightboxById(cardId);
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeDeckId); return;
}
window.TarotUiLightbox?.open?.({ window.TarotUiLightbox?.open?.({
src: primaryCardRequest?.src || src, src,
altText: primaryCardRequest?.altText || altText || "Tarot card enlarged image", altText: altText || "Tarot card enlarged image",
label: primaryCardRequest?.label || altText || "Tarot card enlarged image", label: altText || "Tarot card enlarged image"
cardId: primaryCardRequest?.cardId || cardId,
deckId: primaryCardRequest?.deckId || activeDeckId,
deckLabel: primaryCardRequest?.deckLabel || "",
compareDetails: primaryCardRequest?.compareDetails || [],
allowOverlayCompare: true,
allowDeckCompare: Boolean(cardId),
activeDeckId,
activeDeckLabel: primaryCardRequest?.deckLabel || "",
availableCompareDecks,
maxCompareDecks: 2,
sequenceIds: state.cards.map((card) => card.id),
resolveCardById: buildLightboxCardRequestById,
resolveDeckCardById: buildDeckLightboxCardRequest,
onSelectCardId: (nextCardId) => {
const latestElements = getElements();
selectCardById(nextCardId, latestElements);
scrollCardIntoView(nextCardId, latestElements);
}
}); });
}, },
shouldOpenCardLightboxOnSelect: (latestElements) => Boolean( shouldOpenCardLightboxOnSelect: (latestElements) => Boolean(
@@ -1116,6 +1142,7 @@
ensureTarotSection, ensureTarotSection,
selectCardByTrump, selectCardByTrump,
selectCardByName, selectCardByName,
openCardLightboxById,
getCards: () => state.cards, getCards: () => state.cards,
getHouseTopCardsVisible: () => state.houseTopCardsVisible, getHouseTopCardsVisible: () => state.houseTopCardsVisible,
getHouseTopInfoModes: () => ({ ...state.houseTopInfoModes }), getHouseTopInfoModes: () => ({ ...state.houseTopInfoModes }),

View File

@@ -16,7 +16,7 @@
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css"> <link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css">
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-700.css"> <link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-700.css">
<link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css"> <link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css">
<link rel="stylesheet" href="app/styles.css?v=20260402-frame-center-07"> <link rel="stylesheet" href="app/styles.css?v=20260402-frame-center-09">
</head> </head>
<body> <body>
<div class="topbar"> <div class="topbar">
@@ -1124,9 +1124,9 @@
<script src="app/ui-now-helpers.js?v=20260314-now-planets-grid-01"></script> <script src="app/ui-now-helpers.js?v=20260314-now-planets-grid-01"></script>
<script src="app/ui-now.js?v=20260314-now-planets-grid-01"></script> <script src="app/ui-now.js?v=20260314-now-planets-grid-01"></script>
<script src="app/ui-natal.js"></script> <script src="app/ui-natal.js"></script>
<script src="app/tarot-database-builders.js?v=20260401-tarot-dates-01"></script> <script src="app/tarot-database-builders.js?v=20260402-princess-links-01"></script>
<script src="app/tarot-database-assembly.js?v=20260401-tarot-dates-01"></script> <script src="app/tarot-database-assembly.js?v=20260402-princess-links-01"></script>
<script src="app/tarot-database.js?v=20260401-tarot-dates-01"></script> <script src="app/tarot-database.js?v=20260402-princess-links-01"></script>
<script src="app/ui-calendar-dates.js"></script> <script src="app/ui-calendar-dates.js"></script>
<script src="app/ui-calendar-detail-panels.js"></script> <script src="app/ui-calendar-detail-panels.js"></script>
<script src="app/ui-calendar-detail.js"></script> <script src="app/ui-calendar-detail.js"></script>
@@ -1136,9 +1136,9 @@
<script src="app/ui-holidays-render.js"></script> <script src="app/ui-holidays-render.js"></script>
<script src="app/ui-holidays.js"></script> <script src="app/ui-holidays.js"></script>
<script src="app/ui-tarot-card-derivations.js?v=20260307b"></script> <script src="app/ui-tarot-card-derivations.js?v=20260307b"></script>
<script src="app/ui-tarot-detail.js?v=20260307b"></script> <script src="app/ui-tarot-detail.js?v=20260402-princess-links-01"></script>
<script src="app/ui-tarot-relation-display.js?v=20260307b"></script> <script src="app/ui-tarot-relation-display.js?v=20260307b"></script>
<script src="app/ui-tarot.js?v=20260401-house-top-date-02"></script> <script src="app/ui-tarot.js?v=20260402-frame-lightbox-01"></script>
<script src="app/ui-planets-references.js"></script> <script src="app/ui-planets-references.js"></script>
<script src="app/ui-planets.js"></script> <script src="app/ui-planets.js"></script>
<script src="app/ui-cycles.js"></script> <script src="app/ui-cycles.js"></script>
@@ -1178,7 +1178,7 @@
<script src="app/ui-numbers-detail.js"></script> <script src="app/ui-numbers-detail.js"></script>
<script src="app/ui-numbers.js"></script> <script src="app/ui-numbers.js"></script>
<script src="app/ui-tarot-spread.js"></script> <script src="app/ui-tarot-spread.js"></script>
<script src="app/ui-tarot-frame.js?v=20260402-frame-mobile-center-06"></script> <script src="app/ui-tarot-frame.js?v=20260402-frame-lightbox-01"></script>
<script src="app/ui-settings.js?v=20260309-gate"></script> <script src="app/ui-settings.js?v=20260309-gate"></script>
<script src="app/ui-chrome.js?v=20260328-topbar-settings-01"></script> <script src="app/ui-chrome.js?v=20260328-topbar-settings-01"></script>
<script src="app/ui-navigation.js?v=20260401-tarot-frame-01"></script> <script src="app/ui-navigation.js?v=20260401-tarot-frame-01"></script>
@@ -1187,7 +1187,7 @@
<script src="app/ui-home-calendar.js"></script> <script src="app/ui-home-calendar.js"></script>
<script src="app/ui-section-state.js?v=20260401-tarot-frame-01"></script> <script src="app/ui-section-state.js?v=20260401-tarot-frame-01"></script>
<script src="app/app-runtime.js?v=20260309-gate"></script> <script src="app/app-runtime.js?v=20260309-gate"></script>
<script src="app.js?v=20260402-global-zoom-01"></script> <script src="app.js?v=20260402-frame-lightbox-01"></script>
<script src="app/navigation-detail-test-harness.js?v=20260401-universal-detail-02"></script> <script src="app/navigation-detail-test-harness.js?v=20260401-universal-detail-02"></script>
</body> </body>
</html> </html>