Files
TaroTime/app/quiz-calendars.js

454 lines
14 KiB
JavaScript
Raw Permalink Normal View History

2026-03-07 01:09:00 -08:00
/* quiz-calendars.js — Dynamic quiz category plugin for calendar systems */
/* Registers Hebrew, Islamic, and Wheel of the Year quiz categories with the quiz engine */
(function () {
"use strict";
2026-03-08 03:52:25 -07:00
const quizPluginHelpers = window.QuizPluginHelpers || {};
const {
normalizeOption,
normalizeKey,
toUniqueOptionList,
makeTemplate
} = quizPluginHelpers;
if (
typeof normalizeOption !== "function"
|| typeof normalizeKey !== "function"
|| typeof toUniqueOptionList !== "function"
|| typeof makeTemplate !== "function"
) {
throw new Error("QuizPluginHelpers must load before quiz-calendars.js");
2026-03-07 01:09:00 -08:00
}
function ordinal(n) {
const num = Number(n);
if (!Number.isFinite(num)) return String(n);
const s = ["th", "st", "nd", "rd"];
const v = num % 100;
return num + (s[(v - 20) % 10] || s[v] || s[0]);
}
function getCalendarHolidayEntries(referenceData, calendarId) {
const all = Array.isArray(referenceData?.calendarHolidays) ? referenceData.calendarHolidays : [];
const target = String(calendarId || "").trim().toLowerCase();
return all.filter((holiday) => String(holiday?.calendarId || "").trim().toLowerCase() === target);
}
// ---- Hebrew Calendar Quiz --------------------------------------------------------
function buildHebrewCalendarQuiz(referenceData) {
const months = Array.isArray(referenceData?.hebrewCalendar?.months)
? referenceData.hebrewCalendar.months
: [];
if (months.length < 4) return [];
const bank = [];
const categoryId = "hebrew-calendar-months";
const category = "Hebrew Calendar";
const regularMonths = months.filter((m) => !m.leapYearOnly);
const namePool = toUniqueOptionList(regularMonths.map((m) => m.name));
const orderPool = toUniqueOptionList(regularMonths.map((m) => ordinal(m.order)));
const nativeNamePool = toUniqueOptionList(regularMonths.map((m) => m.nativeName).filter(Boolean));
const zodiacPool = toUniqueOptionList(
regularMonths.map((m) => m.zodiacSign ? m.zodiacSign.charAt(0).toUpperCase() + m.zodiacSign.slice(1) : "").filter(Boolean)
);
const tribePool = toUniqueOptionList(regularMonths.map((m) => m.tribe).filter(Boolean));
const sensePool = toUniqueOptionList(regularMonths.map((m) => m.sense).filter(Boolean));
regularMonths.forEach((month) => {
const name = month.name;
const orderStr = ordinal(month.order);
const nativeName = month.nativeName;
const zodiac = month.zodiacSign
? month.zodiacSign.charAt(0).toUpperCase() + month.zodiacSign.slice(1)
: null;
// "Which month is Nisan in the Hebrew calendar?" → "1st"
if (namePool.length >= 4 && orderPool.length >= 4) {
const t = makeTemplate(
`hebrew-month-order:${month.id}`,
categoryId,
category,
`${name} is the ___ month of the Hebrew religious year`,
orderStr,
orderPool
);
if (t) bank.push(t);
}
// "The 1st month of the Hebrew calendar is" → "Nisan"
if (namePool.length >= 4 && orderPool.length >= 4) {
const t = makeTemplate(
`hebrew-order-to-name:${month.id}`,
categoryId,
category,
`The ${orderStr} month of the Hebrew religious year is`,
name,
namePool
);
if (t) bank.push(t);
}
// Native name → month name
if (nativeName && nativeNamePool.length >= 4) {
const t = makeTemplate(
`hebrew-native-name:${month.id}`,
categoryId,
category,
`The Hebrew month written as "${nativeName}" is`,
name,
namePool
);
if (t) bank.push(t);
}
// Zodiac association
if (zodiac && zodiacPool.length >= 4) {
const t = makeTemplate(
`hebrew-month-zodiac:${month.id}`,
categoryId,
category,
`The Hebrew month of ${name} corresponds to the zodiac sign`,
zodiac,
zodiacPool
);
if (t) bank.push(t);
}
// Tribe of Israel
if (month.tribe && tribePool.length >= 4) {
const t = makeTemplate(
`hebrew-month-tribe:${month.id}`,
categoryId,
category,
`The Hebrew month of ${name} is associated with the tribe of`,
month.tribe,
tribePool
);
if (t) bank.push(t);
}
// Sense
if (month.sense && sensePool.length >= 4) {
const t = makeTemplate(
`hebrew-month-sense:${month.id}`,
categoryId,
category,
`The sense associated with the Hebrew month of ${name} is`,
month.sense,
sensePool
);
if (t) bank.push(t);
}
});
// Holiday repository-based questions (which month does X fall in?)
const monthNameById = new Map(regularMonths.map((month) => [String(month.id), month.name]));
const allObservances = getCalendarHolidayEntries(referenceData, "hebrew")
.map((holiday) => {
const monthName = monthNameById.get(String(holiday?.monthId || ""));
const obsName = String(holiday?.name || "").trim();
if (!monthName || !obsName) {
return null;
}
return { obsName, monthName };
})
.filter(Boolean);
if (namePool.length >= 4) {
allObservances.forEach(({ obsName, monthName }) => {
const t = makeTemplate(
`hebrew-obs-month:${obsName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")}`,
categoryId,
category,
`${obsName} occurs in which Hebrew month`,
monthName,
namePool
);
if (t) bank.push(t);
});
}
return bank;
}
// ---- Islamic Calendar Quiz -------------------------------------------------------
function buildIslamicCalendarQuiz(referenceData) {
const months = Array.isArray(referenceData?.islamicCalendar?.months)
? referenceData.islamicCalendar.months
: [];
if (months.length < 4) return [];
const bank = [];
const categoryId = "islamic-calendar-months";
const category = "Islamic Calendar";
const namePool = toUniqueOptionList(months.map((m) => m.name));
const orderPool = toUniqueOptionList(months.map((m) => ordinal(m.order)));
const meaningPool = toUniqueOptionList(months.map((m) => m.meaning).filter(Boolean));
months.forEach((month) => {
const name = month.name;
const orderStr = ordinal(month.order);
// Order → name
const t1 = makeTemplate(
`islamic-order-to-name:${month.id}`,
categoryId,
category,
`The ${orderStr} month of the Islamic calendar is`,
name,
namePool
);
if (t1) bank.push(t1);
// Name → order
const t2 = makeTemplate(
`islamic-month-order:${month.id}`,
categoryId,
category,
`${name} is the ___ month of the Islamic calendar`,
orderStr,
orderPool
);
if (t2) bank.push(t2);
// Meaning of name
if (month.meaning && meaningPool.length >= 4) {
const t3 = makeTemplate(
`islamic-month-meaning:${month.id}`,
categoryId,
category,
`The name "${name}" in Arabic means`,
month.meaning,
meaningPool
);
if (t3) bank.push(t3);
}
// Sacred month identification
if (month.sacred) {
const yesNoPool = ["Yes — warfare prohibited", "No", "Partially sacred", "Conditionally sacred"];
const t4 = makeTemplate(
`islamic-sacred-${month.id}`,
categoryId,
category,
`Is ${name} one of the four sacred months (Al-Ashhur Al-Hurum)?`,
"Yes — warfare prohibited",
yesNoPool
);
if (t4) bank.push(t4);
}
});
// Observance-based: "Ramadan is the Islamic month of ___" type
const observanceFacts = [
{ q: "The Islamic month of obligatory fasting (Sawm) is", a: "Ramadan" },
{ q: "Eid al-Fitr is celebrated in which Islamic month", a: "Shawwal" },
{ q: "Eid al-Adha falls in which Islamic month", a: "Dhu al-Hijja" },
{ q: "The Hajj pilgrimage takes place in which month", a: "Dhu al-Hijja" },
{ q: "The Prophet Muhammad's birth (Mawlid al-Nabi) is in", a: "Rabi' al-Awwal" },
{ q: "Ashura falls in which Islamic month", a: "Muharram" },
{ q: "Laylat al-Mi'raj (Night of Ascension) is in which month", a: "Rajab" },
{ q: "The Islamic New Year (Hijri New Year) begins in", a: "Muharram" }
];
observanceFacts.forEach(({ q, a }) => {
if (namePool.some((n) => normalizeKey(n) === normalizeKey(a))) {
const t = makeTemplate(
`islamic-fact:${q.slice(0, 30).toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")}`,
categoryId,
category,
q,
a,
namePool
);
if (t) bank.push(t);
}
});
return bank;
}
// ---- Wheel of the Year Quiz ------------------------------------------------------
function buildWheelOfYearQuiz(referenceData) {
const months = Array.isArray(referenceData?.wheelOfYear?.months)
? referenceData.wheelOfYear.months
: [];
if (months.length < 4) return [];
const bank = [];
const categoryId = "wheel-of-year";
const category = "Wheel of the Year";
const namePool = toUniqueOptionList(months.map((m) => m.name));
const typePool = toUniqueOptionList(months.map((m) => m.type ? m.type.charAt(0).toUpperCase() + m.type.slice(1) : "").filter(Boolean));
const elementPool = toUniqueOptionList(
months.map((m) => m.element || (m.associations && m.associations.element) || "").filter(Boolean)
);
const datePool = toUniqueOptionList(months.map((m) => m.date).filter(Boolean));
months.forEach((month) => {
const name = month.name;
const date = month.date;
const element = month.element || "";
const direction = month.associations?.direction || "";
const directionPool = toUniqueOptionList(months.map((m) => m.associations?.direction || "").filter(Boolean));
// Date → Sabbat name
if (date && datePool.length >= 4) {
const t1 = makeTemplate(
`wheel-date-name:${month.id}`,
categoryId,
category,
`The Sabbat on ${date} is`,
name,
namePool
);
if (t1) bank.push(t1);
}
// Sabbat name → date
if (date && datePool.length >= 4) {
const t2 = makeTemplate(
`wheel-name-date:${month.id}`,
categoryId,
category,
`${name} falls on`,
date,
datePool
);
if (t2) bank.push(t2);
}
// Festival type (solar / cross-quarter)
if (month.type && typePool.length >= 2) {
const capType = month.type.charAt(0).toUpperCase() + month.type.slice(1);
const t3 = makeTemplate(
`wheel-type:${month.id}`,
categoryId,
category,
`${name} is a ___ festival`,
capType,
typePool
);
if (t3) bank.push(t3);
}
// Element association
if (element && elementPool.length >= 4) {
const t4 = makeTemplate(
`wheel-element:${month.id}`,
categoryId,
category,
`The primary element associated with ${name} is`,
element,
elementPool
);
if (t4) bank.push(t4);
}
// Direction
if (direction && directionPool.length >= 4) {
const t5 = makeTemplate(
`wheel-direction:${month.id}`,
categoryId,
category,
`The direction associated with ${name} is`,
direction,
directionPool
);
if (t5) bank.push(t5);
}
// Deities pool question
const deities = Array.isArray(month.associations?.deities) ? month.associations.deities : [];
const allDeities = toUniqueOptionList(
months.flatMap((m) => Array.isArray(m.associations?.deities) ? m.associations.deities : [])
);
if (deities.length > 0 && allDeities.length >= 4) {
const mainDeity = deities[0];
const t6 = makeTemplate(
`wheel-deity:${month.id}`,
categoryId,
category,
`${mainDeity} is primarily associated with which Sabbat`,
name,
namePool
);
if (t6) bank.push(t6);
}
});
// Fixed knowledge questions
const wheelFacts = [
{ q: "The Celtic New Year Sabbat is", a: "Samhain" },
{ q: "Which Sabbat marks the longest night of the year", a: "Yule (Winter Solstice)" },
{ q: "The Spring Equinox Sabbat is called", a: "Ostara (Spring Equinox)" },
{ q: "The Summer Solstice Sabbat is called", a: "Litha (Summer Solstice)" },
{ q: "Which Sabbat is the first harvest festival", a: "Lughnasadh" },
{ q: "The Autumn Equinox Sabbat is called", a: "Mabon (Autumn Equinox)" },
{ q: "Which Sabbat is associated with the goddess Brigid", a: "Imbolc" },
{ q: "Beltane celebrates the beginning of which season", a: "Summer" }
];
wheelFacts.forEach(({ q, a }, index) => {
const pool = index < 7 ? namePool : toUniqueOptionList(["Spring", "Summer", "Autumn / Fall", "Winter"]);
if (pool.some((p) => normalizeKey(p) === normalizeKey(a))) {
const t = makeTemplate(
`wheel-fact-${index}`,
categoryId,
category,
q,
a,
pool
);
if (t) bank.push(t);
}
});
return bank;
}
// ---- Registration ----------------------------------------------------------------
function registerCalendarQuizCategories() {
const { registerQuizCategory } = window.QuizSectionUi || {};
if (typeof registerQuizCategory !== "function") {
return;
}
registerQuizCategory(
"hebrew-calendar-months",
"Hebrew Calendar",
(referenceData) => buildHebrewCalendarQuiz(referenceData)
);
registerQuizCategory(
"islamic-calendar-months",
"Islamic Calendar",
(referenceData) => buildIslamicCalendarQuiz(referenceData)
);
registerQuizCategory(
"wheel-of-year",
"Wheel of the Year",
(referenceData) => buildWheelOfYearQuiz(referenceData)
);
}
// Register immediately — ui-quiz.js loads before this file
registerCalendarQuizCategories();
window.QuizCalendarsPlugin = {
registerCalendarQuizCategories
};
})();