updated the calendar and settings pages, added a new calendar page, and made some minor styling changes to the app.

This commit is contained in:
2026-04-15 23:15:45 -07:00
parent 4872e814c9
commit 5902d5f0b1
5 changed files with 222 additions and 26 deletions
+6 -3
View File
@@ -133,7 +133,9 @@ const DEFAULT_SETTINGS = {
longitude: -0.1278,
timeFormat: "minutes",
birthDate: "",
tarotDeck: DEFAULT_TAROT_DECK
tarotDeck: DEFAULT_TAROT_DECK,
stellariumBackgroundEnabled: false,
hasExplicitLocation: false
};
const PLANET_CALENDAR_STYLES = {
@@ -556,7 +558,7 @@ settingsUi.init?.({
appRuntime.applySettings?.(settings);
currentSettings = settings;
},
onSyncSkyBackground: (geo, force) => homeUi.syncNowSkyBackground?.(geo, force),
onSyncSkyBackground: (geo, options) => homeUi.syncNowSkyBackground?.(geo, options),
onStatus: (text) => setStatus(text),
onConnectionSaved: async () => ensureConnectedApp(),
getActiveSection: () => sectionStateUi.getActiveSection?.() || "home",
@@ -579,7 +581,8 @@ calendarVisualsUi.init?.({
homeUi.init?.({
nowSkyLayerEl,
nowPanelEl,
getCurrentGeo: () => appRuntime.getCurrentGeo?.() || null
getCurrentGeo: () => appRuntime.getCurrentGeo?.() || null,
getCurrentSettings: () => appRuntime.getCurrentSettings?.() || currentSettings
});
if (nowOverlayToggleEl && nowPanelEl) {
+33
View File
@@ -7023,6 +7023,32 @@
.settings-field input[type="password"] {
letter-spacing: 0.04em;
}
.settings-toggle-field {
gap: 8px;
}
.settings-toggle-row {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
border: 1px solid #3f3f46;
border-radius: 8px;
background: #09090b;
color: #f4f4f5;
}
.settings-toggle-row input {
width: auto;
margin: 0;
accent-color: #f4f4f5;
}
.settings-toggle-row:has(input:disabled) {
opacity: 0.6;
}
.settings-field-hint {
color: #a1a1aa;
font-size: 12px;
line-height: 1.4;
}
.settings-actions {
margin-top: 12px;
display: flex;
@@ -7150,6 +7176,13 @@
#now-panel.is-overlay-hidden::after {
opacity: 0;
}
#now-panel.is-sky-disabled #now-sky-layer {
opacity: 0;
}
#now-panel.is-sky-disabled::before,
#now-panel.is-sky-disabled::after {
opacity: 0;
}
.now-panel-controls {
position: absolute;
top: 14px;
+47 -4
View File
@@ -19,6 +19,10 @@
return config.getCurrentGeo?.() || null;
}
function getCurrentSettings() {
return config.getCurrentSettings?.() || null;
}
function normalizeGeoForSky(geo) {
const latitude = Number(geo?.latitude);
const longitude = Number(geo?.longitude);
@@ -51,14 +55,53 @@
return wrapperUrl.toString();
}
function syncNowSkyBackground(geo, force = false) {
function normalizeSkySyncOptions(optionsOrForce = false) {
if (typeof optionsOrForce === "boolean") {
return { force: optionsOrForce };
}
return {
force: Boolean(optionsOrForce?.force),
backgroundEnabled: optionsOrForce?.backgroundEnabled,
hasExplicitLocation: optionsOrForce?.hasExplicitLocation
};
}
function clearNowSkyBackground() {
const nowSkyLayerEl = getNowSkyLayerEl();
if (!nowSkyLayerEl || !geo) {
if (!nowSkyLayerEl) {
return;
}
nowSkyLayerEl.removeAttribute("src");
lastNowSkyGeoKey = "";
lastNowSkySourceUrl = "";
}
function syncNowSkyBackground(geo, optionsOrForce = false) {
const nowSkyLayerEl = getNowSkyLayerEl();
const nowPanelEl = getNowPanelEl();
if (!nowSkyLayerEl) {
return;
}
const options = normalizeSkySyncOptions(optionsOrForce);
const settings = getCurrentSettings();
const backgroundEnabled = typeof options.backgroundEnabled === "boolean"
? options.backgroundEnabled
: settings?.stellariumBackgroundEnabled === true;
const hasExplicitLocation = typeof options.hasExplicitLocation === "boolean"
? options.hasExplicitLocation
: settings?.hasExplicitLocation === true;
const normalizedGeo = normalizeGeoForSky(geo);
if (!normalizedGeo) {
const shouldLoadSky = Boolean(backgroundEnabled && hasExplicitLocation && normalizedGeo);
if (nowPanelEl) {
nowPanelEl.classList.toggle("is-sky-disabled", !shouldLoadSky);
}
if (!shouldLoadSky) {
clearNowSkyBackground();
return;
}
@@ -68,7 +111,7 @@
return;
}
if (!force && geoKey === lastNowSkyGeoKey && stellariumUrl === lastNowSkySourceUrl) {
if (!options.force && geoKey === lastNowSkyGeoKey && stellariumUrl === lastNowSkySourceUrl) {
return;
}
+124 -15
View File
@@ -9,7 +9,9 @@
longitude: -0.1278,
timeFormat: "minutes",
birthDate: "",
tarotDeck: "ceremonial-magick"
tarotDeck: "ceremonial-magick",
stellariumBackgroundEnabled: false,
hasExplicitLocation: false
},
onSettingsApplied: null,
onSyncSkyBackground: null,
@@ -31,12 +33,57 @@
timeFormatEl: document.getElementById("time-format"),
birthDateEl: document.getElementById("birth-date"),
tarotDeckEl: document.getElementById("tarot-deck"),
stellariumBackgroundEl: document.getElementById("stellarium-background"),
stellariumBackgroundHintEl: document.getElementById("stellarium-background-hint"),
apiBaseUrlEl: document.getElementById("api-base-url"),
apiKeyEl: document.getElementById("api-key"),
saveSettingsEl: document.getElementById("save-settings"),
useLocationEl: document.getElementById("use-location")
};
}
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(),
@@ -75,9 +122,9 @@
}
}
function syncSky(geo, force) {
function syncSky(geo, options) {
if (typeof config.onSyncSkyBackground === "function") {
config.onSyncSkyBackground(geo, force);
config.onSyncSkyBackground(geo, options);
}
}
@@ -152,13 +199,35 @@
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: parseStoredNumber(settings?.latitude, config.defaultSettings.latitude),
longitude: parseStoredNumber(settings?.longitude, config.defaultSettings.longitude),
latitude,
longitude,
timeFormat: normalizeTimeFormat(settings?.timeFormat),
birthDate: normalizeBirthDate(settings?.birthDate),
tarotDeck: normalizeTarotDeck(settings?.tarotDeck)
tarotDeck: normalizeTarotDeck(settings?.tarotDeck),
stellariumBackgroundEnabled: parseStoredBoolean(settings?.stellariumBackgroundEnabled, false) && hasExplicitLocation,
hasExplicitLocation
};
}
@@ -285,7 +354,7 @@
}
function applySettingsToInputs(settings) {
const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl } = getElements();
const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl, stellariumBackgroundEl } = getElements();
syncTarotDeckInputOptions();
syncConnectionInputs();
const normalized = normalizeSettings(settings);
@@ -296,6 +365,11 @@
if (tarotDeckEl) {
tarotDeckEl.value = normalized.tarotDeck;
}
setLocationEntryState(normalized.hasExplicitLocation);
if (stellariumBackgroundEl) {
stellariumBackgroundEl.checked = normalized.stellariumBackgroundEnabled;
}
syncStellariumBackgroundAvailability();
if (window.TarotCardImages?.setActiveDeck) {
window.TarotCardImages.setActiveDeck(normalized.tarotDeck);
}
@@ -304,9 +378,16 @@
}
function getSettingsFromInputs() {
const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl } = getElements();
const latitude = Number(latEl.value);
const longitude = Number(lngEl.value);
const { latEl, lngEl, timeFormatEl, birthDateEl, tarotDeckEl, 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.");
@@ -317,7 +398,9 @@
longitude,
timeFormat: normalizeTimeFormat(timeFormatEl.value),
birthDate: normalizeBirthDate(birthDateEl.value),
tarotDeck: normalizeTarotDeck(tarotDeckEl?.value)
tarotDeck: normalizeTarotDeck(tarotDeckEl?.value),
stellariumBackgroundEnabled: Boolean(stellariumBackgroundEl?.checked),
hasExplicitLocation: hasExplicitLocationEntry()
});
}
@@ -363,7 +446,14 @@
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);
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") {
@@ -396,8 +486,16 @@
({ 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.");
markLocationAsExplicit();
syncSky(
{ latitude: coords.latitude, longitude: coords.longitude },
{
force: true,
backgroundEnabled: Boolean(getElements().stellariumBackgroundEl?.checked),
hasExplicitLocation: true
}
);
setStatus("Location set from browser. Save Settings to use it across the app.");
},
(err) => {
const detail = err?.message || `code ${err?.code ?? "unknown"}`;
@@ -414,7 +512,9 @@
openSettingsEl,
closeSettingsEl,
settingsPopupEl,
settingsPopupCardEl
settingsPopupCardEl,
latEl,
lngEl
} = getElements();
if (saveSettingsEl) {
@@ -427,6 +527,15 @@
useLocationEl.addEventListener("click", requestGeoLocation);
}
[latEl, lngEl].forEach((inputEl) => {
if (!inputEl) {
return;
}
inputEl.addEventListener("input", markLocationAsExplicit);
inputEl.addEventListener("change", markLocationAsExplicit);
});
if (openSettingsEl) {
openSettingsEl.addEventListener("click", (event) => {
event.stopPropagation();
+12 -4
View File
@@ -16,7 +16,7 @@
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css">
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-700.css">
<link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css">
<link rel="stylesheet" href="app/styles.css?v=20260408-frame-flip-04">
<link rel="stylesheet" href="app/styles.css?v=20260415-stellarium-toggle-01">
</head>
<body>
<div class="topbar">
@@ -126,6 +126,14 @@
<option value="ceremonial-magick" selected>Loading deck manifests...</option>
</select>
</label>
<label class="settings-field settings-field-full settings-toggle-field" for="stellarium-background">
<span>Stellarium Background</span>
<span class="settings-toggle-row">
<input id="stellarium-background" type="checkbox">
<span>Enable live sky background</span>
</span>
<small id="stellarium-background-hint" class="settings-field-hint">Enter a location or use My Location before enabling the live sky background.</small>
</label>
<label class="settings-field settings-field-full">API Base URL
<input id="api-base-url" type="url" inputmode="url" placeholder="http://localhost:3100">
</label>
@@ -1199,15 +1207,15 @@
<script src="app/ui-numbers.js"></script>
<script src="app/ui-tarot-spread.js"></script>
<script src="app/ui-tarot-frame.js?v=20260408-frame-flip-04"></script>
<script src="app/ui-settings.js?v=20260309-gate"></script>
<script src="app/ui-settings.js?v=20260415-stellarium-toggle-01"></script>
<script src="app/ui-chrome.js?v=20260328-topbar-settings-01"></script>
<script src="app/ui-navigation.js?v=20260401-tarot-frame-01"></script>
<script src="app/ui-calendar-formatting.js?v=20260307b"></script>
<script src="app/ui-calendar-visuals.js?v=20260307b"></script>
<script src="app/ui-home-calendar.js"></script>
<script src="app/ui-home-calendar.js?v=20260415-stellarium-toggle-01"></script>
<script src="app/ui-section-state.js?v=20260401-tarot-frame-01"></script>
<script src="app/app-runtime.js?v=20260309-gate"></script>
<script src="app.js?v=20260402-frame-lightbox-01"></script>
<script src="app.js?v=20260415-stellarium-toggle-01"></script>
<script src="app/navigation-detail-test-harness.js?v=20260401-universal-detail-02"></script>
</body>
</html>