diff --git a/app/styles.css b/app/styles.css index 436691e..25b5983 100644 --- a/app/styles.css +++ b/app/styles.css @@ -1183,13 +1183,15 @@ gap: 12px; align-items: start; } - .planet-meta-card { + .planet-meta-card, + .detail-meta-card { border: 1px solid #3f3f46; border-radius: 10px; padding: 10px; background: #111118; } - .planet-meta-card strong { + .planet-meta-card strong, + .detail-meta-card strong { display: block; margin-bottom: 8px; color: #a1a1aa; @@ -3173,9 +3175,36 @@ .alpha-text-controls { display: grid; gap: 10px; - padding: 12px; - border-top: 1px solid #27272a; - background: #101018; + } + + .alpha-text-detail-heading { + display: grid; + gap: 14px; + padding-right: 108px; + } + + .alpha-text-heading-main { + display: grid; + gap: 4px; + } + + .alpha-text-heading-tools { + display: grid; + grid-template-columns: minmax(260px, 420px) minmax(320px, 1fr); + gap: 12px; + align-items: stretch; + } + + .alpha-text-controls--heading { + grid-template-columns: repeat(2, minmax(0, 1fr)); + padding: 14px; + border: 1px solid #2f2f39; + border-radius: 14px; + background: + linear-gradient(180deg, rgba(24, 24, 38, 0.98), rgba(12, 12, 18, 0.98)); + box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.08); + box-sizing: border-box; + align-content: start; } .alpha-text-search-controls { @@ -3197,6 +3226,36 @@ background: linear-gradient(180deg, rgba(24, 24, 38, 0.98), rgba(12, 12, 18, 0.98)); box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.08); + box-sizing: border-box; + } + + .alpha-text-search-controls--heading { + align-content: start; + height: 100%; + } + + .alpha-text-search-inline { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + align-items: stretch; + } + + .alpha-text-search-submit-inline { + min-height: 42px; + padding: 0 14px; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + box-sizing: border-box; + } + + .alpha-text-detail-heading .detail-toggle-inline { + position: absolute; + top: 0; + right: 0; + margin-left: 0; } .alpha-text-control { @@ -3212,6 +3271,7 @@ .alpha-text-select { width: 100%; + min-height: 42px; padding: 7px 8px; border-radius: 6px; border: 1px solid #3f3f46; @@ -3223,6 +3283,7 @@ .alpha-text-search-input { width: 100%; + min-height: 42px; padding: 9px 10px; border-radius: 8px; border: 1px solid #4338ca; @@ -3269,6 +3330,31 @@ min-width: 0; } + @media (max-width: 1040px) { + .alpha-text-heading-tools { + grid-template-columns: 1fr; + } + } + + @media (max-width: 720px) { + .alpha-text-detail-heading { + padding-right: 0; + } + + .alpha-text-controls--heading { + grid-template-columns: 1fr; + } + + .alpha-text-search-inline { + grid-template-columns: 1fr; + } + + .alpha-text-detail-heading .detail-toggle-inline { + position: static; + justify-self: start; + } + } + .alpha-text-meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); @@ -3276,6 +3362,30 @@ align-items: start; } + .alpha-text-extra-card { + align-content: start; + } + + .alpha-text-extra-group { + display: grid; + gap: 6px; + } + + .alpha-text-extra-group + .alpha-text-extra-group { + margin-top: 10px; + } + + .alpha-text-extra-label { + color: #a1a1aa; + font-size: 11px; + letter-spacing: 0.03em; + text-transform: uppercase; + } + + .alpha-text-extra-actions { + margin-top: 0; + } + .alpha-text-toolbar { display: flex; flex-wrap: wrap; @@ -3289,6 +3399,24 @@ gap: 0; } + .alpha-text-reader-card { + display: grid; + gap: 0; + } + + .alpha-text-reader-navigation { + display: flex; + gap: 8px; + align-items: center; + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid #27272a; + } + + .alpha-text-reader-nav-btn--next { + margin-left: auto; + } + .alpha-text-search-summary { margin: 0 0 10px; color: #a1a1aa; @@ -3410,6 +3538,16 @@ line-height: 1.65; } + .alpha-text-verse-text--original { + color: #f5f3ff; + font-family: var(--font-script-arabic), "Noto Serif", serif; + } + + .alpha-text-verse-text--transliteration { + color: #cbd5f5; + font-style: italic; + } + .alpha-text-token-grid { display: flex; flex-wrap: wrap; @@ -4629,11 +4767,11 @@ font-weight: 700; } .now-stats-sabian { - font-size: clamp(17px, 2.5vmin, 23px); - font-weight: 550; - line-height: 1.32; + font-size: clamp(13px, 1.75vmin, 16px); + font-weight: 500; + line-height: 1.42; color: #f8fafc; - white-space: pre-line; + white-space: normal; overflow: visible; max-height: none; } diff --git a/app/ui-alphabet-text.js b/app/ui-alphabet-text.js index 7467a2c..f8db482 100644 --- a/app/ui-alphabet-text.js +++ b/app/ui-alphabet-text.js @@ -25,17 +25,16 @@ searchLoading: false, searchError: "", searchRequestId: 0, - highlightedVerseId: "" + highlightedVerseId: "", + displayPreferencesBySource: {} }; let sourceListEl; let sourceCountEl; let globalSearchFormEl; let globalSearchInputEl; - let globalSearchClearEl; let localSearchFormEl; let localSearchInputEl; - let localSearchClearEl; let workSelectEl; let sectionSelectEl; let detailNameEl; @@ -59,10 +58,8 @@ sourceCountEl = document.getElementById("alpha-text-source-count"); globalSearchFormEl = document.getElementById("alpha-text-global-search-form"); globalSearchInputEl = document.getElementById("alpha-text-global-search-input"); - globalSearchClearEl = document.getElementById("alpha-text-global-search-clear"); localSearchFormEl = document.getElementById("alpha-text-local-search-form"); localSearchInputEl = document.getElementById("alpha-text-local-search-input"); - localSearchClearEl = document.getElementById("alpha-text-local-search-clear"); workSelectEl = document.getElementById("alpha-text-work-select"); sectionSelectEl = document.getElementById("alpha-text-section-select"); detailNameEl = document.getElementById("alpha-text-detail-name"); @@ -151,6 +148,82 @@ return findById(work?.sections, state.selectedSectionId); } + function normalizeTextValue(value) { + return String(value || "").trim(); + } + + const GREEK_TRANSLITERATION_MAP = { + α: "a", β: "b", γ: "g", δ: "d", ε: "e", ζ: "z", η: "e", θ: "th", + ι: "i", κ: "k", λ: "l", μ: "m", ν: "n", ξ: "x", ο: "o", π: "p", + ρ: "r", σ: "s", ς: "s", τ: "t", υ: "u", φ: "ph", χ: "ch", ψ: "ps", + ω: "o" + }; + + const HEBREW_TRANSLITERATION_MAP = { + א: "a", ב: "b", ג: "g", ד: "d", ה: "h", ו: "v", ז: "z", ח: "ch", + ט: "t", י: "y", כ: "k", ך: "k", ל: "l", מ: "m", ם: "m", נ: "n", + ן: "n", ס: "s", ע: "a", פ: "p", ף: "p", צ: "ts", ץ: "ts", ק: "q", + ר: "r", ש: "sh", ת: "t" + }; + + function stripSourceScriptMarks(value) { + return String(value || "") + .normalize("NFD") + .replace(/[\u0300-\u036f\u0591-\u05c7]/g, ""); + } + + function transliterateSourceScriptText(value) { + const stripped = stripSourceScriptMarks(value); + let result = ""; + + for (const character of stripped) { + const lowerCharacter = character.toLowerCase(); + const mapped = GREEK_TRANSLITERATION_MAP[lowerCharacter] + || HEBREW_TRANSLITERATION_MAP[character] + || HEBREW_TRANSLITERATION_MAP[lowerCharacter]; + result += mapped != null ? mapped : character; + } + + return result + .replace(/\s+([,.;:!?])/g, "$1") + .replace(/\s+/g, " ") + .trim(); + } + + function buildTokenDerivedTransliteration(verse) { + const tokenText = (Array.isArray(verse?.tokens) ? verse.tokens : []) + .map((token) => normalizeTextValue(token?.original)) + .filter(Boolean) + .join(" ") + .replace(/\s+([,.;:!?])/g, "$1") + .trim(); + + return tokenText ? transliterateSourceScriptText(tokenText) : ""; + } + + function getVerseTransliteration(verse, source = null) { + const metadata = verse?.metadata && typeof verse.metadata === "object" ? verse.metadata : {}; + const explicit = [ + verse?.transliteration, + verse?.xlit, + metadata?.transliteration, + metadata?.transliterationText, + metadata?.xlit, + metadata?.romanizedText, + metadata?.romanized + ].map(normalizeTextValue).find(Boolean) || ""; + + if (explicit) { + return explicit; + } + + if (source?.features?.hasTokenAnnotations) { + return buildTokenDerivedTransliteration(verse); + } + + return ""; + } + function getSearchInput(scope) { return scope === "source" ? localSearchInputEl : globalSearchInputEl; } @@ -169,12 +242,131 @@ } function updateSearchControls() { - if (globalSearchClearEl instanceof HTMLButtonElement) { - globalSearchClearEl.disabled = !String(globalSearchInputEl?.value || state.globalSearchQuery || "").trim(); + return; + } + + function clearActiveSearchUi(options = {}) { + const preserveHighlight = options.preserveHighlight === true; + const scope = state.activeSearchScope === "source" ? "source" : "global"; + + setStoredSearchQuery(scope, ""); + const input = getSearchInput(scope); + if (input instanceof HTMLInputElement) { + input.value = ""; } - if (localSearchClearEl instanceof HTMLButtonElement) { - localSearchClearEl.disabled = !String(localSearchInputEl?.value || state.localSearchQuery || "").trim(); + state.searchQuery = ""; + state.searchResults = null; + state.searchLoading = false; + state.searchError = ""; + state.searchRequestId += 1; + + if (!preserveHighlight) { + state.highlightedVerseId = ""; + } + + updateSearchControls(); + } + + function getSourceDisplayCapabilities(source, passage) { + const verses = Array.isArray(passage?.verses) ? passage.verses : []; + const hasOriginal = verses.some((verse) => normalizeTextValue(verse?.originalText)); + const hasTransliteration = verses.some((verse) => getVerseTransliteration(verse, source)); + const hasInterlinear = Boolean(source?.features?.hasTokenAnnotations); + const textModeCount = 1 + (hasOriginal ? 1 : 0) + (hasTransliteration ? 1 : 0); + + return { + hasTranslation: true, + hasOriginal, + hasTransliteration, + hasInterlinear, + hasAnyExtras: hasOriginal || hasTransliteration || hasInterlinear, + supportsAllTextMode: textModeCount > 1 + }; + } + + function getDefaultTextDisplayMode(capabilities) { + if (capabilities?.hasTranslation) { + return "translation"; + } + if (capabilities?.hasOriginal) { + return "original"; + } + if (capabilities?.hasTransliteration) { + return "transliteration"; + } + return "translation"; + } + + function getAvailableTextDisplayModes(capabilities) { + const modes = []; + if (capabilities?.hasTranslation) { + modes.push("translation"); + } + if (capabilities?.hasOriginal) { + modes.push("original"); + } + if (capabilities?.hasTransliteration) { + modes.push("transliteration"); + } + if (capabilities?.supportsAllTextMode) { + modes.push("all"); + } + return modes; + } + + function getSourceDisplayPreferences(source, passage) { + const sourceId = normalizeId(source?.id); + const capabilities = getSourceDisplayCapabilities(source, passage); + const availableTextModes = getAvailableTextDisplayModes(capabilities); + const stored = sourceId ? state.displayPreferencesBySource[sourceId] : null; + + let textMode = stored?.textMode; + if (!availableTextModes.includes(textMode)) { + textMode = getDefaultTextDisplayMode(capabilities); + } + + const preferences = { + textMode, + showInterlinear: capabilities.hasInterlinear ? Boolean(stored?.showInterlinear) : false + }; + + if (sourceId) { + state.displayPreferencesBySource[sourceId] = preferences; + } + + return { + ...preferences, + capabilities, + availableTextModes + }; + } + + function updateSourceDisplayPreferences(source, patch) { + const sourceId = normalizeId(source?.id); + if (!sourceId) { + return; + } + + const current = state.displayPreferencesBySource[sourceId] || {}; + state.displayPreferencesBySource[sourceId] = { + ...current, + ...patch + }; + } + + function formatTextDisplayModeLabel(mode) { + switch (mode) { + case "translation": + return "Translation"; + case "original": + return "Original"; + case "transliteration": + return "Transliteration"; + case "all": + return "All"; + default: + return "Display"; } } @@ -269,7 +461,7 @@ function createCard(title) { const card = document.createElement("div"); - card.className = "planet-meta-card"; + card.className = "detail-meta-card planet-meta-card"; if (title) { const heading = document.createElement("strong"); heading.textContent = title; @@ -302,6 +494,25 @@ detailBodyEl.appendChild(card); } + function navigateToPassageTarget(target) { + if (!target) { + return; + } + + state.selectedWorkId = target.workId; + state.selectedSectionId = target.sectionId; + state.lexiconEntry = null; + renderSelectors(); + void loadSelectedPassage(); + } + + function getPassageLocationLabel(passage) { + const source = passage?.source || getSelectedSource(); + const work = passage?.work || getSelectedWork(source); + const section = passage?.section || getSelectedSection(source, work); + return `${work?.title || "--"} · ${section?.title || section?.label || "--"}`; + } + function syncSelectionForSource(source) { const works = Array.isArray(source?.works) ? source.works : []; if (!works.length) { @@ -739,6 +950,7 @@ const source = passage?.source || getSelectedSource(); const work = passage?.work || getSelectedWork(source); const section = passage?.section || getSelectedSection(source, work); + const displayPreferences = getSourceDisplayPreferences(source, passage); const metaGrid = document.createElement("div"); metaGrid.className = "alpha-text-meta-grid"; @@ -755,49 +967,68 @@ `; metaGrid.appendChild(overviewCard); - const navigationCard = createCard("Navigation"); - const toolbar = document.createElement("div"); - toolbar.className = "alpha-text-toolbar"; + if (displayPreferences.capabilities.hasAnyExtras) { + const extraCard = createCard("Extra"); + extraCard.classList.add("alpha-text-extra-card"); - const previousButton = document.createElement("button"); - previousButton.type = "button"; - previousButton.className = "alpha-nav-btn"; - previousButton.textContent = "← Previous"; - previousButton.disabled = !passage?.navigation?.previous; - previousButton.addEventListener("click", () => { - if (!passage?.navigation?.previous) { - return; + if (displayPreferences.availableTextModes.length > 1) { + const displayGroup = document.createElement("div"); + displayGroup.className = "alpha-text-extra-group"; + + const displayLabel = document.createElement("span"); + displayLabel.className = "alpha-text-extra-label"; + displayLabel.textContent = "Display"; + + const displayButtons = document.createElement("div"); + displayButtons.className = "alpha-nav-btns alpha-text-extra-actions"; + + displayPreferences.availableTextModes.forEach((mode) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "alpha-nav-btn"; + button.textContent = formatTextDisplayModeLabel(mode); + button.setAttribute("aria-pressed", displayPreferences.textMode === mode ? "true" : "false"); + button.classList.toggle("is-selected", displayPreferences.textMode === mode); + button.addEventListener("click", () => { + updateSourceDisplayPreferences(source, { textMode: mode }); + renderDetail(); + }); + displayButtons.appendChild(button); + }); + + displayGroup.append(displayLabel, displayButtons); + extraCard.appendChild(displayGroup); } - state.selectedWorkId = passage.navigation.previous.workId; - state.selectedSectionId = passage.navigation.previous.sectionId; - state.lexiconEntry = null; - renderSelectors(); - void loadSelectedPassage(); - }); - const nextButton = document.createElement("button"); - nextButton.type = "button"; - nextButton.className = "alpha-nav-btn"; - nextButton.textContent = "Next →"; - nextButton.disabled = !passage?.navigation?.next; - nextButton.addEventListener("click", () => { - if (!passage?.navigation?.next) { - return; + if (displayPreferences.capabilities.hasInterlinear) { + const interlinearGroup = document.createElement("div"); + interlinearGroup.className = "alpha-text-extra-group"; + + const interlinearLabel = document.createElement("span"); + interlinearLabel.className = "alpha-text-extra-label"; + interlinearLabel.textContent = "Reader"; + + const interlinearButtons = document.createElement("div"); + interlinearButtons.className = "alpha-nav-btns alpha-text-extra-actions"; + + const interlinearButton = document.createElement("button"); + interlinearButton.type = "button"; + interlinearButton.className = "alpha-nav-btn"; + interlinearButton.textContent = "Interlinear"; + interlinearButton.setAttribute("aria-pressed", displayPreferences.showInterlinear ? "true" : "false"); + interlinearButton.classList.toggle("is-selected", displayPreferences.showInterlinear); + interlinearButton.addEventListener("click", () => { + updateSourceDisplayPreferences(source, { showInterlinear: !displayPreferences.showInterlinear }); + renderDetail(); + }); + + interlinearButtons.appendChild(interlinearButton); + interlinearGroup.append(interlinearLabel, interlinearButtons); + extraCard.appendChild(interlinearGroup); } - state.selectedWorkId = passage.navigation.next.workId; - state.selectedSectionId = passage.navigation.next.sectionId; - state.lexiconEntry = null; - renderSelectors(); - void loadSelectedPassage(); - }); - const location = document.createElement("div"); - location.className = "planet-text"; - location.textContent = `${work?.title || "--"} · ${section?.title || "--"}`; - - toolbar.append(previousButton, nextButton); - navigationCard.append(toolbar, location); - metaGrid.appendChild(navigationCard); + metaGrid.appendChild(extraCard); + } if (source?.features?.hasTokenAnnotations) { const noteCard = createCard("Reader Mode"); @@ -809,6 +1040,8 @@ } function createPlainVerse(verse) { + const source = getSelectedSource(); + const displayPreferences = getSourceDisplayPreferences(source, state.currentPassage); const article = document.createElement("article"); article.className = "alpha-text-verse"; article.classList.toggle("is-highlighted", isHighlightedVerse(verse)); @@ -820,12 +1053,9 @@ reference.className = "alpha-text-verse-reference"; reference.textContent = verse.reference || (verse.number ? `Verse ${verse.number}` : ""); - const text = document.createElement("p"); - text.className = "alpha-text-verse-text"; - appendHighlightedText(text, verse.text || "", isHighlightedVerse(verse) ? state.searchQuery : ""); - head.append(reference); - article.append(head, text); + article.append(head); + appendVerseTextLines(article, verse, source, displayPreferences, verse.text || ""); return article; } @@ -840,9 +1070,52 @@ return glossText || String(fallbackText || "").trim(); } - function createTokenVerse(verse, lexiconId) { + function appendVerseTextLines(target, verse, source, displayPreferences, translationText) { + if (!(target instanceof HTMLElement)) { + return; + } + + const mode = displayPreferences?.textMode || "translation"; + const originalText = normalizeTextValue(verse?.originalText); + const transliterationText = getVerseTransliteration(verse, source); + const lines = []; + + const appendLine = (text, variant) => { + const normalizedText = normalizeTextValue(text); + if (!normalizedText || lines.some((entry) => entry.text === normalizedText)) { + return; + } + lines.push({ text: normalizedText, variant }); + }; + + if (mode === "all") { + appendLine(translationText, "translation"); + appendLine(originalText, "original"); + appendLine(transliterationText, "transliteration"); + } else if (mode === "original") { + appendLine(originalText || translationText, originalText ? "original" : "translation"); + } else if (mode === "transliteration") { + appendLine(transliterationText || translationText, transliterationText ? "transliteration" : "translation"); + } else { + appendLine(translationText, "translation"); + } + + if (!lines.length) { + appendLine(translationText, "translation"); + } + + lines.forEach((line) => { + const text = document.createElement("p"); + text.className = `alpha-text-verse-text alpha-text-verse-text--${line.variant}`; + appendHighlightedText(text, line.text, isHighlightedVerse(verse) ? state.searchQuery : ""); + target.appendChild(text); + }); + } + + function createTokenVerse(verse, lexiconId, displayPreferences, source) { const article = document.createElement("article"); - article.className = "alpha-text-verse alpha-text-verse--interlinear"; + article.className = "alpha-text-verse"; + article.classList.toggle("alpha-text-verse--interlinear", Boolean(displayPreferences?.showInterlinear)); article.classList.toggle("is-highlighted", isHighlightedVerse(verse)); const head = document.createElement("div"); @@ -852,14 +1125,6 @@ reference.className = "alpha-text-verse-reference"; reference.textContent = verse.reference || (verse.number ? `Verse ${verse.number}` : ""); - const gloss = document.createElement("p"); - gloss.className = "alpha-text-verse-text"; - appendHighlightedText( - gloss, - buildTokenTranslationText(verse?.tokens, verse?.text), - isHighlightedVerse(verse) ? state.searchQuery : "" - ); - const tokenGrid = document.createElement("div"); tokenGrid.className = "alpha-text-token-grid"; @@ -895,13 +1160,19 @@ }); head.append(reference); - article.append(head, gloss, tokenGrid); + article.append(head); + appendVerseTextLines(article, verse, source, displayPreferences, buildTokenTranslationText(verse?.tokens, verse?.text)); + if (displayPreferences?.showInterlinear) { + article.appendChild(tokenGrid); + } return article; } function createReaderCard(passage) { const source = passage?.source || getSelectedSource(); - const card = createCard(`${source?.title || "Text"} Reader`); + const displayPreferences = getSourceDisplayPreferences(source, passage); + const card = createCard(getPassageLocationLabel(passage)); + card.classList.add("alpha-text-reader-card"); const reader = document.createElement("div"); reader.className = "alpha-text-reader"; @@ -920,12 +1191,42 @@ verses.forEach((verse) => { const verseEl = source?.features?.hasTokenAnnotations - ? createTokenVerse(verse, source.features.lexiconIds?.[0] || "") + ? createTokenVerse(verse, source.features.lexiconIds?.[0] || "", displayPreferences, source) : createPlainVerse(verse); reader.appendChild(verseEl); }); card.appendChild(reader); + + const navigation = document.createElement("div"); + navigation.className = "alpha-text-reader-navigation"; + + if (passage?.navigation?.previous) { + const previousButton = document.createElement("button"); + previousButton.type = "button"; + previousButton.className = "alpha-nav-btn alpha-text-reader-nav-btn"; + previousButton.textContent = "← Previous"; + previousButton.addEventListener("click", () => { + navigateToPassageTarget(passage.navigation.previous); + }); + navigation.appendChild(previousButton); + } + + if (passage?.navigation?.next) { + const nextButton = document.createElement("button"); + nextButton.type = "button"; + nextButton.className = "alpha-nav-btn alpha-text-reader-nav-btn alpha-text-reader-nav-btn--next"; + nextButton.textContent = "Next →"; + nextButton.addEventListener("click", () => { + navigateToPassageTarget(passage.navigation.next); + }); + navigation.appendChild(nextButton); + } + + if (navigation.childElementCount) { + card.appendChild(navigation); + } + return card; } @@ -1168,6 +1469,8 @@ renderSourceList(); renderSelectors(); await loadSelectedPassage(); + clearActiveSearchUi({ preserveHighlight: true }); + renderDetail(); } function bindControls() { @@ -1193,13 +1496,6 @@ }); } - if (globalSearchClearEl instanceof HTMLButtonElement) { - globalSearchClearEl.addEventListener("click", () => { - clearScopedSearch("global"); - renderDetail(); - }); - } - if (localSearchFormEl instanceof HTMLFormElement) { localSearchFormEl.addEventListener("submit", (event) => { event.preventDefault(); @@ -1218,13 +1514,6 @@ }); } - if (localSearchClearEl instanceof HTMLButtonElement) { - localSearchClearEl.addEventListener("click", () => { - clearScopedSearch("source"); - renderDetail(); - }); - } - if (workSelectEl) { workSelectEl.addEventListener("change", () => { state.selectedWorkId = String(workSelectEl.value || ""); diff --git a/app/ui-now.js b/app/ui-now.js index fc77817..dded30d 100644 --- a/app/ui-now.js +++ b/app/ui-now.js @@ -27,6 +27,18 @@ let moonCountdownCache = null; let decanCountdownCache = null; + function joinSabianPhrases(phrases) { + const parts = (Array.isArray(phrases) ? phrases : []) + .map((phrase) => String(phrase || "").trim()) + .filter(Boolean); + + if (!parts.length) { + return "--"; + } + + return parts.join(" "); + } + function renderNowStatsFromSnapshot(elements, stats) { if (elements.nowStatsPlanetsEl) { elements.nowStatsPlanetsEl.replaceChildren(); @@ -47,13 +59,10 @@ if (elements.nowStatsSabianEl) { const sunSabianSymbol = stats?.sunSabianSymbol || null; const moonSabianSymbol = stats?.moonSabianSymbol || null; - const sunLine = sunSabianSymbol?.phrase - ? `Sun Sabian ${sunSabianSymbol.absoluteDegree}: ${sunSabianSymbol.phrase}` - : "Sun Sabian: --"; - const moonLine = moonSabianSymbol?.phrase - ? `Moon Sabian ${moonSabianSymbol.absoluteDegree}: ${moonSabianSymbol.phrase}` - : "Moon Sabian: --"; - elements.nowStatsSabianEl.textContent = `${sunLine}\n${moonLine}`; + elements.nowStatsSabianEl.textContent = joinSabianPhrases([ + moonSabianSymbol?.phrase, + sunSabianSymbol?.phrase + ]); } } diff --git a/index.html b/index.html index 4ab56c4..79a890b 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - +