Files
TaroTime/app/quiz-connections.js

873 lines
31 KiB
JavaScript
Raw Permalink Normal View History

2026-03-08 03:52:25 -07:00
/* quiz-connections.js — Dynamic quiz category plugin for dataset relations */
(function () {
"use strict";
const { getTarotCardDisplayName } = window.TarotCardImages || {};
const quizPluginHelpers = window.QuizPluginHelpers || {};
const {
normalizeOption,
normalizeKey,
buildTemplatesFromSpec,
buildTemplatesFromVariants
} = quizPluginHelpers;
if (
typeof normalizeOption !== "function"
|| typeof normalizeKey !== "function"
|| typeof buildTemplatesFromSpec !== "function"
|| typeof buildTemplatesFromVariants !== "function"
) {
throw new Error("QuizPluginHelpers must load before quiz-connections.js");
}
const cache = {
referenceData: null,
magickDataset: null,
templates: [],
templatesByCategory: new Map()
};
function toTitleCase(value) {
const text = normalizeOption(value).toLowerCase();
if (!text) {
return "";
}
return text.charAt(0).toUpperCase() + text.slice(1);
}
function labelFromId(value) {
const id = normalizeOption(value);
if (!id) {
return "";
}
return id
.replace(/[_-]+/g, " ")
.replace(/\s+/g, " ")
.trim()
.split(" ")
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : ""))
.join(" ");
}
function formatHebrewLetterLabel(entry, fallbackId = "") {
if (entry?.name && entry?.char) {
return `${entry.name} (${entry.char})`;
}
if (entry?.name) {
return entry.name;
}
if (entry?.char) {
return entry.char;
}
return labelFromId(fallbackId);
}
function slugifyId(value) {
return normalizeKey(value)
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
function formatHexagramLabel(entry) {
const number = Number(entry?.number);
const name = normalizeOption(entry?.name);
if (Number.isFinite(number) && name) {
return `Hexagram ${number}: ${name}`;
}
if (Number.isFinite(number)) {
return `Hexagram ${number}`;
}
return name || "Hexagram";
}
function formatTrigramLabel(trigram) {
const name = normalizeOption(trigram?.name);
const element = normalizeOption(trigram?.element);
if (name && element) {
return `${name} (${element})`;
}
return name || element;
}
function formatTarotLabel(value) {
const raw = normalizeOption(value);
if (!raw) {
return "";
}
const keyMatch = raw.match(/^key\s*(\d{1,2})\s*:\s*(.+)$/i);
if (keyMatch) {
const trumpNumber = Number(keyMatch[1]);
const fallbackName = normalizeOption(keyMatch[2]);
if (typeof getTarotCardDisplayName === "function") {
const displayName = normalizeOption(getTarotCardDisplayName(fallbackName, { trumpNumber }));
if (displayName) {
return displayName;
}
}
return fallbackName;
}
if (typeof getTarotCardDisplayName === "function") {
const displayName = normalizeOption(getTarotCardDisplayName(raw));
if (displayName) {
return displayName;
}
}
return raw.replace(/\bPentacles\b/gi, "Disks");
}
function getPlanetLabelById(planetId, planetsById) {
const key = normalizeKey(planetId);
const label = planetsById?.[key]?.name;
if (label) {
return normalizeOption(label);
}
if (key === "primum-mobile") {
return "Primum Mobile";
}
if (key === "olam-yesodot") {
return "Earth / Elements";
}
return normalizeOption(labelFromId(planetId));
}
function formatPathLetter(path) {
const transliteration = normalizeOption(path?.hebrewLetter?.transliteration);
const glyph = normalizeOption(path?.hebrewLetter?.char);
if (transliteration && glyph) {
return `${transliteration} (${glyph})`;
}
return transliteration || glyph;
}
function getSephiraName(numberValue, idValue, sephiraByNumber, sephiraById) {
const numberKey = Number(numberValue);
if (Number.isFinite(numberKey) && sephiraByNumber.has(Math.trunc(numberKey))) {
return sephiraByNumber.get(Math.trunc(numberKey));
}
const idKey = normalizeKey(idValue);
if (idKey && sephiraById.has(idKey)) {
return sephiraById.get(idKey);
}
if (Number.isFinite(numberKey)) {
return `Sephira ${Math.trunc(numberKey)}`;
}
return labelFromId(idValue);
}
function toRomanNumeral(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric) || numeric <= 0) {
return String(value || "");
}
const lookup = [
[1000, "M"],
[900, "CM"],
[500, "D"],
[400, "CD"],
[100, "C"],
[90, "XC"],
[50, "L"],
[40, "XL"],
[10, "X"],
[9, "IX"],
[5, "V"],
[4, "IV"],
[1, "I"]
];
let current = Math.trunc(numeric);
let result = "";
lookup.forEach(([size, symbol]) => {
while (current >= size) {
result += symbol;
current -= size;
}
});
return result || String(Math.trunc(numeric));
}
function formatDecanLabel(decan, signNameById) {
const signName = signNameById.get(normalizeKey(decan?.signId)) || labelFromId(decan?.signId);
const index = Number(decan?.index);
if (!signName || !Number.isFinite(index)) {
return "";
}
return `${signName} Decan ${toRomanNumeral(index)}`;
}
function buildEnglishGematriaCipherGroups(englishLetters, gematriaCiphers) {
const ciphers = Array.isArray(gematriaCiphers?.ciphers) ? gematriaCiphers.ciphers : [];
return ciphers
.map((cipher) => {
const cipherId = normalizeOption(cipher?.id);
const cipherName = normalizeOption(cipher?.name || labelFromId(cipherId));
const slug = slugifyId(cipherId || cipherName);
const values = Array.isArray(cipher?.values) ? cipher.values : [];
if (!slug || !cipherName || !values.length) {
return null;
}
const categoryId = `english-gematria-${slug}`;
const category = `English Gematria: ${cipherName}`;
const rows = englishLetters
.filter((entry) => normalizeOption(entry?.letter) && Number.isFinite(Number(entry?.index)))
.map((entry) => {
const position = Math.trunc(Number(entry.index));
const value = values[position - 1];
if (!Number.isFinite(Number(value))) {
return null;
}
return {
id: normalizeOption(entry.letter),
letter: normalizeOption(entry.letter),
value: String(Math.trunc(Number(value))),
cipherName
};
})
.filter(Boolean);
if (rows.length < 4) {
return null;
}
return {
entries: rows,
variants: [
{
keyPrefix: categoryId,
categoryId,
category,
getKey: (entry) => entry.id,
getPrompt: (entry) => `In the ${entry.cipherName} cipher, ${entry.letter} has a value of`,
getAnswer: (entry) => entry.value
}
]
};
})
.filter(Boolean);
}
function buildAutomatedConnectionTemplates(referenceData, magickDataset) {
if (cache.referenceData === referenceData && cache.magickDataset === magickDataset) {
return cache.templates;
}
const planetsById = referenceData?.planets && typeof referenceData.planets === "object"
? referenceData.planets
: {};
const planets = Object.values(planetsById).filter(Boolean);
const signs = Array.isArray(referenceData?.signs) ? referenceData.signs : [];
const gematriaCiphers = referenceData?.gematriaCiphers && typeof referenceData.gematriaCiphers === "object"
? referenceData.gematriaCiphers
: {};
const decansBySign = referenceData?.decansBySign && typeof referenceData.decansBySign === "object"
? referenceData.decansBySign
: {};
const signNameById = new Map(
signs
.filter((entry) => normalizeOption(entry?.id) && normalizeOption(entry?.name))
.map((entry) => [normalizeKey(entry.id), normalizeOption(entry.name)])
);
const grouped = magickDataset?.grouped || {};
const alphabets = grouped.alphabets || {};
const englishLetters = Array.isArray(alphabets?.english) ? alphabets.english : [];
const hebrewLetters = Array.isArray(alphabets?.hebrew) ? alphabets.hebrew : [];
const hebrewById = new Map(
hebrewLetters
.filter((entry) => normalizeOption(entry?.hebrewLetterId))
.map((entry) => [normalizeKey(entry.hebrewLetterId), entry])
);
const kabbalahTree = grouped?.kabbalah?.["kabbalah-tree"] || {};
const treePaths = Array.isArray(kabbalahTree?.paths) ? kabbalahTree.paths : [];
const treeSephiroth = Array.isArray(kabbalahTree?.sephiroth) ? kabbalahTree.sephiroth : [];
const sephiraByNumber = new Map(
treeSephiroth
.filter((entry) => Number.isFinite(Number(entry?.number)) && normalizeOption(entry?.name))
.map((entry) => [Math.trunc(Number(entry.number)), normalizeOption(entry.name)])
);
const sephiraByTreeId = new Map(
treeSephiroth
.filter((entry) => normalizeOption(entry?.sephiraId) && normalizeOption(entry?.name))
.map((entry) => [normalizeKey(entry.sephiraId), normalizeOption(entry.name)])
);
const sephirotById = grouped?.kabbalah?.sephirot && typeof grouped.kabbalah.sephirot === "object"
? grouped.kabbalah.sephirot
: {};
const cube = grouped?.kabbalah?.cube && typeof grouped.kabbalah.cube === "object"
? grouped.kabbalah.cube
: {};
const cubeWalls = Array.isArray(cube?.walls) ? cube.walls : [];
const cubeEdges = Array.isArray(cube?.edges) ? cube.edges : [];
const cubeCenter = cube?.center && typeof cube.center === "object" ? cube.center : null;
const playingCardsData = grouped?.["playing-cards-52"];
const playingCards = Array.isArray(playingCardsData)
? playingCardsData
: (Array.isArray(playingCardsData?.entries) ? playingCardsData.entries : []);
const flattenDecans = Object.values(decansBySign).flatMap((entries) => Array.isArray(entries) ? entries : []);
const iChing = referenceData?.iChing;
const trigrams = Array.isArray(iChing?.trigrams) ? iChing.trigrams : [];
const hexagrams = Array.isArray(iChing?.hexagrams) ? iChing.hexagrams : [];
const correspondences = Array.isArray(iChing?.correspondences?.tarotToTrigram)
? iChing.correspondences.tarotToTrigram
: [];
const trigramByKey = new Map(
trigrams
.map((trigram) => [normalizeKey(trigram?.name), trigram])
.filter(([key]) => Boolean(key))
);
const hexagramRows = hexagrams
.map((entry) => {
const upper = trigramByKey.get(normalizeKey(entry?.upperTrigram)) || null;
const lower = trigramByKey.get(normalizeKey(entry?.lowerTrigram)) || null;
return {
...entry,
number: Number(entry?.number),
hexagramLabel: formatHexagramLabel(entry),
upperLabel: formatTrigramLabel(upper || { name: entry?.upperTrigram }),
lowerLabel: formatTrigramLabel(lower || { name: entry?.lowerTrigram })
};
})
.filter((entry) => Number.isFinite(entry.number) && entry.hexagramLabel);
const tarotCorrespondenceRows = correspondences
.map((entry, index) => {
const trigram = trigramByKey.get(normalizeKey(entry?.trigram)) || null;
return {
id: `${index}-${normalizeKey(entry?.tarot)}-${normalizeKey(entry?.trigram)}`,
tarotLabel: formatTarotLabel(entry?.tarot),
trigramLabel: formatTrigramLabel(trigram || { name: entry?.trigram })
};
})
.filter((entry) => entry.tarotLabel && entry.trigramLabel);
const englishGematriaGroups = buildEnglishGematriaCipherGroups(englishLetters, gematriaCiphers);
const hebrewNumerologyRows = hebrewLetters
.filter((entry) => normalizeOption(entry?.name) && normalizeOption(entry?.char) && Number.isFinite(Number(entry?.numerology)))
.map((entry) => ({
id: normalizeOption(entry.hebrewLetterId || entry.name),
glyphLabel: `${normalizeOption(entry.name)} (${normalizeOption(entry.char)})`,
char: normalizeOption(entry.char),
value: String(Math.trunc(Number(entry.numerology)))
}));
const englishHebrewRows = englishLetters
.map((entry) => {
const hebrew = hebrewById.get(normalizeKey(entry?.hebrewLetterId)) || null;
if (!normalizeOption(entry?.letter) || !hebrew) {
return null;
}
return {
id: normalizeOption(entry.letter),
letter: normalizeOption(entry.letter),
hebrewLabel: formatHebrewLetterLabel(hebrew, entry?.hebrewLetterId),
hebrewChar: normalizeOption(hebrew?.char)
};
})
.filter(Boolean);
const kabbalahPathRows = treePaths
.map((path) => {
const pathNumber = Number(path?.pathNumber);
if (!Number.isFinite(pathNumber)) {
return null;
}
const pathNumberLabel = String(Math.trunc(pathNumber));
const fromName = getSephiraName(path?.connects?.from, path?.connectIds?.from, sephiraByNumber, sephiraByTreeId);
const toName = getSephiraName(path?.connects?.to, path?.connectIds?.to, sephiraByNumber, sephiraByTreeId);
const letterLabel = formatPathLetter(path);
const tarotLabel = formatTarotLabel(path?.tarot?.card);
return {
id: pathNumberLabel,
pathNumberLabel,
pathPairLabel: fromName && toName ? `${fromName}${toName}` : "",
fromName,
toName,
letterLabel,
tarotLabel
};
})
.filter(Boolean);
const sephirotRows = Object.values(sephirotById || {})
.map((sephira) => {
const sephiraName = normalizeOption(sephira?.name?.roman || sephira?.name?.en);
const planetLabel = getPlanetLabelById(sephira?.planetId, planetsById);
if (!sephiraName || !planetLabel) {
return null;
}
return {
id: normalizeOption(sephira?.id || sephiraName),
sephiraName,
planetLabel
};
})
.filter(Boolean);
const decanRows = flattenDecans
.map((decan) => {
const id = normalizeOption(decan?.id);
const tarotLabel = formatTarotLabel(decan?.tarotMinorArcana);
const decanLabel = formatDecanLabel(decan, signNameById);
const rulerLabel = getPlanetLabelById(decan?.rulerPlanetId, planetsById);
if (!id || !tarotLabel) {
return null;
}
return {
id,
tarotLabel,
decanLabel,
rulerLabel
};
})
.filter(Boolean);
const cubeTarotRows = [
...cubeWalls.map((wall) => {
const wallName = normalizeOption(wall?.name || labelFromId(wall?.id));
const locationLabel = wallName ? `${wallName} Wall` : "";
const tarotLabel = formatTarotLabel(wall?.associations?.tarotCard);
return tarotLabel && locationLabel
? { id: normalizeOption(wall?.id || wallName), tarotLabel, locationLabel }
: null;
}),
...cubeEdges.map((edge) => {
const edgeName = normalizeOption(edge?.name || labelFromId(edge?.id));
const locationLabel = edgeName ? `${edgeName} Edge` : "";
const hebrew = hebrewById.get(normalizeKey(edge?.hebrewLetterId)) || null;
const tarotLabel = formatTarotLabel(hebrew?.tarot?.card);
return tarotLabel && locationLabel
? { id: normalizeOption(edge?.id || edgeName), tarotLabel, locationLabel }
: null;
}),
(() => {
const tarotLabel = formatTarotLabel(cubeCenter?.associations?.tarotCard || cubeCenter?.tarotCard);
return tarotLabel ? { id: "center", tarotLabel, locationLabel: "Center" } : null;
})()
].filter(Boolean);
const cubeHebrewRows = [
...cubeWalls.map((wall) => {
const wallName = normalizeOption(wall?.name || labelFromId(wall?.id));
const locationLabel = wallName ? `${wallName} Wall` : "";
const hebrew = hebrewById.get(normalizeKey(wall?.hebrewLetterId)) || null;
const hebrewLabel = formatHebrewLetterLabel(hebrew, wall?.hebrewLetterId);
return locationLabel && hebrewLabel
? { id: normalizeOption(wall?.id || wallName), locationLabel, hebrewLabel }
: null;
}),
...cubeEdges.map((edge) => {
const edgeName = normalizeOption(edge?.name || labelFromId(edge?.id));
const locationLabel = edgeName ? `${edgeName} Edge` : "";
const hebrew = hebrewById.get(normalizeKey(edge?.hebrewLetterId)) || null;
const hebrewLabel = formatHebrewLetterLabel(hebrew, edge?.hebrewLetterId);
return locationLabel && hebrewLabel
? { id: normalizeOption(edge?.id || edgeName), locationLabel, hebrewLabel }
: null;
}),
(() => {
const hebrew = hebrewById.get(normalizeKey(cubeCenter?.hebrewLetterId)) || null;
const hebrewLabel = formatHebrewLetterLabel(hebrew, cubeCenter?.hebrewLetterId);
return hebrewLabel ? { id: "center", locationLabel: "Center", hebrewLabel } : null;
})()
].filter(Boolean);
const playingCardRows = playingCards
.map((entry) => {
const cardId = normalizeOption(entry?.id);
const rankLabel = normalizeOption(entry?.rankLabel || entry?.rank);
const suitLabel = normalizeOption(entry?.suitLabel || labelFromId(entry?.suit));
const tarotLabel = formatTarotLabel(entry?.tarotCard);
const playingCardLabel = rankLabel && suitLabel ? `${rankLabel} of ${suitLabel}` : "";
return cardId && playingCardLabel && tarotLabel
? { id: cardId, playingCardLabel, tarotLabel }
: null;
})
.filter(Boolean);
const specGroups = [
...englishGematriaGroups,
{
entries: hebrewNumerologyRows,
variants: [
{
keyPrefix: "hebrew-number",
categoryId: "hebrew-numerology",
category: "Hebrew Gematria",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.glyphLabel} has a gematria value of`,
getAnswer: (entry) => entry.value
}
]
},
{
entries: englishHebrewRows,
variants: [
{
keyPrefix: "english-hebrew",
categoryId: "english-hebrew-mapping",
category: "Alphabet Mapping",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.letter} maps to which Hebrew letter`,
getAnswer: (entry) => entry.hebrewLabel,
inverse: {
keyPrefix: "hebrew-english",
getUniquenessKey: (entry) => entry.hebrewLabel,
getKey: (entry) => entry.hebrewChar || entry.hebrewLabel,
getPrompt: (entry) => `${entry.hebrewLabel} maps to which English letter`,
getAnswer: (entry) => entry.letter
}
}
]
},
{
entries: signs.filter((entry) => entry?.name && entry?.rulingPlanetId),
variants: [
{
keyPrefix: "zodiac-ruler",
categoryId: "zodiac-rulers",
category: "Zodiac Rulers",
getKey: (entry) => entry.id || entry.name,
getPrompt: (entry) => `${entry.name} is ruled by`,
getAnswer: (entry) => getPlanetLabelById(entry.rulingPlanetId, planetsById)
}
]
},
{
entries: signs.filter((entry) => entry?.name && entry?.element),
variants: [
{
keyPrefix: "zodiac-element",
categoryId: "zodiac-elements",
category: "Zodiac Elements",
getKey: (entry) => entry.id || entry.name,
getPrompt: (entry) => `${entry.name} is`,
getAnswer: (entry) => normalizeOption(entry.element).replace(/^./, (char) => char.toUpperCase())
}
]
},
{
entries: planets.filter((entry) => entry?.name && entry?.weekday),
variants: [
{
keyPrefix: "planet-weekday",
categoryId: "planetary-weekdays",
category: "Planetary Weekdays",
getKey: (entry) => entry.id || entry.name,
getPrompt: (entry) => `${entry.name} corresponds to`,
getAnswer: (entry) => normalizeOption(entry.weekday),
inverse: {
keyPrefix: "weekday-planet",
getUniquenessKey: (entry) => normalizeOption(entry.weekday),
getKey: (entry) => normalizeOption(entry.weekday),
getPrompt: (entry) => `${normalizeOption(entry.weekday)} corresponds to which planet`,
getAnswer: (entry) => normalizeOption(entry.name)
}
}
]
},
{
entries: signs.filter((entry) => entry?.name && (entry?.tarot?.majorArcana || entry?.tarot?.card)),
variants: [
{
keyPrefix: "zodiac-tarot",
categoryId: "zodiac-tarot",
category: "Zodiac ↔ Tarot",
getKey: (entry) => entry.id || entry.name,
getPrompt: (entry) => `${entry.name} corresponds to`,
getAnswer: (entry) => formatTarotLabel(entry?.tarot?.majorArcana || entry?.tarot?.card),
inverse: {
keyPrefix: "tarot-zodiac",
getUniquenessKey: (entry) => formatTarotLabel(entry?.tarot?.majorArcana || entry?.tarot?.card),
getKey: (entry) => formatTarotLabel(entry?.tarot?.majorArcana || entry?.tarot?.card),
getPrompt: (entry) => `${formatTarotLabel(entry?.tarot?.majorArcana || entry?.tarot?.card)} corresponds to which zodiac sign`,
getAnswer: (entry) => normalizeOption(entry.name)
}
}
]
},
{
entries: kabbalahPathRows.filter((entry) => entry.fromName && entry.toName),
variants: [
{
keyPrefix: "kabbalah-path-between",
categoryId: "kabbalah-path-between-sephirot",
category: "Kabbalah Paths",
getKey: (entry) => entry.id,
getPrompt: (entry) => `What path connects ${entry.fromName} and ${entry.toName}`,
getAnswer: (entry) => entry.pathNumberLabel
}
]
},
{
entries: kabbalahPathRows.filter((entry) => entry.letterLabel),
variants: [
{
keyPrefix: "kabbalah-path-letter",
categoryId: "kabbalah-path-letter",
category: "Kabbalah Paths",
getKey: (entry) => entry.id,
getPrompt: (entry) => `Path ${entry.pathNumberLabel} carries which Hebrew letter`,
getAnswer: (entry) => entry.letterLabel,
inverse: {
keyPrefix: "kabbalah-letter-path-number",
getUniquenessKey: (entry) => entry.letterLabel,
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.letterLabel} belongs to which path`,
getAnswer: (entry) => entry.pathNumberLabel
}
}
]
},
{
entries: kabbalahPathRows.filter((entry) => entry.tarotLabel),
variants: [
{
keyPrefix: "kabbalah-path-tarot",
categoryId: "kabbalah-path-tarot",
category: "Kabbalah ↔ Tarot",
getKey: (entry) => entry.id,
getPrompt: (entry) => `Path ${entry.pathNumberLabel} corresponds to which Tarot trump`,
getAnswer: (entry) => entry.tarotLabel,
inverse: {
keyPrefix: "tarot-trump-path",
getUniquenessKey: (entry) => entry.tarotLabel,
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.tarotLabel} is on which path`,
getAnswer: (entry) => entry.pathNumberLabel
}
}
]
},
{
entries: sephirotRows,
variants: [
{
keyPrefix: "sephirot-planet",
categoryId: "sephirot-planets",
category: "Sephirot ↔ Planet",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.sephiraName} corresponds to which planet`,
getAnswer: (entry) => entry.planetLabel
}
]
},
{
entries: decanRows.filter((entry) => entry.decanLabel),
variants: [
{
keyPrefix: "tarot-decan-sign",
categoryId: "tarot-decan-sign",
category: "Tarot Decans",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.tarotLabel} belongs to which decan`,
getAnswer: (entry) => entry.decanLabel,
inverse: {
keyPrefix: "decan-tarot-card",
getUniquenessKey: (entry) => entry.decanLabel,
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.decanLabel} corresponds to which Tarot card`,
getAnswer: (entry) => entry.tarotLabel
}
}
]
},
{
entries: decanRows.filter((entry) => entry.rulerLabel),
variants: [
{
keyPrefix: "tarot-decan-ruler",
categoryId: "tarot-decan-ruler",
category: "Tarot Decans",
getKey: (entry) => entry.id,
getPrompt: (entry) => `The decan of ${entry.tarotLabel} is ruled by`,
getAnswer: (entry) => entry.rulerLabel
}
]
},
{
entries: cubeTarotRows,
variants: [
{
keyPrefix: "tarot-cube-location",
categoryId: "tarot-cube-location",
category: "Tarot ↔ Cube",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.tarotLabel} is on which Cube location`,
getAnswer: (entry) => entry.locationLabel,
inverse: {
keyPrefix: "cube-location-tarot",
getUniquenessKey: (entry) => entry.locationLabel,
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.locationLabel} corresponds to which Tarot card`,
getAnswer: (entry) => entry.tarotLabel
}
}
]
},
{
entries: cubeHebrewRows,
variants: [
{
keyPrefix: "cube-hebrew-letter",
categoryId: "cube-hebrew-letter",
category: "Cube ↔ Hebrew",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.locationLabel} corresponds to which Hebrew letter`,
getAnswer: (entry) => entry.hebrewLabel,
inverse: {
keyPrefix: "hebrew-cube-location",
getUniquenessKey: (entry) => entry.hebrewLabel,
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.hebrewLabel} is on which Cube location`,
getAnswer: (entry) => entry.locationLabel
}
}
]
},
{
entries: playingCardRows,
variants: [
{
keyPrefix: "playing-card-tarot",
categoryId: "playing-card-tarot",
category: "Playing Card ↔ Tarot",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.playingCardLabel} maps to which Tarot card`,
getAnswer: (entry) => entry.tarotLabel,
inverse: {
keyPrefix: "tarot-playing-card",
getUniquenessKey: (entry) => entry.tarotLabel,
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.tarotLabel} maps to which playing card`,
getAnswer: (entry) => entry.playingCardLabel
}
}
]
},
{
entries: hexagramRows.filter((entry) => normalizeOption(entry.planetaryInfluence)),
variants: [
{
keyPrefix: "iching-planet",
categoryId: "iching-planetary-influence",
category: "I Ching ↔ Planet",
getKey: (entry) => String(entry.number),
getPrompt: (entry) => `${entry.hexagramLabel} corresponds to which planetary influence`,
getAnswer: (entry) => normalizeOption(entry.planetaryInfluence)
}
]
},
{
entries: hexagramRows,
variants: [
{
keyPrefix: "iching-upper-trigram",
categoryId: "iching-trigrams",
category: "I Ching Trigrams",
getKey: (entry) => `${entry.number}-upper`,
getPrompt: (entry) => `What is the upper trigram of ${entry.hexagramLabel}`,
getAnswer: (entry) => entry.upperLabel
},
{
keyPrefix: "iching-lower-trigram",
categoryId: "iching-trigrams",
category: "I Ching Trigrams",
getKey: (entry) => `${entry.number}-lower`,
getPrompt: (entry) => `What is the lower trigram of ${entry.hexagramLabel}`,
getAnswer: (entry) => entry.lowerLabel
}
]
},
{
entries: tarotCorrespondenceRows,
variants: [
{
keyPrefix: "iching-tarot-trigram",
categoryId: "iching-tarot-correspondence",
category: "I Ching ↔ Tarot",
getKey: (entry) => entry.id,
getPrompt: (entry) => `${entry.tarotLabel} corresponds to which I Ching trigram`,
getAnswer: (entry) => entry.trigramLabel
}
]
}
];
const templates = specGroups.flatMap((specGroup) => {
if (Array.isArray(specGroup.variants) && specGroup.variants.length > 1) {
return buildTemplatesFromVariants(specGroup);
}
const [variant] = specGroup.variants || [];
return buildTemplatesFromSpec({
entries: specGroup.entries,
categoryId: variant?.categoryId,
category: variant?.category,
keyPrefix: variant?.keyPrefix,
getKey: variant?.getKey,
getPrompt: variant?.getPrompt,
getAnswer: variant?.getAnswer
});
});
const templatesByCategory = new Map();
templates.forEach((template) => {
if (!templatesByCategory.has(template.categoryId)) {
templatesByCategory.set(template.categoryId, []);
}
templatesByCategory.get(template.categoryId).push(template);
});
cache.referenceData = referenceData;
cache.magickDataset = magickDataset;
cache.templates = templates;
cache.templatesByCategory = templatesByCategory;
return templates;
}
function buildConnectionTemplates(referenceData, magickDataset) {
return buildAutomatedConnectionTemplates(referenceData, magickDataset).slice();
}
function registerConnectionQuizCategories() {
const { registerQuizCategory } = window.QuizSectionUi || {};
if (typeof registerQuizCategory !== "function") {
return;
}
registerQuizCategory("quiz-connections", "Connection Quizzes", buildConnectionTemplates);
}
registerConnectionQuizCategories();
window.QuizConnectionsPlugin = {
registerConnectionQuizCategories,
buildAutomatedConnectionTemplates,
buildConnectionTemplates
};
})();