(function () { "use strict"; const SETTINGS_STORAGE_KEY = "tarot-time-settings-v1"; const SETTINGS_LAST_SAVED_AT_STORAGE_KEY = "tarot-time-settings-last-saved-at-v1"; let config = { defaultSettings: { latitude: 51.5074, longitude: -0.1278, timeFormat: "minutes", birthDate: "", tarotDeck: "ceremonial-magick", stellariumBackgroundEnabled: false, detailTextScale: 1, hasExplicitLocation: false }, onSettingsApplied: null, onSyncSkyBackground: null, onStatus: null, onConnectionSaved: null, onReopenActiveSection: null, setActiveSection: null, getActiveSection: null, onRenderWeek: null }; let lastConnectionProbeResult = null; let lastNonSettingsSection = "home"; function getElements() { return { openSettingsEl: document.getElementById("open-settings"), closeSettingsEl: document.getElementById("close-settings"), latEl: document.getElementById("lat"), lngEl: document.getElementById("lng"), timeFormatEl: document.getElementById("time-format"), birthDateEl: document.getElementById("birth-date"), tarotDeckEl: document.getElementById("tarot-deck"), tarotDeckCacheProgressEl: document.getElementById("tarot-deck-cache-progress"), tarotDeckCacheProgressLabelEl: document.getElementById("tarot-deck-cache-progress-label"), tarotDeckCacheStatusEl: document.getElementById("tarot-deck-cache-status"), detailTextScaleEl: document.getElementById("detail-text-scale"), detailTextScaleValueEl: document.getElementById("detail-text-scale-value"), stellariumBackgroundEl: document.getElementById("stellarium-background"), stellariumBackgroundHintEl: document.getElementById("stellarium-background-hint"), apiBaseUrlEl: document.getElementById("api-base-url"), apiKeyEl: document.getElementById("api-key"), apiConnectionSummaryEl: document.getElementById("api-connection-summary"), apiConnectionSummaryStateEl: document.getElementById("api-connection-summary-state"), apiConnectionSummaryClientEl: document.getElementById("api-connection-summary-client"), apiConnectionSummaryAccessEl: document.getElementById("api-connection-summary-access"), apiConnectionSummaryPermissionsEl: document.getElementById("api-connection-summary-permissions"), settingsPageStatusEl: document.getElementById("settings-page-status"), settingsPageStatusTextEl: document.getElementById("settings-page-status-text"), settingsPageStatusTimeEl: document.getElementById("settings-page-status-time"), saveSettingsEl: document.getElementById("save-settings"), useLocationEl: document.getElementById("use-location") }; } function loadLastSavedAt() { try { const raw = window.localStorage.getItem(SETTINGS_LAST_SAVED_AT_STORAGE_KEY); const parsed = Number(raw); return Number.isFinite(parsed) && parsed > 0 ? parsed : null; } catch { return null; } } function persistLastSavedAt(timestamp) { try { window.localStorage.setItem(SETTINGS_LAST_SAVED_AT_STORAGE_KEY, String(timestamp)); return true; } catch { return false; } } function formatLastSavedAt(timestamp) { if (!Number.isFinite(timestamp) || timestamp <= 0) { return ""; } try { return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(timestamp)); } catch { return new Date(timestamp).toLocaleString(); } } function setSettingsPageStatus(message, tone = "neutral", options = {}) { const { settingsPageStatusEl, settingsPageStatusTextEl, settingsPageStatusTimeEl } = getElements(); if (settingsPageStatusEl) { settingsPageStatusEl.dataset.tone = String(tone || "neutral"); } if (settingsPageStatusTextEl) { settingsPageStatusTextEl.textContent = String(message || "Settings ready."); } const savedAt = options.savedAt === undefined ? loadLastSavedAt() : options.savedAt; const formattedSavedAt = formatLastSavedAt(savedAt); if (settingsPageStatusTimeEl) { settingsPageStatusTimeEl.hidden = !formattedSavedAt; settingsPageStatusTimeEl.textContent = formattedSavedAt ? `Last saved ${formattedSavedAt}` : ""; } } function syncSavedSettingsStatus(message = "Settings ready.") { setSettingsPageStatus(message, "neutral", { savedAt: loadLastSavedAt() }); } function setSaveButtonBusy(isBusy) { const { saveSettingsEl } = getElements(); if (!saveSettingsEl) { return; } if (!saveSettingsEl.dataset.defaultLabel) { saveSettingsEl.dataset.defaultLabel = String(saveSettingsEl.textContent || "Save Settings").trim() || "Save Settings"; } saveSettingsEl.disabled = Boolean(isBusy); saveSettingsEl.textContent = isBusy ? "Saving..." : saveSettingsEl.dataset.defaultLabel; } function setLocationEntryState(isExplicit) { const { latEl, lngEl } = getElements(); const normalizedValue = isExplicit ? "true" : "false"; if (latEl) { latEl.dataset.explicitLocation = normalizedValue; } if (lngEl) { lngEl.dataset.explicitLocation = normalizedValue; } } function hasExplicitLocationEntry() { const { latEl } = getElements(); return latEl?.dataset.explicitLocation === "true"; } function syncStellariumBackgroundAvailability() { const { stellariumBackgroundEl, stellariumBackgroundHintEl } = getElements(); if (!stellariumBackgroundEl) { return; } const hasExplicitLocation = hasExplicitLocationEntry(); stellariumBackgroundEl.disabled = !hasExplicitLocation; if (!hasExplicitLocation) { stellariumBackgroundEl.checked = false; } if (stellariumBackgroundHintEl) { stellariumBackgroundHintEl.textContent = hasExplicitLocation ? "Uses your saved location to load the live sky background." : "Enter a location or use My Location before enabling the live sky background."; } } function markLocationAsExplicit() { setLocationEntryState(true); syncStellariumBackgroundAvailability(); } function getConnectionSettings() { return window.TarotAppConfig?.getConnectionSettings?.() || { apiBaseUrl: String(window.TarotAppConfig?.apiBaseUrl || "").trim(), apiKey: String(window.TarotAppConfig?.apiKey || "").trim() }; } function hasTarotAccess() { return window.TarotAppConfig?.hasTarotAccess?.() === true; } function syncConnectionInputs() { const { apiBaseUrlEl, apiKeyEl } = getElements(); const connectionSettings = getConnectionSettings(); if (apiBaseUrlEl) { apiBaseUrlEl.value = String(connectionSettings.apiBaseUrl || ""); } if (apiKeyEl) { apiKeyEl.value = String(connectionSettings.apiKey || ""); } } function hasConnectionChanged(previous, next) { return String(previous?.apiBaseUrl || "").trim() !== String(next?.apiBaseUrl || "").trim() || String(previous?.apiKey || "").trim() !== String(next?.apiKey || "").trim(); } function normalizeConnectionValues(values) { return Array.isArray(values) ? values.map((entry) => String(entry || "").trim()).filter(Boolean) : []; } function hasAdminCapability(auth) { const roles = normalizeConnectionValues(auth?.roles); const scopes = normalizeConnectionValues(auth?.scopes); return roles.includes("admin") || scopes.includes("api:admin"); } function formatConnectionValues(values, fallback = "none") { const normalized = normalizeConnectionValues(values); return normalized.length ? normalized.join(", ") : fallback; } function setConnectionSummary(result = null) { const { apiConnectionSummaryEl, apiConnectionSummaryStateEl, apiConnectionSummaryClientEl, apiConnectionSummaryAccessEl, apiConnectionSummaryPermissionsEl } = getElements(); if (!apiConnectionSummaryEl) { return; } if (!result) { apiConnectionSummaryEl.dataset.tone = "neutral"; if (apiConnectionSummaryStateEl) { apiConnectionSummaryStateEl.textContent = "Not checked yet."; } if (apiConnectionSummaryClientEl) { apiConnectionSummaryClientEl.textContent = "No authenticated API identity."; } if (apiConnectionSummaryAccessEl) { apiConnectionSummaryAccessEl.textContent = "Unknown"; } if (apiConnectionSummaryPermissionsEl) { apiConnectionSummaryPermissionsEl.textContent = "Save settings to validate this API key."; } return; } const tone = result.ok ? "success" : (result.reason === "auth-required" ? "warning" : "error"); const auth = result.auth || result.health?.auth || {}; const roles = normalizeConnectionValues(auth.roles); const scopes = normalizeConnectionValues(auth.scopes); const authenticated = auth.authenticated === true; const tarotAccessEnabled = result?.capabilities?.tarot === true; const accessValue = authenticated ? `${String(auth.accessLevel || "premium").trim() || "premium"}${hasAdminCapability(auth) ? " - admin capable" : ""}${tarotAccessEnabled ? " - tarot enabled" : " - tarot hidden"}` : (result.health?.apiKeyRequired ? "API key required" : "public"); const permissionsValue = authenticated ? `roles: ${formatConnectionValues(roles)} | scopes: ${formatConnectionValues(scopes)}` : (result.message || "Unable to validate the API connection."); apiConnectionSummaryEl.dataset.tone = tone; if (apiConnectionSummaryStateEl) { apiConnectionSummaryStateEl.textContent = result.ok ? `Connected${Number.isInteger(result.deckCount) ? ` • ${result.deckCount} deck${result.deckCount === 1 ? "" : "s"}` : ""}` : String(result.message || "Unable to validate the API connection."); } if (apiConnectionSummaryClientEl) { apiConnectionSummaryClientEl.textContent = authenticated ? [String(auth.clientId || "").trim(), String(auth.accountId || "").trim()].filter(Boolean).join(" / ") || "Authenticated client" : (result.health?.apiKeyRequired ? "No valid API identity returned." : "Public access"); } if (apiConnectionSummaryAccessEl) { apiConnectionSummaryAccessEl.textContent = accessValue; } if (apiConnectionSummaryPermissionsEl) { apiConnectionSummaryPermissionsEl.textContent = permissionsValue; } } async function refreshConnectionSummary(connectionSettings = getConnectionSettings(), { probeResult = null } = {}) { const apiBaseUrl = String(connectionSettings?.apiBaseUrl || "").trim(); if (!apiBaseUrl) { lastConnectionProbeResult = null; setConnectionSummary(null); return null; } const result = probeResult || await window.TarotDataService?.probeConnection?.(connectionSettings) || null; lastConnectionProbeResult = result; setConnectionSummary(result); return result; } function setStatus(text) { if (typeof config.onStatus === "function") { config.onStatus(text); } } function applyExternalSettings(settings) { if (typeof config.onSettingsApplied === "function") { config.onSettingsApplied(settings); } } function formatDeckCacheStatus(status) { const activeDeckId = String(status?.activeDeckId || normalizeTarotDeck(getElements().tarotDeckEl?.value)).trim().toLowerCase(); const loadedCount = Math.max(0, Number(status?.selectedDeckLoadedCount) || 0); const totalCount = Math.max(0, Number(status?.selectedDeckTotalCount) || 0); const warmedDeckCount = Math.max(0, Number(status?.warmedDeckCount) || 0); const totalDeckCount = Math.max(0, Number(status?.totalDeckCount) || 0); const backgroundProgress = totalDeckCount > 1 ? ` (${Math.min(warmedDeckCount, totalDeckCount)}/${totalDeckCount} decks warmed)` : ""; if (status?.selectedDeckPhase === "loading") { if (totalCount > 0) { return `Caching selected deck images to this browser... (${loadedCount}/${totalCount})${backgroundProgress}`; } return `Caching selected deck images to this browser...${backgroundProgress}`; } if (status?.selectedDeckPhase === "error") { return "Selected deck cache warmup hit an error. Images will still load on demand."; } if (status?.selectedDeckPhase === "ready") { if (totalDeckCount > 1 && warmedDeckCount < totalDeckCount) { return `Selected deck cached and ready for fullscreen use (${activeDeckId}). Warming the rest of the decks in background${backgroundProgress}.`; } if (totalDeckCount > 1) { return `All connected deck images cached and ready (${totalDeckCount}/${totalDeckCount} decks warmed).`; } return `Selected deck cached and ready for fullscreen use (${activeDeckId}).`; } if (totalDeckCount > 1 && warmedDeckCount > 0) { return `Deck cache idle. Background warmup has ${Math.min(warmedDeckCount, totalDeckCount)}/${totalDeckCount} decks ready.`; } return "Deck cache idle."; } function syncDeckCacheStatus(status) { const { tarotDeckCacheStatusEl, tarotDeckCacheProgressEl, tarotDeckCacheProgressLabelEl } = getElements(); if (!tarotDeckCacheStatusEl) { return; } tarotDeckCacheStatusEl.textContent = formatDeckCacheStatus(status); const totalCount = Math.max(0, Number(status?.selectedDeckTotalCount) || 0); const loadedCount = Math.max(0, Number(status?.selectedDeckLoadedCount) || 0); const derivedPercent = totalCount > 0 ? Math.round((loadedCount / totalCount) * 100) : 0; const percent = Math.max(0, Math.min(100, Number(status?.selectedDeckPercent) || derivedPercent)); if (tarotDeckCacheProgressEl) { tarotDeckCacheProgressEl.max = 100; tarotDeckCacheProgressEl.value = percent; } if (tarotDeckCacheProgressLabelEl) { tarotDeckCacheProgressLabelEl.textContent = `${percent}%`; } } function syncDetailTextScaleLabel(detailTextScale) { const { detailTextScaleValueEl } = getElements(); if (!detailTextScaleValueEl) { return; } detailTextScaleValueEl.textContent = `${Math.round(normalizeDetailTextScale(detailTextScale) * 100)}%`; } function syncSky(geo, options) { if (typeof config.onSyncSkyBackground === "function") { config.onSyncSkyBackground(geo, options); } } function normalizeTimeFormat(value) { if (value === "hours") { return "hours"; } if (value === "seconds") { return "seconds"; } return "minutes"; } function clampNumber(value, min, max, fallback) { const parsed = Number(value); if (!Number.isFinite(parsed)) { return fallback; } return Math.min(max, Math.max(min, parsed)); } function normalizeDetailTextScale(value) { return clampNumber(value, 0.85, 1.35, 1); } function normalizeBirthDate(value) { const normalized = String(value || "").trim(); if (!normalized) { return ""; } return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : ""; } function getKnownTarotDeckIds() { if (!hasTarotAccess()) { return new Set([String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase()]); } const knownDeckIds = new Set(); const deckOptions = window.TarotCardImages?.getDeckOptions?.(); if (Array.isArray(deckOptions)) { deckOptions.forEach((option) => { const id = String(option?.id || "").trim().toLowerCase(); if (id) { knownDeckIds.add(id); } }); } if (!knownDeckIds.size) { knownDeckIds.add(String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase()); } return knownDeckIds; } function getFallbackTarotDeckId() { if (!hasTarotAccess()) { return String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase(); } const deckOptions = window.TarotCardImages?.getDeckOptions?.(); if (Array.isArray(deckOptions)) { for (let i = 0; i < deckOptions.length; i += 1) { const id = String(deckOptions[i]?.id || "").trim().toLowerCase(); if (id) { return id; } } } return String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase(); } function normalizeTarotDeck(value) { if (!hasTarotAccess()) { const preservedValue = String(value || "").trim().toLowerCase(); return preservedValue || String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase(); } const normalized = String(value || "").trim().toLowerCase(); const knownDeckIds = getKnownTarotDeckIds(); if (knownDeckIds.has(normalized)) { return normalized; } return getFallbackTarotDeckId(); } function parseStoredNumber(value, fallback) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : fallback; } function parseStoredBoolean(value, fallback = false) { return typeof value === "boolean" ? value : fallback; } function hasLegacyExplicitLocation(settings, normalizedLatitude, normalizedLongitude) { const hasStoredCoordinates = Number.isFinite(Number(settings?.latitude)) && Number.isFinite(Number(settings?.longitude)); if (!hasStoredCoordinates) { return false; } return Math.abs(normalizedLatitude - Number(config.defaultSettings.latitude)) > 0.00005 || Math.abs(normalizedLongitude - Number(config.defaultSettings.longitude)) > 0.00005; } function normalizeSettings(settings) { const latitude = parseStoredNumber(settings?.latitude, config.defaultSettings.latitude); const longitude = parseStoredNumber(settings?.longitude, config.defaultSettings.longitude); const hasExplicitLocation = typeof settings?.hasExplicitLocation === "boolean" ? settings.hasExplicitLocation : hasLegacyExplicitLocation(settings, latitude, longitude); return { latitude, longitude, timeFormat: normalizeTimeFormat(settings?.timeFormat), birthDate: normalizeBirthDate(settings?.birthDate), tarotDeck: normalizeTarotDeck(settings?.tarotDeck), detailTextScale: normalizeDetailTextScale(settings?.detailTextScale), stellariumBackgroundEnabled: parseStoredBoolean(settings?.stellariumBackgroundEnabled, false) && hasExplicitLocation, hasExplicitLocation }; } function getResolvedTimeZone() { try { const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; return String(timeZone || ""); } catch { return ""; } } function buildBirthDateParts(birthDate) { const normalized = normalizeBirthDate(birthDate); if (!normalized) { return null; } const [year, month, day] = normalized.split("-").map((value) => Number(value)); if (!year || !month || !day) { return null; } const localNoon = new Date(year, month - 1, day, 12, 0, 0, 0); const utcNoon = new Date(Date.UTC(year, month - 1, day, 12, 0, 0, 0)); return { year, month, day, isoDate: normalized, localNoonIso: localNoon.toISOString(), utcNoonIso: utcNoon.toISOString(), timezoneOffsetMinutesAtNoon: localNoon.getTimezoneOffset() }; } function buildNatalContext(settings) { const normalized = normalizeSettings(settings); const birthDateParts = buildBirthDateParts(normalized.birthDate); const timeZone = getResolvedTimeZone(); return { latitude: normalized.latitude, longitude: normalized.longitude, birthDate: normalized.birthDate || null, birthDateParts, timeZone: timeZone || "UTC", timezoneOffsetMinutesNow: new Date().getTimezoneOffset(), timezoneOffsetMinutesAtBirthDateNoon: birthDateParts?.timezoneOffsetMinutesAtNoon ?? null }; } function emitSettingsUpdated(settings) { const normalized = normalizeSettings(settings); const natalContext = buildNatalContext(normalized); document.dispatchEvent(new CustomEvent("settings:updated", { detail: { settings: normalized, natalContext } })); } function loadSavedSettings() { try { const raw = window.localStorage.getItem(SETTINGS_STORAGE_KEY); if (!raw) { return { ...config.defaultSettings }; } const parsed = JSON.parse(raw); return normalizeSettings(parsed); } catch { return { ...config.defaultSettings }; } } function saveSettings(settings) { try { const normalized = normalizeSettings(settings); window.localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(normalized)); return true; } catch { return false; } } function syncTarotDeckInputOptions() { const { tarotDeckEl } = getElements(); if (!tarotDeckEl) { return; } if (!hasTarotAccess()) { tarotDeckEl.innerHTML = ""; const hiddenOption = document.createElement("option"); hiddenOption.value = String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase(); hiddenOption.textContent = "Tarot deck unavailable for this API key"; tarotDeckEl.appendChild(hiddenOption); tarotDeckEl.disabled = true; return; } const deckOptions = window.TarotCardImages?.getDeckOptions?.(); const previousValue = String(tarotDeckEl.value || "").trim().toLowerCase(); tarotDeckEl.innerHTML = ""; if (!Array.isArray(deckOptions) || !deckOptions.length) { const emptyOption = document.createElement("option"); emptyOption.value = String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase(); emptyOption.textContent = "No deck manifests found"; tarotDeckEl.appendChild(emptyOption); tarotDeckEl.disabled = true; return; } tarotDeckEl.disabled = false; deckOptions.forEach((option) => { const id = String(option?.id || "").trim().toLowerCase(); if (!id) { return; } const label = String(option?.label || id); const optionEl = document.createElement("option"); optionEl.value = id; optionEl.textContent = label; tarotDeckEl.appendChild(optionEl); }); tarotDeckEl.value = normalizeTarotDeck(previousValue); } function syncActiveTarotDeck(deckId) { if (!hasTarotAccess()) { return; } const normalizedDeckId = normalizeTarotDeck(deckId); if (window.TarotCardImages?.setActiveDeck) { window.TarotCardImages.setActiveDeck(normalizedDeckId); } const { tarotDeckEl } = getElements(); if (tarotDeckEl) { tarotDeckEl.value = normalizedDeckId; } syncDeckCacheStatus(window.TarotCardImages?.getDeckPreloadStatus?.()); } function applySettingsToInputs(settings) { const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl, detailTextScaleEl, stellariumBackgroundEl } = getElements(); syncTarotDeckInputOptions(); syncConnectionInputs(); const normalized = normalizeSettings(settings); latEl.value = String(normalized.latitude); lngEl.value = String(normalized.longitude); timeFormatEl.value = normalized.timeFormat; birthDateEl.value = normalized.birthDate; if (detailTextScaleEl) { detailTextScaleEl.value = String(Math.round(normalized.detailTextScale * 100)); } syncDetailTextScaleLabel(normalized.detailTextScale); if (tarotDeckEl) { tarotDeckEl.value = normalized.tarotDeck; } setLocationEntryState(normalized.hasExplicitLocation); if (stellariumBackgroundEl) { stellariumBackgroundEl.checked = normalized.stellariumBackgroundEnabled; } syncStellariumBackgroundAvailability(); syncActiveTarotDeck(normalized.tarotDeck); applyExternalSettings(normalized); return normalized; } function getSettingsFromInputs() { const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl, detailTextScaleEl, stellariumBackgroundEl } = getElements(); const latitudeText = String(latEl?.value || "").trim(); const longitudeText = String(lngEl?.value || "").trim(); if (!latitudeText || !longitudeText) { throw new Error("Latitude/Longitude must be entered before saving settings."); } const latitude = Number(latitudeText); const longitude = Number(longitudeText); if (Number.isNaN(latitude) || Number.isNaN(longitude)) { throw new Error("Latitude/Longitude must be valid numbers."); } return normalizeSettings({ latitude, longitude, timeFormat: normalizeTimeFormat(timeFormatEl.value), birthDate: normalizeBirthDate(birthDateEl.value), tarotDeck: normalizeTarotDeck(tarotDeckEl?.value), detailTextScale: normalizeDetailTextScale(Number(detailTextScaleEl?.value || 100) / 100), stellariumBackgroundEnabled: Boolean(stellariumBackgroundEl?.checked), hasExplicitLocation: hasExplicitLocationEntry() }); } function getConnectionSettingsFromInputs() { const { apiBaseUrlEl, apiKeyEl } = getElements(); return { apiBaseUrl: String(apiBaseUrlEl?.value || "").trim(), apiKey: String(apiKeyEl?.value || "").trim() }; } function openSettingsPopup() { const activeSection = typeof config.getActiveSection === "function" ? config.getActiveSection() : "home"; if (activeSection && activeSection !== "settings") { lastNonSettingsSection = activeSection; } applySettingsToInputs(loadSavedSettings()); syncSavedSettingsStatus(); void refreshConnectionSummary(getConnectionSettings(), { probeResult: lastConnectionProbeResult }); config.setActiveSection?.("settings"); } function closeSettingsPopup() { config.setActiveSection?.(lastNonSettingsSection || "home"); } async function handleSaveSettings() { setSaveButtonBusy(true); setSettingsPageStatus("Saving settings...", "info"); try { const settings = getSettingsFromInputs(); const previousConnectionSettings = getConnectionSettings(); const connectionSettings = getConnectionSettingsFromInputs(); const connectionChanged = hasConnectionChanged(previousConnectionSettings, connectionSettings); if (connectionChanged) { setSettingsPageStatus("Validating API connection...", "info", { savedAt: loadLastSavedAt() }); const probeResult = await window.TarotDataService?.probeConnection?.(connectionSettings); if (!probeResult?.ok) { setConnectionSummary(probeResult); throw new Error(probeResult?.message || "Unable to validate the API connection."); } lastConnectionProbeResult = probeResult; setConnectionSummary(probeResult); } const connectionResult = window.TarotAppConfig?.updateConnectionSettings?.(connectionSettings) || { didPersist: true }; const normalized = applySettingsToInputs(settings); syncSky( { latitude: normalized.latitude, longitude: normalized.longitude }, { force: true, backgroundEnabled: normalized.stellariumBackgroundEnabled, hasExplicitLocation: normalized.hasExplicitLocation } ); const didPersist = saveSettings(normalized); emitSettingsUpdated(normalized); if (typeof config.getActiveSection === "function" && config.getActiveSection() !== "home" && config.getActiveSection() !== "settings") { config.onReopenActiveSection?.(config.getActiveSection()); } if (connectionChanged && typeof config.onConnectionSaved === "function") { await config.onConnectionSaved(connectionResult, connectionSettings); } else if (typeof config.onRenderWeek === "function") { await config.onRenderWeek(); } if (!didPersist || connectionResult.didPersist === false) { setSettingsPageStatus("Settings applied for this session. Browser storage is unavailable.", "warning", { savedAt: loadLastSavedAt() }); setStatus("Settings applied for this session. Browser storage is unavailable."); } else { const savedAt = Date.now(); persistLastSavedAt(savedAt); setSettingsPageStatus("Settings saved.", "success", { savedAt }); setStatus("Settings saved."); } } catch (error) { setSettingsPageStatus(error?.message || "Unable to save settings.", "error", { savedAt: loadLastSavedAt() }); setStatus(error?.message || "Unable to save settings."); } finally { setSaveButtonBusy(false); } } function requestGeoLocation() { const { latEl, lngEl } = getElements(); if (!navigator.geolocation) { setSettingsPageStatus("Geolocation not available in this browser.", "warning", { savedAt: loadLastSavedAt() }); setStatus("Geolocation not available in this browser."); return; } setSettingsPageStatus("Getting your location...", "info", { savedAt: loadLastSavedAt() }); setStatus("Getting your location..."); navigator.geolocation.getCurrentPosition( ({ coords }) => { latEl.value = coords.latitude.toFixed(4); lngEl.value = coords.longitude.toFixed(4); markLocationAsExplicit(); syncSky( { latitude: coords.latitude, longitude: coords.longitude }, { force: true, backgroundEnabled: Boolean(getElements().stellariumBackgroundEl?.checked), hasExplicitLocation: true } ); setSettingsPageStatus("Location captured. Save Settings to keep it in this browser.", "info", { savedAt: loadLastSavedAt() }); setStatus("Location set from browser. Save Settings to use it across the app."); }, (err) => { const detail = err?.message || `code ${err?.code ?? "unknown"}`; setSettingsPageStatus(`Could not get location (${detail}).`, "error", { savedAt: loadLastSavedAt() }); setStatus(`Could not get location (${detail}).`); }, { enableHighAccuracy: true, timeout: 10000 } ); } function bindInteractions() { const { saveSettingsEl, useLocationEl, openSettingsEl, closeSettingsEl, detailTextScaleEl, latEl, lngEl } = getElements(); if (saveSettingsEl) { saveSettingsEl.addEventListener("click", () => { void handleSaveSettings(); }); } if (useLocationEl) { useLocationEl.addEventListener("click", requestGeoLocation); } if (detailTextScaleEl) { detailTextScaleEl.addEventListener("input", () => { syncDetailTextScaleLabel(Number(detailTextScaleEl.value) / 100); }); } [latEl, lngEl].forEach((inputEl) => { if (!inputEl) { return; } inputEl.addEventListener("input", markLocationAsExplicit); inputEl.addEventListener("change", markLocationAsExplicit); }); if (openSettingsEl) { openSettingsEl.addEventListener("click", (event) => { event.stopPropagation(); if ((config.getActiveSection?.() || "home") !== "settings") { openSettingsPopup(); } else { closeSettingsPopup(); } }); } if (closeSettingsEl) { closeSettingsEl.addEventListener("click", closeSettingsPopup); } document.addEventListener("keydown", (event) => { if (event.key === "Escape" && (config.getActiveSection?.() || "home") === "settings") { closeSettingsPopup(); } }); document.addEventListener("connection:updated", () => { syncConnectionInputs(); void refreshConnectionSummary(getConnectionSettings()); }); document.addEventListener("connection:access-updated", () => { syncTarotDeckInputOptions(); syncActiveTarotDeck(getElements().tarotDeckEl?.value || loadSavedSettings().tarotDeck); void refreshConnectionSummary(getConnectionSettings()); }); document.addEventListener("tarot:deck-cache-status", (event) => { if (hasTarotAccess()) { syncDeckCacheStatus(event?.detail); } }); } function init(nextConfig = {}) { config = { ...config, ...nextConfig, defaultSettings: { ...config.defaultSettings, ...(nextConfig.defaultSettings || {}) } }; syncSavedSettingsStatus(); setConnectionSummary(lastConnectionProbeResult); bindInteractions(); } function loadInitialSettingsAndApply() { const initialSettings = loadSavedSettings(); const normalized = applySettingsToInputs(initialSettings); emitSettingsUpdated(normalized); return normalized; } window.TarotSettingsUi = { ...(window.TarotSettingsUi || {}), init, openSettingsPopup, closeSettingsPopup, loadInitialSettingsAndApply, buildNatalContext, normalizeSettings }; })();