Files
TaroTime/app.js
2026-03-20 13:39:54 -07:00

597 lines
19 KiB
JavaScript

const { getCenteredWeekStartDay, getDateKey, getMoonPhaseName } = window.TarotCalc;
const { loadReferenceData, loadMagickDataset } = window.TarotDataService;
const { buildWeekEvents } = window.TarotEventBuilder;
const { updateNowPanel } = window.TarotNowUi;
const { ensureTarotSection } = window.TarotSectionUi || {};
const { ensurePlanetSection } = window.PlanetSectionUi || {};
const { ensureCyclesSection } = window.CyclesSectionUi || {};
const { ensureElementsSection } = window.ElementsSectionUi || {};
const { ensureAudioCircleSection } = window.AudioCircleUi || {};
const { ensureAudioNotesSection } = window.AudioNotesUi || {};
const { ensureIChingSection } = window.IChingSectionUi || {};
const { ensureKabbalahSection } = window.KabbalahSectionUi || {};
const { ensureCubeSection } = window.CubeSectionUi || {};
const { ensureAlphabetSection } = window.AlphabetSectionUi || {};
const { ensureAlphabetTextSection } = window.AlphabetTextUi || {};
const { ensureZodiacSection } = window.ZodiacSectionUi || {};
const { ensureQuizSection } = window.QuizSectionUi || {};
const { ensureGodsSection } = window.GodsSectionUi || {};
const { ensureEnochianSection } = window.EnochianSectionUi || {};
const { ensureCalendarSection } = window.CalendarSectionUi || {};
const { ensureHolidaySection } = window.HolidaySectionUi || {};
const { ensureNatalPanel } = window.TarotNatalUi || {};
const { ensureNumbersSection, selectNumberEntry, normalizeNumberValue } = window.TarotNumbersUi || {};
const tarotSpreadUi = window.TarotSpreadUi || {};
const settingsUi = window.TarotSettingsUi || {};
const chromeUi = window.TarotChromeUi || {};
const navigationUi = window.TarotNavigationUi || {};
const calendarFormattingUi = window.TarotCalendarFormatting || {};
const calendarVisualsUi = window.TarotCalendarVisuals || {};
const homeUi = window.TarotHomeUi || {};
const sectionStateUi = window.TarotSectionStateUi || {};
const appRuntime = window.TarotAppRuntime || {};
const statusEl = document.getElementById("status");
const monthStripEl = document.getElementById("month-strip");
const calendarEl = document.getElementById("calendar");
const timelineSectionEl = document.getElementById("timeline-section");
const calendarSectionEl = document.getElementById("calendar-section");
const holidaySectionEl = document.getElementById("holiday-section");
const audioCircleSectionEl = document.getElementById("audio-circle-section");
const audioNotesSectionEl = document.getElementById("audio-notes-section");
const tarotSectionEl = document.getElementById("tarot-section");
const tarotHouseSectionEl = document.getElementById("tarot-house-section");
const astronomySectionEl = document.getElementById("astronomy-section");
const natalSectionEl = document.getElementById("natal-section");
const planetSectionEl = document.getElementById("planet-section");
const cyclesSectionEl = document.getElementById("cycles-section");
const elementsSectionEl = document.getElementById("elements-section");
const ichingSectionEl = document.getElementById("iching-section");
const kabbalahSectionEl = document.getElementById("kabbalah-section");
const kabbalahTreeSectionEl = document.getElementById("kabbalah-tree-section");
const cubeSectionEl = document.getElementById("cube-section");
const alphabetSectionEl = document.getElementById("alphabet-section");
const alphabetLettersSectionEl = document.getElementById("alphabet-letters-section");
const alphabetTextSectionEl = document.getElementById("alphabet-text-section");
const numbersSectionEl = document.getElementById("numbers-section");
const zodiacSectionEl = document.getElementById("zodiac-section");
const quizSectionEl = document.getElementById("quiz-section");
const godsSectionEl = document.getElementById("gods-section");
const enochianSectionEl = document.getElementById("enochian-section");
const openHomeEl = document.getElementById("open-home");
const openCalendarEl = document.getElementById("open-calendar");
const openCalendarTimelineEl = document.getElementById("open-calendar-timeline");
const openCalendarMonthsEl = document.getElementById("open-calendar-months");
const openHolidaysEl = document.getElementById("open-holidays");
const openAudioEl = document.getElementById("open-audio");
const openAudioCircleEl = document.getElementById("open-audio-circle");
const openAudioNotesEl = document.getElementById("open-audio-notes");
const openTarotEl = document.getElementById("open-tarot");
const openTarotHouseEl = document.getElementById("open-tarot-house");
const openAstronomyEl = document.getElementById("open-astronomy");
const openPlanetsEl = document.getElementById("open-planets");
const openCyclesEl = document.getElementById("open-cycles");
const openElementsEl = document.getElementById("open-elements");
const openIChingEl = document.getElementById("open-iching");
const openKabbalahEl = document.getElementById("open-kabbalah");
const openKabbalahTreeEl = document.getElementById("open-kabbalah-tree");
const openKabbalahCubeEl = document.getElementById("open-kabbalah-cube");
const openAlphabetEl = document.getElementById("open-alphabet");
const openAlphabetWordEl = document.getElementById("open-alphabet-word");
const openAlphabetLettersEl = document.getElementById("open-alphabet-letters");
const openAlphabetTextEl = document.getElementById("open-alphabet-text");
const openNumbersEl = document.getElementById("open-numbers");
const openZodiacEl = document.getElementById("open-zodiac");
const openNatalEl = document.getElementById("open-natal");
const openQuizEl = document.getElementById("open-quiz");
const openGodsEl = document.getElementById("open-gods");
const openEnochianEl = document.getElementById("open-enochian");
const latEl = document.getElementById("lat");
const lngEl = document.getElementById("lng");
const nowSkyLayerEl = document.getElementById("now-sky-layer");
const nowPanelEl = document.getElementById("now-panel");
const nowOverlayToggleEl = document.getElementById("now-overlay-toggle");
const connectionGateEl = document.getElementById("connection-gate");
const connectionGateBaseUrlEl = document.getElementById("connection-gate-base-url");
const connectionGateApiKeyEl = document.getElementById("connection-gate-api-key");
const connectionGateStatusEl = document.getElementById("connection-gate-status");
const connectionGateConnectEl = document.getElementById("connection-gate-connect");
const nowElements = {
nowHourEl: document.getElementById("now-hour"),
nowHourTarotEl: document.getElementById("now-hour-tarot"),
nowCountdownEl: document.getElementById("now-countdown"),
nowHourNextEl: document.getElementById("now-hour-next"),
nowHourCardEl: document.getElementById("now-hour-card"),
nowMoonEl: document.getElementById("now-moon"),
nowMoonTarotEl: document.getElementById("now-moon-tarot"),
nowMoonCountdownEl: document.getElementById("now-moon-countdown"),
nowMoonNextEl: document.getElementById("now-moon-next"),
nowMoonCardEl: document.getElementById("now-moon-card"),
nowDecanEl: document.getElementById("now-decan"),
nowDecanTarotEl: document.getElementById("now-decan-tarot"),
nowDecanCountdownEl: document.getElementById("now-decan-countdown"),
nowDecanNextEl: document.getElementById("now-decan-next"),
nowDecanCardEl: document.getElementById("now-decan-card"),
nowStatsSabianEl: document.getElementById("now-stats-sabian"),
nowStatsPlanetsEl: document.getElementById("now-stats-planets")
};
const baseWeekOptions = {
hourStart: 0,
hourEnd: 24,
eventView: ["allday", "time"],
taskView: false
};
const PLANET_CALENDAR_ORDER = ["saturn", "jupiter", "mars", "sol", "venus", "mercury", "luna"];
const DEFAULT_TAROT_DECK = "ceremonial-magick";
const DEFAULT_SETTINGS = {
latitude: 51.5074,
longitude: -0.1278,
timeFormat: "minutes",
birthDate: "",
tarotDeck: DEFAULT_TAROT_DECK
};
const PLANET_CALENDAR_STYLES = {
saturn: {
name: "♄ Saturn",
color: "#f4f4f5",
backgroundColor: "#0a0a0a",
borderColor: "#0a0a0a"
},
jupiter: {
name: "♃ Jupiter",
color: "#eff6ff",
backgroundColor: "#1d4ed8",
borderColor: "#1d4ed8"
},
mars: {
name: "♂ Mars",
color: "#fff1f2",
backgroundColor: "#dc2626",
borderColor: "#dc2626"
},
sol: {
name: "☉ Sol",
color: "#111827",
backgroundColor: "#facc15",
borderColor: "#eab308"
},
venus: {
name: "♀ Venus",
color: "#ecfdf5",
backgroundColor: "#16a34a",
borderColor: "#15803d"
},
mercury: {
name: "☿ Mercury",
color: "#111827",
backgroundColor: "#fb923c",
borderColor: "#f97316"
},
luna: {
name: "☾ Luna",
color: "#111827",
backgroundColor: "#e2e8f0",
borderColor: "#cbd5e1"
}
};
const planetaryCalendars = PLANET_CALENDAR_ORDER.map((planetId) => {
const style = PLANET_CALENDAR_STYLES[planetId];
return {
id: `planet-${planetId}`,
name: style.name,
color: style.color,
backgroundColor: style.backgroundColor,
dragBackgroundColor: style.backgroundColor,
borderColor: style.borderColor
};
});
const calendar = new tui.Calendar("#calendar", {
defaultView: "week",
usageStatistics: false,
isReadOnly: true,
useFormPopup: false,
useDetailPopup: false,
gridSelection: false,
calendars: [
...planetaryCalendars,
{
id: "planetary",
name: "Planetary (Fallback)",
color: "#f4f4f5",
backgroundColor: "#52525b",
dragBackgroundColor: "#52525b",
borderColor: "#52525b"
},
{
id: "astrology",
name: "Astrology & Tarot",
color: "#18181b",
backgroundColor: "#fcd34d",
dragBackgroundColor: "#fcd34d",
borderColor: "#fcd34d"
}
],
week: {
...baseWeekOptions,
startDayOfWeek: getCenteredWeekStartDay(new Date())
}
});
appRuntime.init?.({
calendar,
baseWeekOptions,
defaultSettings: DEFAULT_SETTINGS,
latEl,
lngEl,
nowElements,
calendarVisualsUi,
homeUi,
onStatus: (text) => setStatus(text),
services: {
getCenteredWeekStartDay,
getDateKey,
loadReferenceData,
loadMagickDataset,
buildWeekEvents,
updateNowPanel
},
ensure: {
ensureTarotSection,
ensurePlanetSection,
ensureCyclesSection,
ensureIChingSection,
ensureCalendarSection,
ensureHolidaySection,
ensureNatalPanel,
ensureQuizSection
}
});
let currentSettings = { ...DEFAULT_SETTINGS };
let hasRenderedConnectedShell = false;
function setStatus(text) {
if (!statusEl) {
return;
}
statusEl.textContent = text;
}
function getConnectionSettings() {
return window.TarotAppConfig?.getConnectionSettings?.() || {
apiBaseUrl: "",
apiKey: ""
};
}
function syncConnectionGateInputs() {
const connectionSettings = getConnectionSettings();
if (connectionGateBaseUrlEl) {
connectionGateBaseUrlEl.value = String(connectionSettings.apiBaseUrl || "");
}
if (connectionGateApiKeyEl) {
connectionGateApiKeyEl.value = String(connectionSettings.apiKey || "");
}
}
function setConnectionGateStatus(text, tone = "default") {
if (!connectionGateStatusEl) {
return;
}
connectionGateStatusEl.textContent = text || "";
if (tone && tone !== "default") {
connectionGateStatusEl.dataset.tone = tone;
} else {
delete connectionGateStatusEl.dataset.tone;
}
}
function showConnectionGate(message, tone = "default") {
syncConnectionGateInputs();
if (connectionGateEl) {
connectionGateEl.hidden = false;
}
document.body.classList.add("connection-gated");
setConnectionGateStatus(message, tone);
}
function hideConnectionGate() {
if (connectionGateEl) {
connectionGateEl.hidden = true;
}
document.body.classList.remove("connection-gated");
}
function getConnectionSettingsFromGate() {
return {
apiBaseUrl: String(connectionGateBaseUrlEl?.value || "").trim(),
apiKey: String(connectionGateApiKeyEl?.value || "").trim()
};
}
async function ensureConnectedApp(nextConnectionSettings = null) {
if (nextConnectionSettings) {
window.TarotAppConfig?.updateConnectionSettings?.(nextConnectionSettings);
}
syncConnectionGateInputs();
const configuredConnection = getConnectionSettings();
if (!configuredConnection.apiBaseUrl) {
showConnectionGate("Enter an API Base URL to load TaroTime.", "error");
return false;
}
showConnectionGate("Connecting to the API...", "pending");
const probeResult = await window.TarotDataService?.probeConnection?.();
if (!probeResult?.ok) {
showConnectionGate(probeResult?.message || "Unable to reach the API.", "error");
return false;
}
hideConnectionGate();
if (!hasRenderedConnectedShell) {
sectionStateUi.setActiveSection?.("home");
hasRenderedConnectedShell = true;
}
setConnectionGateStatus("Connected.", "success");
setStatus(`Connected to ${configuredConnection.apiBaseUrl}.`);
await appRuntime.renderWeek?.();
return true;
}
function bindConnectionGate() {
syncConnectionGateInputs();
if (connectionGateConnectEl) {
connectionGateConnectEl.addEventListener("click", () => {
void ensureConnectedApp(getConnectionSettingsFromGate());
});
}
[connectionGateBaseUrlEl, connectionGateApiKeyEl].forEach((element) => {
if (!element) {
return;
}
element.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
event.preventDefault();
void ensureConnectedApp(getConnectionSettingsFromGate());
}
});
});
document.addEventListener("connection:updated", () => {
syncConnectionGateInputs();
});
}
window.TarotNumbersUi?.init?.({
getReferenceData: () => appRuntime.getReferenceData?.() || null,
getMagickDataset: () => appRuntime.getMagickDataset?.() || null,
ensureTarotSection
});
window.TarotSpreadUi?.init?.({
ensureTarotSection,
getReferenceData: () => appRuntime.getReferenceData?.() || null,
getMagickDataset: () => appRuntime.getMagickDataset?.() || null,
getActiveSection: () => sectionStateUi.getActiveSection?.() || "home",
setActiveSection: (section) => sectionStateUi.setActiveSection?.(section)
});
sectionStateUi.init?.({
calendar,
tarotSpreadUi,
settingsUi,
calendarVisualsUi,
homeUi,
getReferenceData: () => appRuntime.getReferenceData?.() || null,
getMagickDataset: () => appRuntime.getMagickDataset?.() || null,
elements: {
calendarEl,
monthStripEl,
nowPanelEl,
timelineSectionEl,
calendarSectionEl,
holidaySectionEl,
audioCircleSectionEl,
audioNotesSectionEl,
tarotSectionEl,
tarotHouseSectionEl,
astronomySectionEl,
natalSectionEl,
planetSectionEl,
cyclesSectionEl,
elementsSectionEl,
ichingSectionEl,
kabbalahSectionEl,
kabbalahTreeSectionEl,
cubeSectionEl,
alphabetSectionEl,
alphabetLettersSectionEl,
alphabetTextSectionEl,
numbersSectionEl,
zodiacSectionEl,
quizSectionEl,
godsSectionEl,
enochianSectionEl,
openHomeEl,
openCalendarEl,
openCalendarTimelineEl,
openCalendarMonthsEl,
openHolidaysEl,
openAudioEl,
openAudioCircleEl,
openAudioNotesEl,
openTarotEl,
openTarotHouseEl,
openAstronomyEl,
openPlanetsEl,
openCyclesEl,
openElementsEl,
openIChingEl,
openKabbalahEl,
openKabbalahTreeEl,
openKabbalahCubeEl,
openAlphabetEl,
openAlphabetWordEl,
openAlphabetLettersEl,
openAlphabetTextEl,
openNumbersEl,
openZodiacEl,
openNatalEl,
openQuizEl,
openGodsEl,
openEnochianEl
},
ensure: {
ensureTarotSection,
ensurePlanetSection,
ensureCyclesSection,
ensureElementsSection,
ensureIChingSection,
ensureKabbalahSection,
ensureCubeSection,
ensureAlphabetSection,
ensureAlphabetTextSection,
ensureZodiacSection,
ensureQuizSection,
ensureGodsSection,
ensureEnochianSection,
ensureCalendarSection,
ensureHolidaySection,
ensureNatalPanel,
ensureNumbersSection,
ensureAudioCircleSection,
ensureAudioNotesSection
}
});
settingsUi.init?.({
defaultSettings: DEFAULT_SETTINGS,
onSettingsApplied: (settings) => {
appRuntime.applySettings?.(settings);
currentSettings = settings;
},
onSyncSkyBackground: (geo, force) => homeUi.syncNowSkyBackground?.(geo, force),
onStatus: (text) => setStatus(text),
onConnectionSaved: async () => ensureConnectedApp(),
getActiveSection: () => sectionStateUi.getActiveSection?.() || "home",
onReopenActiveSection: (section) => sectionStateUi.setActiveSection?.(section),
onRenderWeek: () => appRuntime.renderWeek?.()
});
chromeUi.init?.();
calendarFormattingUi.init?.({
getCurrentTimeFormat: () => appRuntime.getCurrentTimeFormat?.() || "minutes",
getReferenceData: () => appRuntime.getReferenceData?.() || null
});
calendarVisualsUi.init?.({
calendar,
monthStripEl,
getCurrentGeo: () => appRuntime.getCurrentGeo?.() || null,
parseGeoInput: () => appRuntime.parseGeoInput?.(),
getMoonPhaseName
});
homeUi.init?.({
nowSkyLayerEl,
nowPanelEl,
getCurrentGeo: () => appRuntime.getCurrentGeo?.() || null
});
if (nowOverlayToggleEl && nowPanelEl) {
const syncNowOverlayVisibility = () => {
nowPanelEl.classList.toggle("is-overlay-hidden", !nowOverlayToggleEl.checked);
};
nowOverlayToggleEl.addEventListener("change", syncNowOverlayVisibility);
syncNowOverlayVisibility();
}
navigationUi.init?.({
tarotSpreadUi,
getActiveSection: () => sectionStateUi.getActiveSection?.() || "home",
setActiveSection: (section) => sectionStateUi.setActiveSection?.(section),
getReferenceData: () => appRuntime.getReferenceData?.() || null,
getMagickDataset: () => appRuntime.getMagickDataset?.() || null,
normalizeNumberValue,
selectNumberEntry,
elements: {
openHomeEl,
openCalendarEl,
openCalendarTimelineEl,
openCalendarMonthsEl,
openHolidaysEl,
openAudioEl,
openAudioCircleEl,
openAudioNotesEl,
openTarotEl,
openTarotHouseEl,
openAstronomyEl,
openPlanetsEl,
openCyclesEl,
openElementsEl,
openIChingEl,
openKabbalahEl,
openKabbalahTreeEl,
openKabbalahCubeEl,
openAlphabetEl,
openAlphabetWordEl,
openAlphabetLettersEl,
openAlphabetTextEl,
openNumbersEl,
openZodiacEl,
openNatalEl,
openQuizEl,
openGodsEl,
openEnochianEl
},
ensure: {
ensureTarotSection,
ensurePlanetSection,
ensureCyclesSection,
ensureElementsSection,
ensureIChingSection,
ensureKabbalahSection,
ensureCubeSection,
ensureAlphabetSection,
ensureAlphabetTextSection,
ensureZodiacSection,
ensureGodsSection,
ensureCalendarSection,
ensureAudioCircleSection
}
});
window.TarotNatal = {
...(window.TarotNatal || {}),
getSettings() {
return appRuntime.getCurrentSettings?.() || { ...currentSettings };
},
getContext() {
return settingsUi.buildNatalContext?.(appRuntime.getCurrentSettings?.() || currentSettings) || null;
},
buildContextFromSettings(settings) {
return settingsUi.buildNatalContext?.(settings) || null;
}
};
const initialSettings = settingsUi.loadInitialSettingsAndApply?.() || { ...DEFAULT_SETTINGS };
homeUi.syncNowSkyBackground?.({ latitude: initialSettings.latitude, longitude: initialSettings.longitude }, true);
bindConnectionGate();
void ensureConnectedApp();