Files
TaroTime/app/card-images.js

867 lines
25 KiB
JavaScript
Raw Normal View History

2026-03-07 01:09:00 -08:00
(function () {
const DEFAULT_DECK_ID = "ceremonial-magick";
const trumpNumberByCanonicalName = {
fool: 0,
magus: 1,
magician: 1,
"high priestess": 2,
empress: 3,
emperor: 4,
hierophant: 5,
lovers: 6,
chariot: 7,
lust: 8,
strength: 8,
hermit: 9,
fortune: 10,
"wheel of fortune": 10,
justice: 11,
"hanged man": 12,
death: 13,
art: 14,
temperance: 14,
devil: 15,
tower: 16,
star: 17,
moon: 18,
sun: 19,
aeon: 20,
judgement: 20,
judgment: 20,
universe: 21,
world: 21
};
const pipValueByToken = {
ace: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9,
ten: 10,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"10": 10
};
const rankWordByPipValue = {
1: "Ace",
2: "Two",
3: "Three",
4: "Four",
5: "Five",
6: "Six",
7: "Seven",
8: "Eight",
9: "Nine",
10: "Ten"
};
const trumpRomanToNumber = {
I: 1,
II: 2,
III: 3,
IV: 4,
V: 5,
VI: 6,
VII: 7,
VIII: 8,
IX: 9,
X: 10,
XI: 11,
XII: 12,
XIII: 13,
XIV: 14,
XV: 15,
XVI: 16,
XVII: 17,
XVIII: 18,
XIX: 19,
XX: 20,
XXI: 21
};
const suitSearchAliasesById = {
wands: ["wands"],
cups: ["cups"],
swords: ["swords"],
disks: ["disks", "pentacles", "coins"]
};
const defaultPipRankOrder = ["Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"];
const defaultThumbnailConfig = {
root: "thumbs",
width: 240,
height: 360,
fit: "inside",
quality: 82
};
2026-03-07 01:09:00 -08:00
const DECK_REGISTRY_PATH = "asset/tarot deck/decks.json";
2026-03-07 05:17:50 -08:00
let deckManifestSources = buildDeckManifestSources();
2026-03-07 01:09:00 -08:00
const manifestCache = new Map();
2026-03-07 05:17:50 -08:00
const cardBackCache = new Map();
const cardBackThumbnailCache = new Map();
2026-03-07 01:09:00 -08:00
let activeDeckId = DEFAULT_DECK_ID;
function canonicalMajorName(cardName) {
return String(cardName || "")
.trim()
.toLowerCase()
.replace(/^the\s+/, "")
.replace(/\s+/g, " ");
}
function canonicalMinorName(cardName) {
const parsedMinor = parseMinorCard(cardName);
if (!parsedMinor) {
return "";
}
return `${String(parsedMinor.rankKey || "").trim().toLowerCase()} of ${parsedMinor.suitId}`;
}
function toTitleCase(value) {
const normalized = String(value || "").trim().toLowerCase();
if (!normalized) {
return "";
}
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
}
function normalizeDeckId(deckId) {
2026-03-07 05:17:50 -08:00
const sources = getDeckManifestSources();
2026-03-07 01:09:00 -08:00
const normalized = String(deckId || "").trim().toLowerCase();
2026-03-07 05:17:50 -08:00
if (sources[normalized]) {
2026-03-07 01:09:00 -08:00
return normalized;
}
2026-03-07 05:17:50 -08:00
if (sources[DEFAULT_DECK_ID]) {
2026-03-07 01:09:00 -08:00
return DEFAULT_DECK_ID;
}
2026-03-07 05:17:50 -08:00
const fallbackId = Object.keys(sources)[0];
2026-03-07 01:09:00 -08:00
return fallbackId || DEFAULT_DECK_ID;
}
function normalizeTrumpNumber(value) {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 21) {
return null;
}
return parsed;
}
function parseTrumpNumberKey(value) {
const normalized = String(value || "").trim().toUpperCase();
if (!normalized) {
return null;
}
if (/^\d+$/.test(normalized)) {
return normalizeTrumpNumber(Number(normalized));
}
if (Object.prototype.hasOwnProperty.call(trumpRomanToNumber, normalized)) {
return normalizeTrumpNumber(trumpRomanToNumber[normalized]);
}
return null;
}
function normalizeSuitId(suitInput) {
const suit = String(suitInput || "").trim().toLowerCase();
if (suit === "pentacles") {
return "disks";
}
return suit;
}
function resolveDeckOptions(optionsOrDeckId) {
let resolvedDeckId = activeDeckId;
let trumpNumber = null;
if (typeof optionsOrDeckId === "string") {
resolvedDeckId = normalizeDeckId(optionsOrDeckId);
} else if (optionsOrDeckId && typeof optionsOrDeckId === "object") {
if (optionsOrDeckId.deckId) {
resolvedDeckId = normalizeDeckId(optionsOrDeckId.deckId);
}
trumpNumber = normalizeTrumpNumber(optionsOrDeckId.trumpNumber);
}
return { resolvedDeckId, trumpNumber };
}
function parseMinorCard(cardName) {
const match = String(cardName || "")
.trim()
.match(/^(ace|two|three|four|five|six|seven|eight|nine|ten|knight|queen|prince|princess|king|page|[2-9]|10)\s+of\s+(cups|wands|swords|pentacles|disks)$/i);
if (!match) {
return null;
}
const rankToken = String(match[1] || "").toLowerCase();
const suitId = normalizeSuitId(match[2]);
const pipValue = pipValueByToken[rankToken] ?? null;
if (Number.isFinite(pipValue)) {
const rankWord = rankWordByPipValue[pipValue] || "";
return {
suitId,
pipValue,
court: "",
rankWord,
rankKey: rankWord.toLowerCase()
};
}
const courtWord = toTitleCase(rankToken);
if (!courtWord) {
return null;
}
return {
suitId,
pipValue: null,
court: rankToken,
rankWord: courtWord,
rankKey: rankToken
};
}
function applyTemplate(template, variables) {
return String(template || "")
.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, token) => {
const value = variables[token];
return value == null ? "" : String(value);
});
}
2026-03-07 05:17:50 -08:00
function isRemoteAssetPath(pathValue) {
return /^(https?:)?\/\//i.test(String(pathValue || ""));
}
function toDeckAssetPath(manifest, relativeOrAbsolutePath) {
const normalizedPath = String(relativeOrAbsolutePath || "").trim();
if (!normalizedPath) {
return "";
}
if (isRemoteAssetPath(normalizedPath) || normalizedPath.startsWith("/")) {
return normalizedPath;
}
return `${manifest.basePath}/${normalizedPath.replace(/^\.\//, "")}`;
}
function resolveDeckCardBackPath(manifest) {
if (!manifest) {
return null;
}
const explicitCardBack = String(manifest.cardBack || "").trim();
if (explicitCardBack) {
return toDeckAssetPath(manifest, explicitCardBack) || null;
}
const detectedCardBack = String(manifest.cardBackPath || "").trim();
if (detectedCardBack) {
return toDeckAssetPath(manifest, detectedCardBack) || null;
}
return null;
}
function normalizeThumbnailConfig(rawConfig, fallbackRoot = "") {
if (rawConfig === false) {
return false;
}
if (!rawConfig || typeof rawConfig !== "object") {
const root = String(fallbackRoot || "").trim();
if (!root) {
return null;
}
return {
...defaultThumbnailConfig,
root
};
}
const root = String(rawConfig.root || fallbackRoot || defaultThumbnailConfig.root).trim();
if (!root) {
return null;
}
return {
root,
width: Number.isInteger(Number(rawConfig.width)) && Number(rawConfig.width) > 0
? Number(rawConfig.width)
: defaultThumbnailConfig.width,
height: Number.isInteger(Number(rawConfig.height)) && Number(rawConfig.height) > 0
? Number(rawConfig.height)
: defaultThumbnailConfig.height,
fit: String(rawConfig.fit || defaultThumbnailConfig.fit).trim() || defaultThumbnailConfig.fit,
quality: Number.isInteger(Number(rawConfig.quality)) && Number(rawConfig.quality) >= 1 && Number(rawConfig.quality) <= 100
? Number(rawConfig.quality)
: defaultThumbnailConfig.quality
};
}
function resolveDeckThumbnailPath(manifest, relativePath) {
if (!manifest || !manifest.thumbnails || manifest.thumbnails === false) {
return null;
}
const normalizedPath = String(relativePath || "").trim().replace(/^\.\//, "");
if (!normalizedPath || isRemoteAssetPath(normalizedPath) || normalizedPath.startsWith("/")) {
return null;
}
return toDeckAssetPath(manifest, `${manifest.thumbnails.root}/${normalizedPath}`) || null;
}
2026-03-07 01:09:00 -08:00
function readManifestJsonSync(path) {
try {
const request = new XMLHttpRequest();
request.open("GET", encodeURI(path), false);
request.send(null);
const okStatus = (request.status >= 200 && request.status < 300) || request.status === 0;
if (!okStatus || !request.responseText) {
return null;
}
return JSON.parse(request.responseText);
} catch {
return null;
}
}
function toDeckSourceMap(sourceList) {
const sourceMap = {};
if (!Array.isArray(sourceList)) {
return sourceMap;
}
sourceList.forEach((entry) => {
const id = String(entry?.id || "").trim().toLowerCase();
const basePath = String(entry?.basePath || "").trim().replace(/\/$/, "");
const manifestPath = String(entry?.manifestPath || "").trim();
if (!id || !basePath || !manifestPath) {
return;
}
sourceMap[id] = {
id,
label: String(entry?.label || id),
basePath,
2026-03-07 05:17:50 -08:00
manifestPath,
cardBackPath: String(entry?.cardBackPath || "").trim(),
thumbnailRoot: String(entry?.thumbnailRoot || "").trim()
2026-03-07 01:09:00 -08:00
};
});
return sourceMap;
}
function buildDeckManifestSources() {
const registry = readManifestJsonSync(DECK_REGISTRY_PATH);
const registryDecks = Array.isArray(registry)
? registry
: (Array.isArray(registry?.decks) ? registry.decks : null);
return toDeckSourceMap(registryDecks);
}
2026-03-07 05:17:50 -08:00
function getDeckManifestSources(forceRefresh = false) {
if (forceRefresh || !deckManifestSources || Object.keys(deckManifestSources).length === 0) {
deckManifestSources = buildDeckManifestSources();
}
return deckManifestSources || {};
}
2026-03-07 01:09:00 -08:00
function normalizeDeckManifest(source, rawManifest) {
if (!rawManifest || typeof rawManifest !== "object") {
return null;
}
const rawNameOverrides = rawManifest.nameOverrides;
const nameOverrides = {};
if (rawNameOverrides && typeof rawNameOverrides === "object") {
Object.entries(rawNameOverrides).forEach(([rawKey, rawValue]) => {
const key = canonicalMajorName(rawKey);
const value = String(rawValue || "").trim();
if (key && value) {
nameOverrides[key] = value;
}
});
}
const rawMajorNameOverridesByTrump = rawManifest.majorNameOverridesByTrump;
const majorNameOverridesByTrump = {};
if (rawMajorNameOverridesByTrump && typeof rawMajorNameOverridesByTrump === "object") {
Object.entries(rawMajorNameOverridesByTrump).forEach(([rawKey, rawValue]) => {
const trumpNumber = parseTrumpNumberKey(rawKey);
const value = String(rawValue || "").trim();
if (Number.isInteger(trumpNumber) && value) {
majorNameOverridesByTrump[trumpNumber] = value;
}
});
}
const rawMinorNameOverrides = rawManifest.minorNameOverrides;
const minorNameOverrides = {};
if (rawMinorNameOverrides && typeof rawMinorNameOverrides === "object") {
Object.entries(rawMinorNameOverrides).forEach(([rawKey, rawValue]) => {
const key = canonicalMinorName(rawKey);
const value = String(rawValue || "").trim();
if (key && value) {
minorNameOverrides[key] = value;
}
});
}
return {
id: source.id,
label: String(rawManifest.label || source.label || source.id),
basePath: String(source.basePath || "").replace(/\/$/, ""),
2026-03-07 05:17:50 -08:00
cardBack: String(rawManifest.cardBack || "").trim(),
cardBackPath: String(source.cardBackPath || "").trim(),
thumbnails: normalizeThumbnailConfig(rawManifest.thumbnails, source.thumbnailRoot),
2026-03-07 01:09:00 -08:00
majors: rawManifest.majors || {},
minors: rawManifest.minors || {},
nameOverrides,
minorNameOverrides,
majorNameOverridesByTrump
};
}
function getDeckManifest(deckId) {
const normalizedDeckId = normalizeDeckId(deckId);
if (manifestCache.has(normalizedDeckId)) {
return manifestCache.get(normalizedDeckId);
}
2026-03-07 05:17:50 -08:00
let sources = getDeckManifestSources();
let source = sources[normalizedDeckId];
if (!source) {
sources = getDeckManifestSources(true);
source = sources[normalizedDeckId];
}
2026-03-07 01:09:00 -08:00
if (!source) {
return null;
}
const rawManifest = readManifestJsonSync(source.manifestPath);
const normalizedManifest = normalizeDeckManifest(source, rawManifest);
2026-03-07 05:17:50 -08:00
if (normalizedManifest) {
manifestCache.set(normalizedDeckId, normalizedManifest);
}
2026-03-07 01:09:00 -08:00
return normalizedManifest;
}
function getRankOrder(minorRule, fallbackRankOrder = []) {
const explicitRankOrder = Array.isArray(minorRule?.rankOrder) ? minorRule.rankOrder : [];
const rankOrderSource = explicitRankOrder.length ? explicitRankOrder : fallbackRankOrder;
return rankOrderSource.map((entry) => String(entry || "").trim()).filter(Boolean);
}
function getRankIndex(minorRule, parsedMinor, fallbackRankOrder = []) {
2026-03-07 01:09:00 -08:00
if (!minorRule || !parsedMinor) {
return null;
}
const lowerRankWord = String(parsedMinor.rankWord || "").toLowerCase();
const lowerRankKey = String(parsedMinor.rankKey || "").toLowerCase();
const indexByKey = minorRule.rankIndexByKey;
if (indexByKey && typeof indexByKey === "object") {
const mapped = Number(indexByKey[lowerRankKey]);
if (Number.isInteger(mapped) && mapped >= 0) {
return mapped;
}
}
const rankOrder = getRankOrder(minorRule, fallbackRankOrder);
2026-03-07 01:09:00 -08:00
for (let i = 0; i < rankOrder.length; i += 1) {
const candidate = String(rankOrder[i] || "").toLowerCase();
if (candidate && (candidate === lowerRankWord || candidate === lowerRankKey)) {
return i;
}
}
return null;
}
function resolveMinorNumberTemplateGroup(groupRule, parsedMinor, fallbackRankOrder = []) {
if (!groupRule || typeof groupRule !== "object") {
return null;
}
const rankIndex = getRankIndex(groupRule, parsedMinor, fallbackRankOrder);
if (!Number.isInteger(rankIndex) || rankIndex < 0) {
return null;
}
const suitBaseRaw = Number(groupRule?.suitBase?.[parsedMinor.suitId]);
if (!Number.isFinite(suitBaseRaw)) {
return null;
}
const numberPad = Number.isInteger(groupRule.numberPad) ? groupRule.numberPad : 2;
const cardNumber = String(suitBaseRaw + rankIndex).padStart(numberPad, "0");
const template = String(groupRule.template || "{number}.png");
return applyTemplate(template, {
number: cardNumber,
suitId: parsedMinor.suitId,
rank: parsedMinor.rankWord,
rankKey: parsedMinor.rankKey,
index: rankIndex
});
}
2026-03-07 01:09:00 -08:00
function resolveMajorFile(manifest, canonicalName) {
const majorRule = manifest?.majors;
if (!majorRule || typeof majorRule !== "object") {
return null;
}
if (majorRule.mode === "canonical-map") {
const cards = majorRule.cards || {};
const fileName = cards[canonicalName];
return typeof fileName === "string" && fileName ? fileName : null;
}
const trumpNo = trumpNumberByCanonicalName[canonicalName];
if (!Number.isInteger(trumpNo) || trumpNo < 0 || trumpNo > 21) {
return null;
}
if (majorRule.mode === "trump-map") {
const cards = majorRule.cards || {};
const fileName = cards[String(trumpNo)] ?? cards[trumpNo];
return typeof fileName === "string" && fileName ? fileName : null;
}
if (majorRule.mode === "trump-template") {
const numberPad = Number.isInteger(majorRule.numberPad) ? majorRule.numberPad : 2;
const template = String(majorRule.template || "{number}.png");
const number = String(trumpNo).padStart(numberPad, "0");
return applyTemplate(template, {
trump: trumpNo,
number
});
}
return null;
}
function resolveMinorFile(manifest, parsedMinor) {
const minorRule = manifest?.minors;
if (!minorRule || typeof minorRule !== "object") {
return null;
}
if (minorRule.mode === "split-number-template") {
if (Number.isFinite(parsedMinor.pipValue)) {
return resolveMinorNumberTemplateGroup(minorRule.smalls, parsedMinor, defaultPipRankOrder);
}
return resolveMinorNumberTemplateGroup(minorRule.courts, parsedMinor);
}
2026-03-07 01:09:00 -08:00
const rankIndex = getRankIndex(minorRule, parsedMinor);
if (!Number.isInteger(rankIndex) || rankIndex < 0) {
return null;
}
if (minorRule.mode === "suit-base-and-rank-order") {
const suitBaseRaw = Number(minorRule?.suitBase?.[parsedMinor.suitId]);
if (!Number.isFinite(suitBaseRaw)) {
return null;
}
const numberPad = Number.isInteger(minorRule.numberPad) ? minorRule.numberPad : 2;
const cardNumber = String(suitBaseRaw + rankIndex).padStart(numberPad, "0");
const suitWord = String(minorRule?.suitLabel?.[parsedMinor.suitId] || toTitleCase(parsedMinor.suitId));
const template = String(minorRule.template || "{number}_{rank} {suit}.webp");
return applyTemplate(template, {
number: cardNumber,
rank: parsedMinor.rankWord,
rankKey: parsedMinor.rankKey,
suit: suitWord,
suitId: parsedMinor.suitId,
index: rankIndex + 1
});
}
if (minorRule.mode === "suit-prefix-and-rank-order") {
const suitPrefix = minorRule?.suitPrefix?.[parsedMinor.suitId];
if (!suitPrefix) {
return null;
}
const indexStart = Number.isInteger(minorRule.indexStart) ? minorRule.indexStart : 1;
const indexPad = Number.isInteger(minorRule.indexPad) ? minorRule.indexPad : 2;
const suitIndex = String(indexStart + rankIndex).padStart(indexPad, "0");
const template = String(minorRule.template || "{suit}{index}.png");
return applyTemplate(template, {
suit: suitPrefix,
suitId: parsedMinor.suitId,
index: suitIndex,
rank: parsedMinor.rankWord,
rankKey: parsedMinor.rankKey
});
}
if (minorRule.mode === "suit-base-number-template") {
const suitBaseRaw = Number(minorRule?.suitBase?.[parsedMinor.suitId]);
if (!Number.isFinite(suitBaseRaw)) {
return null;
}
const numberPad = Number.isInteger(minorRule.numberPad) ? minorRule.numberPad : 2;
const cardNumber = String(suitBaseRaw + rankIndex).padStart(numberPad, "0");
const template = String(minorRule.template || "{number}.png");
return applyTemplate(template, {
number: cardNumber,
suitId: parsedMinor.suitId,
rank: parsedMinor.rankWord,
rankKey: parsedMinor.rankKey,
index: rankIndex
});
}
return null;
}
function resolveCardRelativePath(manifest, cardName) {
2026-03-07 01:09:00 -08:00
if (!manifest) {
return null;
}
const canonical = canonicalMajorName(cardName);
const majorFile = resolveMajorFile(manifest, canonical);
if (majorFile) {
return majorFile;
2026-03-07 01:09:00 -08:00
}
const parsedMinor = parseMinorCard(cardName);
if (!parsedMinor) {
return null;
}
return resolveMinorFile(manifest, parsedMinor);
}
function resolveWithDeck(deckId, cardName, variant = "full") {
const manifest = getDeckManifest(deckId);
if (!manifest) {
return null;
}
const relativePath = resolveCardRelativePath(manifest, cardName);
if (!relativePath) {
2026-03-07 01:09:00 -08:00
return null;
}
if (variant === "thumbnail") {
return resolveDeckThumbnailPath(manifest, relativePath) || `${manifest.basePath}/${relativePath}`;
}
return `${manifest.basePath}/${relativePath}`;
2026-03-07 01:09:00 -08:00
}
function resolveTarotCardImage(cardName, optionsOrDeckId) {
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
const activePath = resolveWithDeck(resolvedDeckId, cardName);
2026-03-07 01:09:00 -08:00
if (activePath) {
return encodeURI(activePath);
}
2026-03-07 05:17:50 -08:00
return null;
}
function resolveTarotCardThumbnail(cardName, optionsOrDeckId) {
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
const thumbnailPath = resolveWithDeck(resolvedDeckId, cardName, "thumbnail");
if (thumbnailPath) {
return encodeURI(thumbnailPath);
}
return null;
}
2026-03-07 05:17:50 -08:00
function resolveTarotCardBackImage(optionsOrDeckId) {
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
if (cardBackCache.has(resolvedDeckId)) {
const cachedPath = cardBackCache.get(resolvedDeckId);
return cachedPath ? encodeURI(cachedPath) : null;
}
const manifest = getDeckManifest(resolvedDeckId);
const activeBackPath = resolveDeckCardBackPath(manifest);
cardBackCache.set(resolvedDeckId, activeBackPath || null);
if (activeBackPath) {
return encodeURI(activeBackPath);
2026-03-07 01:09:00 -08:00
}
return null;
}
function resolveTarotCardBackThumbnail(optionsOrDeckId) {
const { resolvedDeckId } = resolveDeckOptions(optionsOrDeckId);
if (cardBackThumbnailCache.has(resolvedDeckId)) {
const cachedPath = cardBackThumbnailCache.get(resolvedDeckId);
return cachedPath ? encodeURI(cachedPath) : null;
}
const manifest = getDeckManifest(resolvedDeckId);
const relativeBackPath = String(manifest?.cardBack || manifest?.cardBackPath || "").trim();
const thumbnailPath = resolveDeckThumbnailPath(manifest, relativeBackPath) || resolveDeckCardBackPath(manifest);
cardBackThumbnailCache.set(resolvedDeckId, thumbnailPath || null);
return thumbnailPath ? encodeURI(thumbnailPath) : null;
}
2026-03-07 01:09:00 -08:00
function resolveDisplayNameWithDeck(deckId, cardName, trumpNumber) {
const manifest = getDeckManifest(deckId);
const fallbackName = String(cardName || "").trim();
if (!manifest) {
return fallbackName;
}
let resolvedTrumpNumber = normalizeTrumpNumber(trumpNumber);
if (!Number.isInteger(resolvedTrumpNumber)) {
const canonical = canonicalMajorName(cardName);
resolvedTrumpNumber = normalizeTrumpNumber(trumpNumberByCanonicalName[canonical]);
}
if (Number.isInteger(resolvedTrumpNumber)) {
const byTrump = manifest?.majorNameOverridesByTrump?.[resolvedTrumpNumber];
if (byTrump) {
return byTrump;
}
}
const canonical = canonicalMajorName(cardName);
const override = manifest?.nameOverrides?.[canonical];
if (override) {
return override;
}
const minorKey = canonicalMinorName(cardName);
const minorOverride = manifest?.minorNameOverrides?.[minorKey];
if (minorOverride) {
return minorOverride;
}
return fallbackName;
}
function getTarotCardSearchAliases(cardName, optionsOrDeckId) {
const fallbackName = String(cardName || "").trim();
if (!fallbackName) {
return [];
}
const { resolvedDeckId, trumpNumber } = resolveDeckOptions(optionsOrDeckId);
const aliases = new Set();
aliases.add(fallbackName);
const displayName = String(resolveDisplayNameWithDeck(resolvedDeckId, fallbackName, trumpNumber) || "").trim();
if (displayName) {
aliases.add(displayName);
}
const canonicalMajor = canonicalMajorName(fallbackName);
const resolvedTrumpNumber = Number.isInteger(normalizeTrumpNumber(trumpNumber))
? normalizeTrumpNumber(trumpNumber)
: normalizeTrumpNumber(trumpNumberByCanonicalName[canonicalMajor]);
if (Number.isInteger(resolvedTrumpNumber)) {
aliases.add(canonicalMajor);
aliases.add(`the ${canonicalMajor}`);
aliases.add(`trump ${resolvedTrumpNumber}`);
}
const parsedMinor = parseMinorCard(fallbackName);
if (parsedMinor) {
const suitAliases = suitSearchAliasesById[parsedMinor.suitId] || [parsedMinor.suitId];
suitAliases.forEach((suitAlias) => {
aliases.add(`${parsedMinor.rankKey} of ${suitAlias}`);
if (Number.isInteger(parsedMinor.pipValue)) {
aliases.add(`${parsedMinor.pipValue} of ${suitAlias}`);
}
});
}
return Array.from(aliases);
}
function getTarotCardDisplayName(cardName, optionsOrDeckId) {
const { resolvedDeckId, trumpNumber } = resolveDeckOptions(optionsOrDeckId);
return resolveDisplayNameWithDeck(resolvedDeckId, cardName, trumpNumber);
}
function setActiveDeck(deckId) {
activeDeckId = normalizeDeckId(deckId);
getDeckManifest(activeDeckId);
return activeDeckId;
}
function getDeckOptions() {
2026-03-07 05:17:50 -08:00
return Object.values(getDeckManifestSources()).map((source) => {
2026-03-07 01:09:00 -08:00
const manifest = getDeckManifest(source.id);
return {
id: source.id,
label: manifest?.label || source.label
};
});
}
document.addEventListener("settings:updated", (event) => {
const nextDeck = event?.detail?.settings?.tarotDeck;
setActiveDeck(nextDeck);
});
window.TarotCardImages = {
resolveTarotCardImage,
resolveTarotCardThumbnail,
2026-03-07 05:17:50 -08:00
resolveTarotCardBackImage,
resolveTarotCardBackThumbnail,
2026-03-07 01:09:00 -08:00
getTarotCardDisplayName,
getTarotCardSearchAliases,
setActiveDeck,
getDeckOptions,
getActiveDeck: () => activeDeckId
};
})();