(function () { "use strict"; const SETTINGS_STORAGE_KEY = "tarot-time-settings-v1"; let config = { defaultSettings: { latitude: 51.5074, longitude: -0.1278, timeFormat: "minutes", birthDate: "", tarotDeck: "ceremonial-magick" }, onSettingsApplied: null, onSyncSkyBackground: null, onStatus: null, onConnectionSaved: null, onReopenActiveSection: null, getActiveSection: null, onRenderWeek: null }; function getElements() { return { openSettingsEl: document.getElementById("open-settings"), closeSettingsEl: document.getElementById("close-settings"), settingsPopupEl: document.getElementById("settings-popup"), settingsPopupCardEl: document.getElementById("settings-popup-card"), latEl: document.getElementById("lat"), lngEl: document.getElementById("lng"), timeFormatEl: document.getElementById("time-format"), birthDateEl: document.getElementById("birth-date"), tarotDeckEl: document.getElementById("tarot-deck"), apiBaseUrlEl: document.getElementById("api-base-url"), apiKeyEl: document.getElementById("api-key"), saveSettingsEl: document.getElementById("save-settings"), useLocationEl: document.getElementById("use-location") }; } function getConnectionSettings() { return window.TarotAppConfig?.getConnectionSettings?.() || { apiBaseUrl: String(window.TarotAppConfig?.apiBaseUrl || "").trim(), apiKey: String(window.TarotAppConfig?.apiKey || "").trim() }; } 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 setStatus(text) { if (typeof config.onStatus === "function") { config.onStatus(text); } } function applyExternalSettings(settings) { if (typeof config.onSettingsApplied === "function") { config.onSettingsApplied(settings); } } function syncSky(geo, force) { if (typeof config.onSyncSkyBackground === "function") { config.onSyncSkyBackground(geo, force); } } function normalizeTimeFormat(value) { if (value === "hours") { return "hours"; } if (value === "seconds") { return "seconds"; } return "minutes"; } function normalizeBirthDate(value) { const normalized = String(value || "").trim(); if (!normalized) { return ""; } return /^\d{4}-\d{2}-\d{2}$/.test(normalized) ? normalized : ""; } function getKnownTarotDeckIds() { 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() { 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) { 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 normalizeSettings(settings) { return { latitude: parseStoredNumber(settings?.latitude, config.defaultSettings.latitude), longitude: parseStoredNumber(settings?.longitude, config.defaultSettings.longitude), timeFormat: normalizeTimeFormat(settings?.timeFormat), birthDate: normalizeBirthDate(settings?.birthDate), tarotDeck: normalizeTarotDeck(settings?.tarotDeck) }; } 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; } 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 applySettingsToInputs(settings) { const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl } = 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 (tarotDeckEl) { tarotDeckEl.value = normalized.tarotDeck; } if (window.TarotCardImages?.setActiveDeck) { window.TarotCardImages.setActiveDeck(normalized.tarotDeck); } applyExternalSettings(normalized); return normalized; } function getSettingsFromInputs() { const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl } = getElements(); const latitude = Number(latEl.value); const longitude = Number(lngEl.value); 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) }); } function getConnectionSettingsFromInputs() { const { apiBaseUrlEl, apiKeyEl } = getElements(); return { apiBaseUrl: String(apiBaseUrlEl?.value || "").trim(), apiKey: String(apiKeyEl?.value || "").trim() }; } function openSettingsPopup() { const { settingsPopupEl, openSettingsEl } = getElements(); if (!settingsPopupEl) { return; } applySettingsToInputs(loadSavedSettings()); settingsPopupEl.hidden = false; if (openSettingsEl) { openSettingsEl.setAttribute("aria-expanded", "true"); } } function closeSettingsPopup() { const { settingsPopupEl, openSettingsEl } = getElements(); if (!settingsPopupEl) { return; } settingsPopupEl.hidden = true; if (openSettingsEl) { openSettingsEl.setAttribute("aria-expanded", "false"); } } async function handleSaveSettings() { try { const settings = getSettingsFromInputs(); const previousConnectionSettings = getConnectionSettings(); const connectionSettings = getConnectionSettingsFromInputs(); const connectionChanged = hasConnectionChanged(previousConnectionSettings, connectionSettings); const connectionResult = window.TarotAppConfig?.updateConnectionSettings?.(connectionSettings) || { didPersist: true }; const normalized = applySettingsToInputs(settings); syncSky({ latitude: normalized.latitude, longitude: normalized.longitude }, true); const didPersist = saveSettings(normalized); emitSettingsUpdated(normalized); if (typeof config.getActiveSection === "function" && config.getActiveSection() !== "home") { config.onReopenActiveSection?.(config.getActiveSection()); } closeSettingsPopup(); 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) { setStatus("Settings applied for this session. Browser storage is unavailable."); } } catch (error) { setStatus(error?.message || "Unable to save settings."); } } function requestGeoLocation() { const { latEl, lngEl } = getElements(); if (!navigator.geolocation) { setStatus("Geolocation not available in this browser."); return; } setStatus("Getting your location..."); navigator.geolocation.getCurrentPosition( ({ coords }) => { latEl.value = coords.latitude.toFixed(4); lngEl.value = coords.longitude.toFixed(4); syncSky({ latitude: coords.latitude, longitude: coords.longitude }, true); setStatus("Location set from browser. Click Save Settings to refresh."); }, (err) => { const detail = err?.message || `code ${err?.code ?? "unknown"}`; setStatus(`Could not get location (${detail}).`); }, { enableHighAccuracy: true, timeout: 10000 } ); } function bindInteractions() { const { saveSettingsEl, useLocationEl, openSettingsEl, closeSettingsEl, settingsPopupEl, settingsPopupCardEl } = getElements(); if (saveSettingsEl) { saveSettingsEl.addEventListener("click", () => { void handleSaveSettings(); }); } if (useLocationEl) { useLocationEl.addEventListener("click", requestGeoLocation); } if (openSettingsEl) { openSettingsEl.addEventListener("click", (event) => { event.stopPropagation(); if (settingsPopupEl?.hidden) { openSettingsPopup(); } else { closeSettingsPopup(); } }); } if (closeSettingsEl) { closeSettingsEl.addEventListener("click", closeSettingsPopup); } document.addEventListener("click", (event) => { const clickTarget = event.target; if (!settingsPopupEl || settingsPopupEl.hidden) { return; } if (!(clickTarget instanceof Node)) { return; } if (settingsPopupCardEl?.contains(clickTarget) || openSettingsEl?.contains(clickTarget)) { return; } closeSettingsPopup(); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closeSettingsPopup(); } }); document.addEventListener("connection:updated", () => { syncConnectionInputs(); syncTarotDeckInputOptions(); }); } function init(nextConfig = {}) { config = { ...config, ...nextConfig, defaultSettings: { ...config.defaultSettings, ...(nextConfig.defaultSettings || {}) } }; bindInteractions(); } function loadInitialSettingsAndApply() { const initialSettings = loadSavedSettings(); const normalized = applySettingsToInputs(initialSettings); emitSettingsUpdated(normalized); return normalized; } window.TarotSettingsUi = { ...(window.TarotSettingsUi || {}), init, openSettingsPopup, closeSettingsPopup, loadInitialSettingsAndApply, buildNatalContext, normalizeSettings }; })();