(function () { "use strict"; const THEORY_DEFAULT_INPUT = "108"; const THEORY_MAX_FACTORIZATION_ABS = 10000000000; const THEORY_MAX_COLLATZ_START = 10000000; const theoryState = { inputValue: THEORY_DEFAULT_INPUT }; function formatTheoryNumber(value, fractionDigits = 12) { if (!Number.isFinite(value)) { return "--"; } if (Object.is(value, -0)) { return "0"; } const abs = Math.abs(value); if (abs !== 0 && (abs >= 1e15 || abs < 1e-6)) { return value.toExponential(8); } const formatter = new Intl.NumberFormat("en-US", { maximumFractionDigits: fractionDigits }); return formatter.format(value); } function formatTheoryList(values) { if (!Array.isArray(values) || !values.length) { return "--"; } return values.join(", "); } function parseTheoryInput(rawInput) { const text = String(rawInput ?? "").trim(); if (!text) { return { ok: false, message: "Enter a number to analyze." }; } const normalized = text.replace(/[,_\s]/g, ""); const value = Number(normalized); if (!Number.isFinite(value)) { return { ok: false, message: "Could not parse that value as a finite number." }; } return { ok: true, raw: text, normalized, value }; } function gcd(a, b) { let x = Math.abs(Math.trunc(a)); let y = Math.abs(Math.trunc(b)); while (y !== 0) { const next = x % y; x = y; y = next; } return x; } function getDigitStats(integerValue) { const absInt = Math.abs(integerValue); const digits = String(absInt); const digitArray = digits.split("").map((digit) => Number(digit)); const digitSum = digitArray.reduce((sum, digit) => sum + digit, 0); let additivePersistence = 0; let additiveCurrent = absInt; while (additiveCurrent >= 10) { additiveCurrent = String(additiveCurrent) .split("") .reduce((sum, digit) => sum + Number(digit), 0); additivePersistence += 1; } let multiplicativePersistence = 0; let multiplicativeCurrent = absInt; while (multiplicativeCurrent >= 10) { multiplicativeCurrent = String(multiplicativeCurrent) .split("") .reduce((product, digit) => product * Number(digit), 1); multiplicativePersistence += 1; if (multiplicativePersistence > 20) { break; } } return { digits, digitCount: digits.length, digitSum, digitalRoot: additiveCurrent, additivePersistence, multiplicativePersistence }; } function factorizeInteger(absInteger) { if (!Number.isSafeInteger(absInteger) || absInteger < 2) { return []; } let remainder = absInteger; const factors = []; let exponent = 0; while (remainder % 2 === 0) { remainder /= 2; exponent += 1; } if (exponent > 0) { factors.push({ prime: 2, exponent }); } let divisor = 3; while (divisor * divisor <= remainder) { exponent = 0; while (remainder % divisor === 0) { remainder /= divisor; exponent += 1; } if (exponent > 0) { factors.push({ prime: divisor, exponent }); } divisor += 2; } if (remainder > 1) { factors.push({ prime: remainder, exponent: 1 }); } return factors; } function createDivisorsFromFactors(factors) { if (!Array.isArray(factors) || !factors.length) { return [1]; } let divisors = [1]; factors.forEach(({ prime, exponent }) => { const next = []; divisors.forEach((existing) => { let multiplier = 1; for (let power = 0; power <= exponent; power += 1) { next.push(existing * multiplier); multiplier *= prime; } }); divisors = next; }); return divisors.sort((left, right) => left - right); } function getRomanNumeral(value) { const integer = Math.trunc(value); if (!Number.isFinite(integer) || integer < 1 || integer > 3999) { return "--"; } const symbols = [ [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 remainder = integer; let output = ""; symbols.forEach(([weight, glyph]) => { while (remainder >= weight) { remainder -= weight; output += glyph; } }); return output; } function isPerfectSquare(value) { if (!Number.isSafeInteger(value) || value < 0) { return false; } const root = Math.trunc(Math.sqrt(value)); return root * root === value; } function isPerfectCube(value) { if (!Number.isSafeInteger(value)) { return false; } const root = Math.round(Math.cbrt(value)); return root * root * root === value; } function fibonacciMembership(value) { if (!Number.isSafeInteger(value) || value < 0) { return false; } const candidateA = 5 * value * value + 4; const candidateB = 5 * value * value - 4; return isPerfectSquare(candidateA) || isPerfectSquare(candidateB); } function factorialMembership(value) { if (!Number.isSafeInteger(value) || value < 1) { return null; } let current = 1; let index = 1; while (current < value && current <= Number.MAX_SAFE_INTEGER) { index += 1; current *= index; } return current === value ? index : null; } function collatzStats(value) { if (!Number.isSafeInteger(value) || value <= 0 || value > THEORY_MAX_COLLATZ_START) { return { available: false, reason: `Available for positive integers up to ${formatTheoryNumber(THEORY_MAX_COLLATZ_START, 0)}.` }; } let current = value; let steps = 0; let peak = value; while (current !== 1 && steps < 20000) { if (current % 2 === 0) { current /= 2; } else { current = (3 * current) + 1; } if (current > peak) { peak = current; } steps += 1; } return { available: true, steps, peak, completed: current === 1 }; } function happyNumberStats(value) { if (!Number.isSafeInteger(value) || value <= 0) { return { available: false, isHappy: false }; } const seen = new Set(); let current = value; while (current !== 1 && !seen.has(current)) { seen.add(current); current = String(current) .split("") .reduce((sum, digit) => { const numberDigit = Number(digit); return sum + (numberDigit * numberDigit); }, 0); } return { available: true, isHappy: current === 1 }; } function getIntegerProperties(integerValue) { const absInteger = Math.abs(integerValue); const digitStats = getDigitStats(integerValue); const canFactorExactly = absInteger <= THEORY_MAX_FACTORIZATION_ABS; const factors = canFactorExactly ? factorizeInteger(absInteger) : []; let divisorCount = null; let sigma = null; let properDivisorSum = null; let divisors = null; let eulerPhi = null; let mobius = null; let liouville = null; let isPrime = false; let isComposite = false; let semiprime = false; if (canFactorExactly && absInteger >= 1) { divisorCount = factors.reduce((product, item) => product * (item.exponent + 1), 1); sigma = factors.reduce((product, item) => { const numerator = (item.prime ** (item.exponent + 1)) - 1; return product * (numerator / (item.prime - 1)); }, 1); properDivisorSum = absInteger === 1 ? 0 : sigma - absInteger; eulerPhi = absInteger === 1 ? 1 : factors.reduce((product, item) => product * ((item.prime - 1) * (item.prime ** (item.exponent - 1))), 1); const hasSquareFactor = factors.some((item) => item.exponent > 1); mobius = absInteger === 1 ? 1 : (hasSquareFactor ? 0 : (factors.length % 2 === 0 ? 1 : -1)); liouville = absInteger === 1 ? 1 : (factors.reduce((sum, item) => sum + item.exponent, 0) % 2 === 0 ? 1 : -1); isPrime = absInteger > 1 && factors.length === 1 && factors[0].exponent === 1; isComposite = absInteger > 1 && !isPrime; semiprime = absInteger > 1 && factors.reduce((sum, item) => sum + item.exponent, 0) === 2; if (divisorCount <= 256) { divisors = createDivisorsFromFactors(factors); } } const abundanceClass = properDivisorSum == null ? "Unknown" : (properDivisorSum === absInteger ? "Perfect" : (properDivisorSum > absInteger ? "Abundant" : "Deficient")); const triangularIndex = absInteger >= 0 ? ((Math.sqrt((8 * absInteger) + 1) - 1) / 2) : null; const pentagonalIndex = absInteger >= 0 ? ((1 + Math.sqrt((24 * absInteger) + 1)) / 6) : null; const hexagonalIndex = absInteger >= 0 ? ((1 + Math.sqrt((8 * absInteger) + 1)) / 4) : null; const factorialIndex = factorialMembership(absInteger); const collatz = collatzStats(absInteger); const happy = happyNumberStats(absInteger); const residueMods = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] .map((modulus) => `mod ${modulus}: ${((integerValue % modulus) + modulus) % modulus}`); const divisibility = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] .map((modulus) => `${modulus}:${integerValue % modulus === 0 ? "Y" : "N"}`); const parity = integerValue % 2 === 0 ? "Even" : "Odd"; const powerOfTwo = absInteger > 0 ? Number.isInteger(Math.log2(absInteger)) : false; const powerOfTen = absInteger > 0 ? Number.isInteger(Math.log10(absInteger)) : false; const binary = absInteger <= Number.MAX_SAFE_INTEGER ? integerValue.toString(2) : "--"; const octal = absInteger <= Number.MAX_SAFE_INTEGER ? integerValue.toString(8) : "--"; const hex = absInteger <= Number.MAX_SAFE_INTEGER ? integerValue.toString(16).toUpperCase() : "--"; const decimalText = String(absInteger); const isPalindrome = decimalText === decimalText.split("").reverse().join(""); const isHarshad = digitStats.digitSum > 0 ? absInteger % digitStats.digitSum === 0 : false; const armstrongPower = digitStats.digitCount; const armstrongSum = decimalText .split("") .reduce((sum, digit) => sum + (Number(digit) ** armstrongPower), 0); const isArmstrong = armstrongSum === absInteger; const theoryRows = { integerMeta: [ ["Parity", parity], ["Safe integer", Number.isSafeInteger(integerValue) ? "Yes" : "No"], ["GCD(n, 360)", formatTheoryNumber(gcd(integerValue, 360), 0)], ["Power of 2", powerOfTwo ? "Yes" : "No"], ["Power of 10", powerOfTen ? "Yes" : "No"] ], digits: [ ["Digits", digitStats.digits], ["Digit count", formatTheoryNumber(digitStats.digitCount, 0)], ["Digit sum", formatTheoryNumber(digitStats.digitSum, 0)], ["Digital root", formatTheoryNumber(digitStats.digitalRoot, 0)], ["Additive persistence", formatTheoryNumber(digitStats.additivePersistence, 0)], ["Multiplicative persistence", formatTheoryNumber(digitStats.multiplicativePersistence, 0)] ], numberTheory: [ ["Prime", isPrime ? "Yes" : "No"], ["Composite", isComposite ? "Yes" : "No"], ["Semiprime", semiprime ? "Yes" : "No"], ["Prime factorization", factors.length ? factors.map((item) => `${item.prime}^${item.exponent}`).join(" x ") : (absInteger < 2 ? "--" : "Unavailable")], ["Divisor count (tau)", divisorCount == null ? "Unavailable" : formatTheoryNumber(divisorCount, 0)], ["Sum of divisors (sigma)", sigma == null ? "Unavailable" : formatTheoryNumber(sigma, 0)], ["Proper divisor sum", properDivisorSum == null ? "Unavailable" : formatTheoryNumber(properDivisorSum, 0)], ["Abundance class", abundanceClass], ["Euler totient phi(n)", eulerPhi == null ? "Unavailable" : formatTheoryNumber(eulerPhi, 0)], ["Mobius mu(n)", mobius == null ? "Unavailable" : formatTheoryNumber(mobius, 0)], ["Liouville lambda(n)", liouville == null ? "Unavailable" : formatTheoryNumber(liouville, 0)] ], sequences: [ ["Triangular", Number.isInteger(triangularIndex) ? `Yes (T_${formatTheoryNumber(triangularIndex, 0)})` : "No"], ["Square", isPerfectSquare(absInteger) ? "Yes" : "No"], ["Cube", isPerfectCube(integerValue) ? "Yes" : "No"], ["Pentagonal", Number.isInteger(pentagonalIndex) ? `Yes (P_${formatTheoryNumber(pentagonalIndex, 0)})` : "No"], ["Hexagonal", Number.isInteger(hexagonalIndex) ? `Yes (H_${formatTheoryNumber(hexagonalIndex, 0)})` : "No"], ["Fibonacci", fibonacciMembership(absInteger) ? "Yes" : "No"], ["Factorial", factorialIndex ? `Yes (${formatTheoryNumber(factorialIndex, 0)}!)` : "No"] ], digitTraits: [ ["Palindrome (base 10)", isPalindrome ? "Yes" : "No"], ["Harshad", isHarshad ? "Yes" : "No"], ["Armstrong/Narcissistic", isArmstrong ? "Yes" : "No"], ["Happy", happy.available ? (happy.isHappy ? "Yes" : "No") : "Unavailable"] ], modular: [ ["Divisibility (2..12)", formatTheoryList(divisibility)], ["Residues", formatTheoryList(residueMods)] ], representations: [ ["Binary", binary], ["Octal", octal], ["Hex", hex], ["Roman numeral", getRomanNumeral(absInteger)] ], dynamic: [ ["Collatz", collatz.available ? `${formatTheoryNumber(collatz.steps, 0)} steps, peak ${formatTheoryNumber(collatz.peak, 0)}${collatz.completed ? "" : " (stopped early)"}` : collatz.reason], ["Divisors", divisors ? formatTheoryList(divisors.map((item) => formatTheoryNumber(item, 0))) : (canFactorExactly ? "List omitted (over 256 divisors)." : `Unavailable above |n| > ${formatTheoryNumber(THEORY_MAX_FACTORIZATION_ABS, 0)}.`)] ] }; return { absInteger, canFactorExactly, rows: theoryRows }; } function buildTheorySection(title, rows) { const sectionEl = document.createElement("section"); sectionEl.className = "numbers-theory-section"; const headingEl = document.createElement("h4"); headingEl.className = "numbers-theory-section-heading"; headingEl.textContent = title; const bodyEl = document.createElement("div"); bodyEl.className = "numbers-theory-grid"; rows.forEach((row) => { if (!Array.isArray(row) || row.length < 2) { return; } const labelEl = document.createElement("div"); labelEl.className = "numbers-theory-label"; labelEl.textContent = String(row[0]); const valueEl = document.createElement("div"); valueEl.className = "numbers-theory-value"; valueEl.textContent = String(row[1]); bodyEl.append(labelEl, valueEl); }); sectionEl.append(headingEl, bodyEl); return sectionEl; } function buildTheoryAnalysisView(rawInput) { const parsed = parseTheoryInput(rawInput); const fragment = document.createDocumentFragment(); if (!parsed.ok) { const messageEl = document.createElement("div"); messageEl.className = "numbers-detail-text numbers-detail-text--muted"; messageEl.textContent = parsed.message; fragment.appendChild(messageEl); return fragment; } const { value } = parsed; const abs = Math.abs(value); const isInteger = Number.isInteger(value); const reciprocal = value !== 0 ? (1 / value) : null; const integerPart = Math.trunc(value); const fractionalPart = value - integerPart; fragment.appendChild(buildTheorySection("General", [ ["Input", parsed.raw], ["Normalized", formatTheoryNumber(value)], ["Sign", value > 0 ? "Positive" : (value < 0 ? "Negative" : "Zero")], ["Absolute value", formatTheoryNumber(abs)], ["Integer", isInteger ? "Yes" : "No"], ["Whole number", isInteger && value >= 0 ? "Yes" : "No"], ["Natural number", isInteger && value >= 1 ? "Yes" : "No"], ["Opposite", formatTheoryNumber(-value)], ["Reciprocal", reciprocal == null ? "Undefined" : formatTheoryNumber(reciprocal)], ["Floor", formatTheoryNumber(Math.floor(value), 0)], ["Ceiling", formatTheoryNumber(Math.ceil(value), 0)], ["Truncated", formatTheoryNumber(integerPart, 0)], ["Fractional part", formatTheoryNumber(fractionalPart)], ["Scientific notation", value.toExponential(8)] ])); if (!isInteger) { fragment.appendChild(buildTheorySection("Non-integer notes", [ ["Detail", "Integer-only number theory metrics are shown for integers. Enter a whole number for full analysis."] ])); return fragment; } if (!Number.isSafeInteger(value)) { fragment.appendChild(buildTheorySection("Safe integer limit", [ ["Detail", "This value is outside JavaScript safe integer precision. Exact factor/divisor properties are skipped."] ])); return fragment; } const integerProperties = getIntegerProperties(value); fragment.appendChild(buildTheorySection("Integer metadata", integerProperties.rows.integerMeta)); fragment.appendChild(buildTheorySection("Digit analysis", integerProperties.rows.digits)); fragment.appendChild(buildTheorySection("Number theory", integerProperties.rows.numberTheory)); fragment.appendChild(buildTheorySection("Sequence membership", integerProperties.rows.sequences)); fragment.appendChild(buildTheorySection("Digit traits", integerProperties.rows.digitTraits)); fragment.appendChild(buildTheorySection("Modular arithmetic", integerProperties.rows.modular)); fragment.appendChild(buildTheorySection("Representations", integerProperties.rows.representations)); fragment.appendChild(buildTheorySection("Dynamic behavior", integerProperties.rows.dynamic)); return fragment; } function buildTheoryCard(selectedNumberValue) { const cardEl = document.createElement("div"); cardEl.className = "numbers-detail-card numbers-theory-card"; const headingEl = document.createElement("strong"); headingEl.textContent = "Theory"; const introEl = document.createElement("div"); introEl.className = "numbers-detail-text numbers-detail-text--muted"; introEl.textContent = "Enter any number to inspect deep number theory and arithmetic properties."; const controlsEl = document.createElement("div"); controlsEl.className = "numbers-theory-controls"; const inputEl = document.createElement("input"); inputEl.type = "text"; inputEl.inputMode = "decimal"; inputEl.className = "numbers-theory-input"; inputEl.placeholder = "e.g. 108, 137, -42, 12.5"; inputEl.value = theoryState.inputValue || ""; inputEl.setAttribute("aria-label", "Number to analyze"); const analyzeBtn = document.createElement("button"); analyzeBtn.type = "button"; analyzeBtn.className = "numbers-nav-btn"; analyzeBtn.textContent = "Analyze"; const selectedBtn = document.createElement("button"); selectedBtn.type = "button"; selectedBtn.className = "numbers-nav-btn"; selectedBtn.textContent = `Use Selected Number (${selectedNumberValue})`; controlsEl.append(inputEl, analyzeBtn, selectedBtn); const outputEl = document.createElement("div"); outputEl.className = "numbers-theory-output"; const renderOutput = () => { theoryState.inputValue = inputEl.value; const content = buildTheoryAnalysisView(inputEl.value); outputEl.replaceChildren(content); }; analyzeBtn.addEventListener("click", renderOutput); inputEl.addEventListener("keydown", (event) => { if (event.key === "Enter") { event.preventDefault(); renderOutput(); } }); selectedBtn.addEventListener("click", () => { inputEl.value = String(selectedNumberValue); renderOutput(); }); renderOutput(); cardEl.append(headingEl, introEl, controlsEl, outputEl); return cardEl; } function hasTarotAccess() { return window.TarotAppConfig?.hasTarotAccess?.() === true; } function getCalendarMonthLinksForNumber(context, value) { const { getReferenceData, normalizeNumberValue, computeDigitalRoot } = context; const referenceData = getReferenceData(); const normalized = normalizeNumberValue(value); const calendarGroups = [ { calendarId: "gregorian", calendarLabel: "Gregorian", months: Array.isArray(referenceData?.calendarMonths) ? referenceData.calendarMonths : [] }, { calendarId: "hebrew", calendarLabel: "Hebrew", months: Array.isArray(referenceData?.hebrewCalendar?.months) ? referenceData.hebrewCalendar.months : [] }, { calendarId: "islamic", calendarLabel: "Islamic", months: Array.isArray(referenceData?.islamicCalendar?.months) ? referenceData.islamicCalendar.months : [] }, { calendarId: "wheel-of-year", calendarLabel: "Wheel of the Year", months: Array.isArray(referenceData?.wheelOfYear?.months) ? referenceData.wheelOfYear.months : [] } ]; const links = []; calendarGroups.forEach((group) => { group.months.forEach((month) => { const monthOrder = Number(month?.order); const normalizedOrder = Number.isFinite(monthOrder) ? Math.trunc(monthOrder) : null; const monthRoot = normalizedOrder != null ? computeDigitalRoot(normalizedOrder) : null; if (monthRoot !== normalized) { return; } links.push({ calendarId: group.calendarId, calendarLabel: group.calendarLabel, monthId: String(month.id || "").trim(), monthName: String(month.name || month.id || "Month").trim(), monthOrder: normalizedOrder }); }); }); return links.filter((link) => link.monthId); } function buildFallbackPlayingDeckEntries(context) { const { PLAYING_SUIT_SYMBOL, PLAYING_SUIT_LABEL, PLAYING_SUIT_TO_TAROT, PLAYING_RANKS, rankLabelToTarotMinorRank } = context; const entries = []; Object.keys(PLAYING_SUIT_SYMBOL).forEach((suit) => { PLAYING_RANKS.forEach((rank) => { const tarotSuit = PLAYING_SUIT_TO_TAROT[suit]; const tarotRank = rankLabelToTarotMinorRank(rank.rankLabel); entries.push({ id: `${rank.rank}${PLAYING_SUIT_SYMBOL[suit]}`, suit, suitLabel: PLAYING_SUIT_LABEL[suit], suitSymbol: PLAYING_SUIT_SYMBOL[suit], rank: rank.rank, rankLabel: rank.rankLabel, rankValue: rank.rankValue, tarotSuit, tarotCard: `${tarotRank} of ${tarotSuit}` }); }); }); return entries; } function getPlayingDeckEntries(context) { const { getMagickDataset, PLAYING_SUIT_SYMBOL, PLAYING_SUIT_LABEL, PLAYING_SUIT_TO_TAROT, rankLabelToTarotMinorRank } = context; const deckData = getMagickDataset()?.grouped?.["playing-cards-52"]; const rawEntries = Array.isArray(deckData) ? deckData : (Array.isArray(deckData?.entries) ? deckData.entries : []); if (!rawEntries.length) { return buildFallbackPlayingDeckEntries(context); } return rawEntries .map((entry) => { const suit = String(entry?.suit || "").trim().toLowerCase(); const rankLabel = String(entry?.rankLabel || "").trim(); const rank = String(entry?.rank || "").trim(); if (!suit || !rank) { return null; } const suitSymbol = String(entry?.suitSymbol || PLAYING_SUIT_SYMBOL[suit] || "").trim(); const tarotSuit = String(entry?.tarotSuit || PLAYING_SUIT_TO_TAROT[suit] || "").trim(); const tarotCard = String(entry?.tarotCard || "").trim(); const rankValueRaw = Number(entry?.rankValue); const rankValue = Number.isFinite(rankValueRaw) ? Math.trunc(rankValueRaw) : null; return { id: String(entry?.id || `${rank}${suitSymbol}`).trim(), suit, suitLabel: String(entry?.suitLabel || PLAYING_SUIT_LABEL[suit] || suit).trim(), suitSymbol, rank, rankLabel: rankLabel || rank, rankValue, tarotSuit, tarotCard: tarotCard || `${rankLabelToTarotMinorRank(rankLabel || rank)} of ${tarotSuit}` }; }) .filter(Boolean); } function findPlayingCardBySuitAndValue(entries, suit, value) { const normalizedSuit = String(suit || "").trim().toLowerCase(); const targetValue = Number(value); return entries.find((entry) => entry.suit === normalizedSuit && Number(entry.rankValue) === targetValue) || null; } function buildNumbersSpecialCardSlots(context, playingSuit) { const { PLAYING_SUIT_LABEL, PLAYING_SUIT_TO_TAROT, NUMBERS_SPECIAL_BASE_VALUES, numbersSpecialFlipState } = context; const suit = String(playingSuit || "hearts").trim().toLowerCase(); const selectedSuit = ["hearts", "diamonds", "clubs", "spades"].includes(suit) ? suit : "hearts"; const deckEntries = getPlayingDeckEntries(context); const cardEl = document.createElement("div"); cardEl.className = "numbers-detail-card numbers-special-card-section"; const headingEl = document.createElement("strong"); headingEl.textContent = "4 Card Arrangement"; const subEl = document.createElement("div"); subEl.className = "numbers-detail-text numbers-detail-text--muted"; subEl.textContent = `Click a card to flip to its opposite (${PLAYING_SUIT_LABEL[selectedSuit]} ↔ ${PLAYING_SUIT_TO_TAROT[selectedSuit]}).`; const boardEl = document.createElement("div"); boardEl.className = "numbers-special-board"; NUMBERS_SPECIAL_BASE_VALUES.forEach((baseValue) => { const oppositeValue = 9 - baseValue; const frontCard = findPlayingCardBySuitAndValue(deckEntries, selectedSuit, baseValue); const backCard = findPlayingCardBySuitAndValue(deckEntries, selectedSuit, oppositeValue); if (!frontCard || !backCard) { return; } const slotKey = `${selectedSuit}:${baseValue}`; const isFlipped = Boolean(numbersSpecialFlipState.get(slotKey)); const faceBtn = document.createElement("button"); faceBtn.type = "button"; faceBtn.className = `numbers-special-card${isFlipped ? " is-flipped" : ""}`; faceBtn.setAttribute("aria-pressed", isFlipped ? "true" : "false"); faceBtn.setAttribute("aria-label", `${frontCard.rankLabel} of ${frontCard.suitLabel}. Click to flip to ${backCard.rankLabel}.`); faceBtn.dataset.suit = selectedSuit; const innerEl = document.createElement("div"); innerEl.className = "numbers-special-card-inner"; const frontFaceEl = document.createElement("div"); frontFaceEl.className = "numbers-special-card-face numbers-special-card-face--front"; const frontRankEl = document.createElement("div"); frontRankEl.className = "numbers-special-card-rank"; frontRankEl.textContent = frontCard.rankLabel; const frontSuitEl = document.createElement("div"); frontSuitEl.className = "numbers-special-card-suit"; frontSuitEl.textContent = frontCard.suitSymbol; const frontMetaEl = document.createElement("div"); frontMetaEl.className = "numbers-special-card-meta"; frontMetaEl.textContent = frontCard.tarotCard; frontFaceEl.append(frontRankEl, frontSuitEl, frontMetaEl); const backFaceEl = document.createElement("div"); backFaceEl.className = "numbers-special-card-face numbers-special-card-face--back"; const backTagEl = document.createElement("div"); backTagEl.className = "numbers-special-card-tag"; backTagEl.textContent = "Opposite"; const backRankEl = document.createElement("div"); backRankEl.className = "numbers-special-card-rank"; backRankEl.textContent = backCard.rankLabel; const backSuitEl = document.createElement("div"); backSuitEl.className = "numbers-special-card-suit"; backSuitEl.textContent = backCard.suitSymbol; const backMetaEl = document.createElement("div"); backMetaEl.className = "numbers-special-card-meta"; backMetaEl.textContent = backCard.tarotCard; backFaceEl.append(backTagEl, backRankEl, backSuitEl, backMetaEl); innerEl.append(frontFaceEl, backFaceEl); faceBtn.append(innerEl); faceBtn.addEventListener("click", () => { const next = !Boolean(numbersSpecialFlipState.get(slotKey)); numbersSpecialFlipState.set(slotKey, next); faceBtn.classList.toggle("is-flipped", next); faceBtn.setAttribute("aria-pressed", next ? "true" : "false"); }); boardEl.appendChild(faceBtn); }); if (!boardEl.childElementCount) { const emptyEl = document.createElement("div"); emptyEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyEl.textContent = "No card slots available for this mapping yet."; boardEl.appendChild(emptyEl); } cardEl.append(headingEl, subEl, boardEl); return cardEl; } function renderNumbersSpecialPanel(context, value, entry) { const { specialPanelEl } = context.elements; if (!specialPanelEl) { return; } const isTheoryView = context.activeNumbersView === "theory"; specialPanelEl.hidden = isTheoryView; if (isTheoryView) { specialPanelEl.replaceChildren(); return; } const playingSuit = entry?.associations?.playingSuit || "hearts"; const boardCardEl = buildNumbersSpecialCardSlots(context, playingSuit); specialPanelEl.replaceChildren(boardCardEl); } function parseTarotCardNumber(rawValue) { if (typeof rawValue === "number") { return Number.isFinite(rawValue) ? Math.trunc(rawValue) : null; } if (typeof rawValue === "string") { const trimmed = rawValue.trim(); if (!trimmed || !/^-?\d+$/.test(trimmed)) { return null; } return Number(trimmed); } return null; } function extractTarotCardNumericValue(context, card) { const { TAROT_RANK_NUMBER_MAP } = context; const directNumber = parseTarotCardNumber(card?.number); if (directNumber !== null) { return directNumber; } const rankKey = String(card?.rank || "").trim().toLowerCase(); if (Object.prototype.hasOwnProperty.call(TAROT_RANK_NUMBER_MAP, rankKey)) { return TAROT_RANK_NUMBER_MAP[rankKey]; } const numerologyRelation = Array.isArray(card?.relations) ? card.relations.find((relation) => String(relation?.type || "").trim().toLowerCase() === "numerology") : null; const relationValue = Number(numerologyRelation?.data?.value); if (Number.isFinite(relationValue)) { return Math.trunc(relationValue); } return null; } function getAlphabetPositionLinksForDigitalRoot(context, targetRoot) { const { getMagickDataset, computeDigitalRoot } = context; const alphabets = getMagickDataset()?.grouped?.alphabets; if (!alphabets || typeof alphabets !== "object") { return []; } const links = []; const addLink = (alphabetLabel, entry, buttonLabel, detail) => { const index = Number(entry?.index); if (!Number.isFinite(index)) { return; } const normalizedIndex = Math.trunc(index); if (computeDigitalRoot(normalizedIndex) !== targetRoot) { return; } links.push({ alphabet: alphabetLabel, index: normalizedIndex, label: buttonLabel, detail }); }; const toTitle = (value) => String(value || "") .trim() .replace(/[_-]+/g, " ") .replace(/\s+/g, " ") .toLowerCase() .replace(/\b([a-z])/g, (match, ch) => ch.toUpperCase()); const englishEntries = Array.isArray(alphabets.english) ? alphabets.english : []; englishEntries.forEach((entry) => { const letter = String(entry?.letter || "").trim(); if (!letter) { return; } addLink("English", entry, `${letter}`, { alphabet: "english", englishLetter: letter }); }); const greekEntries = Array.isArray(alphabets.greek) ? alphabets.greek : []; greekEntries.forEach((entry) => { const greekName = String(entry?.name || "").trim(); if (!greekName) { return; } const glyph = String(entry?.char || "").trim(); const displayName = String(entry?.displayName || toTitle(greekName)).trim(); addLink("Greek", entry, glyph ? `${displayName} - ${glyph}` : displayName, { alphabet: "greek", greekName }); }); const hebrewEntries = Array.isArray(alphabets.hebrew) ? alphabets.hebrew : []; hebrewEntries.forEach((entry) => { const hebrewLetterId = String(entry?.hebrewLetterId || "").trim(); if (!hebrewLetterId) { return; } const glyph = String(entry?.char || "").trim(); const name = String(entry?.name || hebrewLetterId).trim(); const displayName = toTitle(name); addLink("Hebrew", entry, glyph ? `${displayName} - ${glyph}` : displayName, { alphabet: "hebrew", hebrewLetterId }); }); const arabicEntries = Array.isArray(alphabets.arabic) ? alphabets.arabic : []; arabicEntries.forEach((entry) => { const arabicName = String(entry?.name || "").trim(); if (!arabicName) { return; } const glyph = String(entry?.char || "").trim(); const displayName = toTitle(arabicName); addLink("Arabic", entry, glyph ? `${displayName} - ${glyph}` : displayName, { alphabet: "arabic", arabicName }); }); const enochianEntries = Array.isArray(alphabets.enochian) ? alphabets.enochian : []; enochianEntries.forEach((entry) => { const enochianId = String(entry?.id || "").trim(); if (!enochianId) { return; } const title = String(entry?.title || enochianId).trim(); const displayName = toTitle(title); addLink("Enochian", entry, `${displayName}`, { alphabet: "enochian", enochianId }); }); return links.sort((left, right) => { if (left.index !== right.index) { return left.index - right.index; } const alphabetCompare = left.alphabet.localeCompare(right.alphabet); if (alphabetCompare !== 0) { return alphabetCompare; } return left.label.localeCompare(right.label); }); } function getTarotCardsForDigitalRoot(context, targetRoot, numberEntry = null) { if (!hasTarotAccess()) { return []; } const { getReferenceData, getMagickDataset, ensureTarotSection, computeDigitalRoot } = context; const referenceData = getReferenceData(); const magickDataset = getMagickDataset(); if (typeof ensureTarotSection === "function" && referenceData) { ensureTarotSection(referenceData, magickDataset); } const allCards = window.TarotSectionUi?.getCards?.() || []; const explicitTrumpNumbers = Array.isArray(numberEntry?.associations?.tarotTrumpNumbers) ? numberEntry.associations.tarotTrumpNumbers .map((value) => Number(value)) .filter((value) => Number.isFinite(value)) .map((value) => Math.trunc(value)) : []; const filteredCards = explicitTrumpNumbers.length ? allCards.filter((card) => { const numberValue = parseTarotCardNumber(card?.number); return card?.arcana === "Major" && numberValue !== null && explicitTrumpNumbers.includes(numberValue); }) : allCards.filter((card) => { const numberValue = extractTarotCardNumericValue(context, card); return numberValue !== null && computeDigitalRoot(numberValue) === targetRoot; }); return filteredCards.sort((left, right) => { const leftNumber = extractTarotCardNumericValue(context, left); const rightNumber = extractTarotCardNumericValue(context, right); if (leftNumber !== rightNumber) { return (leftNumber ?? 0) - (rightNumber ?? 0); } if (left?.arcana !== right?.arcana) { return left?.arcana === "Major" ? -1 : 1; } return String(left?.name || "").localeCompare(String(right?.name || "")); }); } function renderNumberDetail(context) { const { elements, getNumberEntryByValue, normalizeNumberValue, selectNumberEntry } = context; const { detailNameEl, detailTypeEl, detailSummaryEl, detailBodyEl } = elements; const entry = getNumberEntryByValue(context.value); if (!entry) { return; } const isTheoryView = context.activeNumbersView === "theory"; const detailHeadingEl = detailNameEl instanceof HTMLElement ? detailNameEl.closest(".planet-detail-heading") : null; const sequenceNavEl = detailHeadingEl instanceof HTMLElement ? detailHeadingEl.querySelector(".detail-sequence-nav") : null; if (sequenceNavEl instanceof HTMLElement) { sequenceNavEl.hidden = isTheoryView; } const normalized = entry.value; const opposite = entry.opposite; const rootTarget = normalizeNumberValue(entry.digitalRoot); if (detailNameEl) { detailNameEl.textContent = `Number ${normalized} · ${entry.label}`; } if (detailTypeEl) { detailTypeEl.textContent = isTheoryView ? "Theory Analyzer" : `Opposite: ${opposite}`; } if (detailSummaryEl) { detailSummaryEl.textContent = isTheoryView ? "Analyze any value with an extensive set of arithmetic and number theory properties." : (entry.summary || ""); } renderNumbersSpecialPanel(context, normalized, entry); if (!detailBodyEl) { return; } detailBodyEl.replaceChildren(); const theoryCardEl = buildTheoryCard(normalized); if (isTheoryView) { detailBodyEl.append(theoryCardEl); return; } const pairCardEl = document.createElement("div"); pairCardEl.className = "numbers-detail-card"; const pairHeadingEl = document.createElement("strong"); pairHeadingEl.textContent = "Number Pair"; const pairTextEl = document.createElement("div"); pairTextEl.className = "numbers-detail-text"; pairTextEl.textContent = `Opposite: ${opposite}`; const keywordText = entry.keywords.length ? `Keywords: ${entry.keywords.join(", ")}` : "Keywords: --"; const pairKeywordsEl = document.createElement("div"); pairKeywordsEl.className = "numbers-detail-text numbers-detail-text--muted"; pairKeywordsEl.textContent = keywordText; const oppositeBtn = document.createElement("button"); oppositeBtn.type = "button"; oppositeBtn.className = "numbers-nav-btn"; oppositeBtn.textContent = `Open Opposite Number ${opposite}`; oppositeBtn.addEventListener("click", () => { selectNumberEntry(opposite); }); pairCardEl.append(pairHeadingEl, pairTextEl, pairKeywordsEl, oppositeBtn); const kabbalahCardEl = document.createElement("div"); kabbalahCardEl.className = "numbers-detail-card"; const kabbalahHeadingEl = document.createElement("strong"); kabbalahHeadingEl.textContent = "Kabbalah Link"; const kabbalahNode = Number(entry?.associations?.kabbalahNode); const kabbalahTextEl = document.createElement("div"); kabbalahTextEl.className = "numbers-detail-text"; kabbalahTextEl.textContent = `Tree node target: ${kabbalahNode}`; const kabbalahBtn = document.createElement("button"); kabbalahBtn.type = "button"; kabbalahBtn.className = "numbers-nav-btn"; kabbalahBtn.textContent = `Open Kabbalah Tree Node ${kabbalahNode}`; kabbalahBtn.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:kabbalah-path", { detail: { pathNo: kabbalahNode } })); }); kabbalahCardEl.append(kabbalahHeadingEl, kabbalahTextEl, kabbalahBtn); const alphabetCardEl = document.createElement("div"); alphabetCardEl.className = "numbers-detail-card"; const alphabetHeadingEl = document.createElement("strong"); alphabetHeadingEl.textContent = "Alphabet Links"; const alphabetLinksWrapEl = document.createElement("div"); alphabetLinksWrapEl.className = "numbers-links-wrap"; const alphabetLinks = getAlphabetPositionLinksForDigitalRoot(context, rootTarget); if (!alphabetLinks.length) { const emptyAlphabetEl = document.createElement("div"); emptyAlphabetEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyAlphabetEl.textContent = "No alphabet position entries found for this digital root yet."; alphabetLinksWrapEl.appendChild(emptyAlphabetEl); } else { alphabetLinks.forEach((link) => { const button = document.createElement("button"); button.type = "button"; button.className = "numbers-nav-btn"; button.textContent = `${link.alphabet}: ${link.label}`; button.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:alphabet", { detail: link.detail })); }); alphabetLinksWrapEl.appendChild(button); }); } alphabetCardEl.append(alphabetHeadingEl, alphabetLinksWrapEl); const calendarCardEl = document.createElement("div"); calendarCardEl.className = "numbers-detail-card"; const calendarHeadingEl = document.createElement("strong"); calendarHeadingEl.textContent = "Calendar Links"; const calendarLinksWrapEl = document.createElement("div"); calendarLinksWrapEl.className = "numbers-links-wrap"; const calendarLinks = getCalendarMonthLinksForNumber(context, normalized); if (!calendarLinks.length) { const emptyCalendarEl = document.createElement("div"); emptyCalendarEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyCalendarEl.textContent = "No calendar months currently mapped to this number."; calendarLinksWrapEl.appendChild(emptyCalendarEl); } else { calendarLinks.forEach((link) => { const button = document.createElement("button"); button.type = "button"; button.className = "numbers-nav-btn"; button.textContent = `${link.calendarLabel}: ${link.monthName} (Month ${link.monthOrder})`; button.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:calendar-month", { detail: { calendarId: link.calendarId, monthId: link.monthId } })); }); calendarLinksWrapEl.appendChild(button); }); } calendarCardEl.append(calendarHeadingEl, calendarLinksWrapEl); const detailCards = [theoryCardEl, pairCardEl, kabbalahCardEl, alphabetCardEl]; if (hasTarotAccess()) { const tarotCardEl = document.createElement("div"); tarotCardEl.className = "numbers-detail-card"; const tarotHeadingEl = document.createElement("strong"); tarotHeadingEl.textContent = "Tarot Links"; const tarotLinksWrapEl = document.createElement("div"); tarotLinksWrapEl.className = "numbers-links-wrap"; const tarotCards = getTarotCardsForDigitalRoot(context, rootTarget, entry); if (!tarotCards.length) { const emptyEl = document.createElement("div"); emptyEl.className = "numbers-detail-text numbers-detail-text--muted"; emptyEl.textContent = "No tarot numeric entries found yet for this root. Add card numbers to map them."; tarotLinksWrapEl.appendChild(emptyEl); } else { tarotCards.forEach((card) => { const button = document.createElement("button"); button.type = "button"; button.className = "numbers-nav-btn"; button.textContent = `${card.name}`; button.addEventListener("click", () => { document.dispatchEvent(new CustomEvent("nav:tarot-trump", { detail: { cardName: card.name } })); }); tarotLinksWrapEl.appendChild(button); }); } tarotCardEl.append(tarotHeadingEl, tarotLinksWrapEl); detailCards.push(tarotCardEl); } detailCards.push(calendarCardEl); detailBodyEl.append(...detailCards); } window.NumbersDetailUi = { renderNumberDetail }; })();