update app and index.html

This commit is contained in:
2026-06-01 13:15:12 -07:00
parent 254f488eca
commit e0d0f15731
9 changed files with 754 additions and 38 deletions
+382 -15
View File
@@ -215,6 +215,329 @@
.replace(/[^A-Z]/g, "");
}
const GREEK_NATIVE_NAMES = {
alpha: { classical: "ἄλφα", koine: "άλφα" },
beta: { classical: "βῆτα", koine: "βήτα" },
gamma: { classical: "γάμμα", koine: "γάμμα" },
delta: { classical: "δέλτα", koine: "δέλτα" },
epsilon: { classical: "ἒ ψιλόν", koine: "έψιλον" },
zeta: { classical: "ζῆτα", koine: "ζήτα" },
eta: { classical: "ἦτα", koine: "ήτα" },
theta: { classical: "θῆτα", koine: "θήτα" },
iota: { classical: "ἰῶτα", koine: "ιώτα" },
kappa: { classical: "κάππα", koine: "κάππα" },
lambda: { classical: "λάμβδα", koine: "λάμδα" },
mu: { classical: "μῦ", koine: "μι" },
nu: { classical: "νῦ", koine: "νι" },
xi: { classical: "ξῖ", koine: "ξι" },
omicron: { classical: "ὂ μικρόν", koine: "όμικρον" },
pi: { classical: "πῖ", koine: "πι" },
rho: { classical: "ῥῶ", koine: "ρω" },
sigma: { classical: "σῖγμα", koine: "σίγμα" },
tau: { classical: "ταῦ", koine: "ταυ" },
upsilon: { classical: "ὖ ψιλόν", koine: "ύψιλον" },
phi: { classical: "φῖ", koine: "φι" },
chi: { classical: "χῖ", koine: "χι" },
psi: { classical: "ψῖ", koine: "ψι" },
omega: { classical: "ὦ μέγα", koine: "ωμέγα" },
digamma: { classical: "δίγαμμα", koine: "δίγαμμα" },
qoppa: { classical: "κόππα", koine: "κόππα" },
sampi: { classical: "σαμπί", koine: "σαμπί" }
};
const GREEK_TRANSLITERATIONS = {
alpha: { classical: "A", koine: "A" },
beta: { classical: "B", koine: "V" },
gamma: { classical: "G", koine: "G" },
delta: { classical: "D", koine: "Th" },
epsilon: { classical: "E", koine: "E" },
zeta: { classical: "Z", koine: "Z" },
eta: { classical: "E", koine: "I" },
theta: { classical: "Th", koine: "Th" },
iota: { classical: "I", koine: "I" },
kappa: { classical: "K", koine: "K" },
lambda: { classical: "L", koine: "L" },
mu: { classical: "M", koine: "M" },
nu: { classical: "N", koine: "N" },
xi: { classical: "X", koine: "X" },
omicron: { classical: "O", koine: "O" },
pi: { classical: "P", koine: "P" },
rho: { classical: "R", koine: "R" },
sigma: { classical: "S", koine: "S" },
tau: { classical: "T", koine: "T" },
upsilon: { classical: "U/Y", koine: "I" },
phi: { classical: "Ph", koine: "F" },
chi: { classical: "Kh/Ch", koine: "Ch" },
psi: { classical: "Ps", koine: "Ps" },
omega: { classical: "O", koine: "O" },
digamma: { classical: "W", koine: "V" },
qoppa: { classical: "Q", koine: "Q" },
sampi: { classical: "Ss/Ts", koine: "Ss/Ts" }
};
const HEBREW_NATIVE_NAMES = {
alef: "אלף",
bet: "בית",
gimel: "גימל",
dalet: "דלת",
he: "הא",
vav: "וו",
zayin: "זין",
het: "חית",
tet: "טית",
yod: "יוד",
kaf: "כף",
lamed: "למד",
mem: "מם",
nun: "נון",
samekh: "סמך",
ayin: "עין",
pe: "פה",
tsadi: "צדי",
qof: "קוף",
resh: "ריש",
shin: "שין",
tav: "תו"
};
function greekNativeNamesByLetter(letter) {
const key = String(letter?.name || "").trim().toLowerCase();
const names = GREEK_NATIVE_NAMES[key];
if (!names) {
return { classical: "", koine: "" };
}
return {
classical: String(names.classical || "").trim(),
koine: String(names.koine || "").trim()
};
}
function formatGreekNativeNamesByLetter(letter) {
const names = greekNativeNamesByLetter(letter);
if (names.classical && names.koine && names.classical !== names.koine) {
return `${names.classical} / ${names.koine}`;
}
return names.classical || names.koine || "";
}
function toGreekUppercase(value) {
return String(value || "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toUpperCase();
}
function formatGreekNativeUppercaseByLetter(letter) {
const names = greekNativeNamesByLetter(letter);
const classicalName = toGreekUppercase(names.classical);
const koineName = toGreekUppercase(names.koine);
if (classicalName && koineName && classicalName !== koineName) {
return `${classicalName} / ${koineName}`;
}
return classicalName || koineName || "";
}
function greekTransliterationVariantsByLetter(letter) {
const key = String(letter?.name || "").trim().toLowerCase();
const variants = GREEK_TRANSLITERATIONS[key];
if (!variants) {
const fallback = String(letter?.transliteration || "").trim();
return {
classical: fallback,
koine: fallback
};
}
return {
classical: String(variants.classical || "").trim(),
koine: String(variants.koine || "").trim()
};
}
function formatGreekTransliterationByLetter(letter) {
const variants = greekTransliterationVariantsByLetter(letter);
if (variants.classical && variants.koine && variants.classical !== variants.koine) {
return `${variants.classical} / ${variants.koine}`;
}
return variants.classical || variants.koine || String(letter?.transliteration || "").trim();
}
function greekPlaceValueByIndex(index) {
const value = Number(index);
if (!Number.isFinite(value)) {
return null;
}
const position = Math.trunc(value);
if (position <= 0) {
return null;
}
if (position <= 9) {
return position;
}
if (position <= 18) {
return (position - 9) * 10;
}
return (position - 18) * 100;
}
function buildGreekGematriaMap(alphabets, mode = "orderly") {
const map = new Map();
const greekClassical = Array.isArray(alphabets?.greek) ? alphabets.greek : [];
const greekArchaic = Array.isArray(alphabets?.greekArchaic) ? alphabets.greekArchaic : [];
[...greekClassical, ...greekArchaic].forEach((entry) => {
const usePlaceValue = mode === "place-value";
const value = usePlaceValue && !entry?.archaic
? greekPlaceValueByIndex(entry?.index)
: Number(entry?.numerology);
if (!Number.isFinite(value)) {
return;
}
[entry?.char, entry?.charLower, entry?.charFinal].forEach((glyph) => {
const key = String(glyph || "").trim();
if (!key) {
return;
}
map.set(key, Math.trunc(value));
});
});
if (!map.has("ς") && map.has("σ")) {
map.set("ς", map.get("σ"));
}
return map;
}
function buildHebrewGematriaMap(alphabets) {
const map = new Map();
const hebrewLetters = Array.isArray(alphabets?.hebrew) ? alphabets.hebrew : [];
hebrewLetters.forEach((entry) => {
const value = Number(entry?.numerology);
const glyph = String(entry?.char || "").trim();
if (!glyph || !Number.isFinite(value)) {
return;
}
map.set(glyph, Math.trunc(value));
});
const finals = {
ך: "כ",
ם: "מ",
ן: "נ",
ף: "פ",
ץ: "צ"
};
Object.entries(finals).forEach(([finalForm, baseForm]) => {
if (!map.has(finalForm) && map.has(baseForm)) {
map.set(finalForm, map.get(baseForm));
}
});
return map;
}
function computeNameGematria(name, valueMap, options = {}) {
if (!(valueMap instanceof Map) || valueMap.size === 0) {
return null;
}
const normalizeGreek = options?.normalizeGreek === true;
const normalized = normalizeGreek
? String(name || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "")
: String(name || "");
let sum = 0;
let matched = 0;
for (const glyph of normalized) {
const key = String(glyph || "").trim();
if (!key) {
continue;
}
const value = valueMap.get(key);
if (!Number.isFinite(value)) {
continue;
}
sum += value;
matched += 1;
}
if (!matched) {
return null;
}
return sum;
}
function computeNameGematriaWithBreakdown(name, valueMap, options = {}) {
if (!(valueMap instanceof Map) || valueMap.size === 0) {
return null;
}
const normalizeGreek = options?.normalizeGreek === true;
const normalized = normalizeGreek
? String(name || "").normalize("NFD").replace(/[\u0300-\u036f]/g, "")
: String(name || "");
let total = 0;
const parts = [];
for (const glyph of normalized) {
const key = String(glyph || "").trim();
if (!key) {
continue;
}
const value = valueMap.get(key);
if (!Number.isFinite(value)) {
continue;
}
const numericValue = Math.trunc(value);
total += numericValue;
parts.push(`${key}(${numericValue})`);
}
if (!parts.length) {
return null;
}
return {
total,
breakdown: `${parts.join(" + ")} = ${total}`
};
}
function hebrewNativeNameByLetter(letter) {
const key = String(letter?.hebrewLetterId || "").trim().toLowerCase();
return HEBREW_NATIVE_NAMES[key] || "";
}
function findGreekEquivalentForHebrew(letter, greekLetters) {
const greekEquivalentKey = String(letter?.greekEquivalent || "").trim().toLowerCase();
if (greekEquivalentKey) {
return greekLetters.find((entry) => String(entry?.name || "").trim().toLowerCase() === greekEquivalentKey) || null;
}
const hebrewId = String(letter?.hebrewLetterId || "").trim().toLowerCase();
if (!hebrewId) {
return null;
}
return greekLetters.find((entry) => String(entry?.hebrewLetterId || "").trim().toLowerCase() === hebrewId) || null;
}
function extractEnglishLetterRefs(value) {
if (Array.isArray(value)) {
return [...new Set(value.map((entry) => normalizeLatinLetter(entry)).filter(Boolean))];
@@ -230,7 +553,9 @@
function renderAlphabetEquivalentCard(activeAlphabet, letter, context) {
const hebrewLetters = Array.isArray(context.alphabets?.hebrew) ? context.alphabets.hebrew : [];
const greekLetters = Array.isArray(context.alphabets?.greek) ? context.alphabets.greek : [];
const greekClassicalLetters = Array.isArray(context.alphabets?.greek) ? context.alphabets.greek : [];
const greekArchaicLetters = Array.isArray(context.alphabets?.greekArchaic) ? context.alphabets.greekArchaic : [];
const greekLetters = [...greekClassicalLetters, ...greekArchaicLetters];
const englishLetters = Array.isArray(context.alphabets?.english) ? context.alphabets.english : [];
const arabicLetters = Array.isArray(context.alphabets?.arabic) ? context.alphabets.arabic : [];
const enochianLetters = Array.isArray(context.alphabets?.enochian) ? context.alphabets.enochian : [];
@@ -259,7 +584,7 @@
if (activeAlphabet === "hebrew") {
addHebrewId(letter?.hebrewLetterId);
} else if (activeAlphabet === "greek") {
} else if (activeAlphabet === "greek" || activeAlphabet === "greekArchaic") {
addHebrewId(letter?.hebrewLetterId);
englishLetters
.filter((entry) => context.normalizeId(entry?.greekEquivalent) === context.normalizeId(letter?.name))
@@ -309,13 +634,14 @@
if (!(viaHebrew || viaEnglish)) {
return;
}
if (activeAlphabet === "greek" && key === activeGreekKey) {
const greekAlphabet = grk?.archaic ? "greekArchaic" : "greek";
if ((activeAlphabet === "greek" || activeAlphabet === "greekArchaic") && key === activeGreekKey) {
return;
}
buttons.push(`<button class="alpha-sister-btn" data-alpha="greek" data-key="${grk.name}">
buttons.push(`<button class="alpha-sister-btn" data-alpha="${greekAlphabet}" data-key="${grk.name}">
<span class="alpha-sister-glyph">${grk.char}</span>
<span class="alpha-sister-name">Greek: ${grk.displayName} (${grk.transliteration}) · isopsephy ${grk.numerology}</span>
<span class="alpha-sister-name">Greek${grk?.archaic ? " (Archaic)" : ""}: ${grk.displayName} (${formatGreekTransliterationByLetter(grk)}) · isopsephy ${grk.numerology}</span>
</button>`);
});
@@ -378,6 +704,14 @@
function renderHebrewDetail(context) {
const { letter, detailSubEl, detailBodyEl } = context;
const greekClassicalLetters = Array.isArray(context.alphabets?.greek) ? context.alphabets.greek : [];
const greekArchaicLetters = Array.isArray(context.alphabets?.greekArchaic) ? context.alphabets.greekArchaic : [];
const greekLetters = [...greekClassicalLetters, ...greekArchaicLetters];
const greekEquivalent = findGreekEquivalentForHebrew(letter, greekLetters);
const greekEquivalentNativeName = formatGreekNativeNamesByLetter(greekEquivalent);
const hebrewNativeName = hebrewNativeNameByLetter(letter);
const hebrewNameGematria = computeNameGematriaWithBreakdown(hebrewNativeName, buildHebrewGematriaMap(context.alphabets));
detailSubEl.textContent = `${letter.name}${letter.transliteration}`;
detailBodyEl.innerHTML = "";
@@ -385,8 +719,12 @@
sections.push(context.card("Letter Details", `
<dl class="alpha-dl">
<dt>Character</dt><dd>${letter.char}</dd>
<dt>Name</dt><dd>${letter.name}</dd>
<dt>Name (English)</dt><dd>${letter.name}</dd>
<dt>Name (Hebrew)</dt><dd>${hebrewNativeName || "—"}</dd>
<dt>Name Gematria (Hebrew)</dt><dd>${Number.isFinite(hebrewNameGematria?.total) ? hebrewNameGematria.total : "—"}</dd>
<dt>Name Gematria Breakdown (Hebrew)</dt><dd>${hebrewNameGematria?.breakdown || "—"}</dd>
<dt>Transliteration</dt><dd>${letter.transliteration}</dd>
<dt>Greek Equivalent</dt><dd>${greekEquivalent ? `${greekEquivalent.char} ${greekEquivalent.displayName}${greekEquivalentNativeName ? ` (${greekEquivalentNativeName})` : ""}` : "—"}</dd>
<dt>Meaning</dt><dd>${letter.meaning}</dd>
<dt>Gematria Value</dt><dd>${letter.numerology}</dd>
<dt>Letter Type</dt><dd class="alpha-badge alpha-badge--${letter.letterType}">${letter.letterType}</dd>
@@ -450,10 +788,26 @@
context.attachDetailListeners();
}
function renderGreekDetail(context) {
function renderGreekDetail(context, alphabetKey = "greek") {
const { letter, detailSubEl, detailBodyEl } = context;
const archaicBadge = letter.archaic ? ' <span class="alpha-badge alpha-badge--archaic">archaic</span>' : "";
detailSubEl.textContent = `${letter.displayName}${letter.archaic ? " (archaic)" : ""}${letter.transliteration}`;
const setLabel = alphabetKey === "greekArchaic" ? "Archaic" : "Classical/Koine";
const greekNativeNames = greekNativeNamesByLetter(letter);
const greekNativeNameCombined = formatGreekNativeNamesByLetter(letter);
const greekNativeUppercase = formatGreekNativeUppercaseByLetter(letter);
const greekClassicalUppercase = toGreekUppercase(greekNativeNames.classical);
const greekKoineUppercase = toGreekUppercase(greekNativeNames.koine);
const greekTranslit = greekTransliterationVariantsByLetter(letter);
const greekTranslitCombined = formatGreekTransliterationByLetter(letter);
const greekOrderlyMap = buildGreekGematriaMap(context.alphabets, "orderly");
const greekPlaceValueMap = buildGreekGematriaMap(context.alphabets, "place-value");
const greekClassicalNameGematriaOrderly = computeNameGematriaWithBreakdown(greekNativeNames.classical, greekOrderlyMap, { normalizeGreek: true });
const greekClassicalNameGematriaPlace = computeNameGematriaWithBreakdown(greekNativeNames.classical, greekPlaceValueMap, { normalizeGreek: true });
const greekKoineNameGematriaOrderly = computeNameGematriaWithBreakdown(greekNativeNames.koine, greekOrderlyMap, { normalizeGreek: true });
const greekKoineNameGematriaPlace = computeNameGematriaWithBreakdown(greekNativeNames.koine, greekPlaceValueMap, { normalizeGreek: true });
const orderlyLetterValue = Number.isFinite(Number(letter?.numerology)) ? Math.trunc(Number(letter.numerology)) : null;
const placeLetterValue = letter?.archaic ? orderlyLetterValue : greekPlaceValueByIndex(letter?.index);
detailSubEl.textContent = `${letter.displayName}${letter.archaic ? " (archaic)" : ""}${greekTranslitCombined} · ${setLabel}`;
detailBodyEl.innerHTML = "";
const sections = [];
@@ -465,20 +819,33 @@
<dt>Uppercase</dt><dd>${letter.char}</dd>
<dt>Lowercase</dt><dd>${letter.charLower || "—"}</dd>
${charRow}
<dt>Name</dt><dd>${letter.displayName}${archaicBadge}</dd>
<dt>Transliteration</dt><dd>${letter.transliteration}</dd>
<dt>Name (English)</dt><dd>${letter.displayName}${archaicBadge}</dd>
<dt>Name (Greek)</dt><dd>${greekNativeNameCombined || "—"}</dd>
<dt>Name (Greek Uppercase)</dt><dd>${greekNativeUppercase || "—"}</dd>
<dt>Name (Greek Classical)</dt><dd>${greekNativeNames.classical || "—"}</dd>
<dt>Name (Greek Classical Uppercase)</dt><dd>${greekClassicalUppercase || "—"}</dd>
<dt>Name (Greek Koine)</dt><dd>${greekNativeNames.koine || "—"}</dd>
<dt>Name (Greek Koine Uppercase)</dt><dd>${greekKoineUppercase || "—"}</dd>
<dt>Transliteration</dt><dd>${greekTranslitCombined || "—"}</dd>
<dt>Transliteration (Classical)</dt><dd>${greekTranslit.classical || "—"}</dd>
<dt>Transliteration (Koine)</dt><dd>${greekTranslit.koine || "—"}</dd>
<dt>IPA</dt><dd>${letter.ipa || "—"}</dd>
<dt>Isopsephy Value</dt><dd>${letter.numerology}</dd>
<dt>Isopsephy Value (Orderly)</dt><dd>${Number.isFinite(orderlyLetterValue) ? orderlyLetterValue : "—"}</dd>
<dt>Isopsephy Value (1-10-100)</dt><dd>${Number.isFinite(placeLetterValue) ? placeLetterValue : "—"}</dd>
<dt>Name Gematria (Greek Classical)</dt><dd>orderly ${Number.isFinite(greekClassicalNameGematriaOrderly?.total) ? greekClassicalNameGematriaOrderly.total : "—"} · 1-10-100 ${Number.isFinite(greekClassicalNameGematriaPlace?.total) ? greekClassicalNameGematriaPlace.total : "—"}</dd>
<dt>Name Breakdown (Greek Classical)</dt><dd>orderly: ${greekClassicalNameGematriaOrderly?.breakdown || "—"} · 1-10-100: ${greekClassicalNameGematriaPlace?.breakdown || "—"}</dd>
<dt>Name Gematria (Greek Koine)</dt><dd>orderly ${Number.isFinite(greekKoineNameGematriaOrderly?.total) ? greekKoineNameGematriaOrderly.total : "—"} · 1-10-100 ${Number.isFinite(greekKoineNameGematriaPlace?.total) ? greekKoineNameGematriaPlace.total : "—"}</dd>
<dt>Name Breakdown (Greek Koine)</dt><dd>orderly: ${greekKoineNameGematriaOrderly?.breakdown || "—"} · 1-10-100: ${greekKoineNameGematriaPlace?.breakdown || "—"}</dd>
<dt>Meaning / Origin</dt><dd>${letter.meaning || "—"}</dd>
</dl>
`));
const positionRootCard = renderPositionDigitalRootCard(letter, "greek", context);
const positionRootCard = renderPositionDigitalRootCard(letter, alphabetKey, context);
if (positionRootCard) {
sections.push(positionRootCard);
}
const equivalentsCard = renderAlphabetEquivalentCard("greek", letter, context);
const equivalentsCard = renderAlphabetEquivalentCard(alphabetKey, letter, context);
if (equivalentsCard) {
sections.push(equivalentsCard);
}
@@ -617,8 +984,8 @@
const alphabet = context.alphabet;
if (alphabet === "hebrew") {
renderHebrewDetail(context);
} else if (alphabet === "greek") {
renderGreekDetail(context);
} else if (alphabet === "greek" || alphabet === "greekArchaic") {
renderGreekDetail(context, alphabet);
} else if (alphabet === "english") {
renderEnglishDetail(context);
} else if (alphabet === "arabic") {