diff --git a/app.js b/app.js
index 6db48c8..0f5c0e9 100644
--- a/app.js
+++ b/app.js
@@ -44,6 +44,7 @@ const tarotSectionEl = document.getElementById("tarot-section");
const tarotFrameSectionEl = document.getElementById("tarot-frame-section");
const tarotHouseSectionEl = document.getElementById("tarot-house-section");
const astronomySectionEl = document.getElementById("astronomy-section");
+const skySectionEl = document.getElementById("sky-section");
const natalSectionEl = document.getElementById("natal-section");
const planetSectionEl = document.getElementById("planet-section");
const cyclesSectionEl = document.getElementById("cycles-section");
@@ -96,6 +97,7 @@ const openNumbersEl = document.getElementById("open-numbers");
const openNumbersBrowseEl = document.getElementById("open-numbers-browse");
const openNumbersTheoryEl = document.getElementById("open-numbers-theory");
const openZodiacEl = document.getElementById("open-zodiac");
+const openSkyEl = document.getElementById("open-sky");
const openNatalEl = document.getElementById("open-natal");
const openQuizEl = document.getElementById("open-quiz");
const openGodsEl = document.getElementById("open-gods");
@@ -104,6 +106,7 @@ const latEl = document.getElementById("lat");
const lngEl = document.getElementById("lng");
const nowSkyLayerEl = document.getElementById("now-sky-layer");
const nowPanelEl = document.getElementById("now-panel");
+const homeWelcomeEl = document.getElementById("home-welcome");
const nowOverlayToggleEl = document.getElementById("now-overlay-toggle");
const connectionGateEl = document.getElementById("connection-gate");
const connectionGateBaseUrlEl = document.getElementById("connection-gate-base-url");
@@ -281,7 +284,7 @@ appRuntime.init?.({
calendarVisualsUi,
homeUi,
hasTarotAccess: () => hasTarotFeatureAccess(),
- shouldPollNow: () => (sectionStateUi.getActiveSection?.() || "home") === "home" && document.hidden !== true,
+ shouldPollNow: () => (sectionStateUi.getActiveSection?.() || "home") === "sky" && document.hidden !== true,
nowPollIntervalMs: 5 * 60 * 1000,
onStatus: (text) => setStatus(text),
services: {
@@ -554,6 +557,7 @@ sectionStateUi.init?.({
calendarEl,
monthStripEl,
nowPanelEl,
+ homeWelcomeEl,
settingsSectionEl,
timelineSectionEl,
calendarSectionEl,
@@ -564,6 +568,7 @@ sectionStateUi.init?.({
tarotFrameSectionEl,
tarotHouseSectionEl,
astronomySectionEl,
+ skySectionEl,
natalSectionEl,
planetSectionEl,
cyclesSectionEl,
@@ -615,6 +620,7 @@ sectionStateUi.init?.({
openNumbersBrowseEl,
openNumbersTheoryEl,
openZodiacEl,
+ openSkyEl,
openNatalEl,
openQuizEl,
openGodsEl,
@@ -739,6 +745,7 @@ navigationUi.init?.({
openNumbersBrowseEl,
openNumbersTheoryEl,
openZodiacEl,
+ openSkyEl,
openNatalEl,
openQuizEl,
openGodsEl,
diff --git a/app/app-runtime.js b/app/app-runtime.js
index 032a864..7663b99 100644
--- a/app/app-runtime.js
+++ b/app/app-runtime.js
@@ -126,11 +126,20 @@
function syncNowTickerState() {
if (config.shouldPollNow?.() === false) {
stopNowTicker();
+ config.homeUi?.clearNowSkyBackground?.();
return;
}
if (!nowInterval && referenceData && currentGeo) {
startNowTicker();
+ return;
+ }
+
+ if (nowInterval && referenceData && currentGeo) {
+ const now = new Date();
+ config.homeUi?.syncNowPanelTheme?.(now);
+ void config.services.updateNowPanel?.(referenceData, currentGeo, config.nowElements, currentTimeFormat);
+ config.calendarVisualsUi?.applyDynamicNowIndicatorVisual?.(now);
}
}
@@ -144,8 +153,10 @@
try {
const tarotAccessEnabled = config.hasTarotAccess?.() === true;
currentGeo = parseGeoInput();
- config.homeUi?.syncNowPanelTheme?.(new Date());
- config.homeUi?.syncNowSkyBackground?.(currentGeo);
+ if (config.shouldPollNow?.()) {
+ config.homeUi?.syncNowPanelTheme?.(new Date());
+ config.homeUi?.syncNowSkyBackground?.(currentGeo);
+ }
if (!referenceData || !magickDataset) {
setStatus(tarotAccessEnabled
diff --git a/app/data-service.js b/app/data-service.js
index bf1a0ee..8b07130 100644
--- a/app/data-service.js
+++ b/app/data-service.js
@@ -133,18 +133,26 @@
: undefined;
}
- async function fetchJson(path) {
+ async function fetchJson(path, { timeoutMs = 30000 } = {}) {
if (!path) {
throw new Error("API connection is not configured.");
}
- const response = await fetch(path, {
- headers: buildRequestHeaders()
- });
- if (!response.ok) {
- throw new Error(`Failed to load ${path} (${response.status})`);
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
+
+ try {
+ const response = await fetch(path, {
+ headers: buildRequestHeaders(),
+ signal: controller.signal
+ });
+ if (!response.ok) {
+ throw new Error(`Failed to load ${path} (${response.status})`);
+ }
+ return response.json();
+ } finally {
+ clearTimeout(timeoutId);
}
- return response.json();
}
function buildObjectPath(target, pathParts, value) {
@@ -240,7 +248,7 @@
}
const activeSection = String(window.TarotSectionStateUi?.getActiveSection?.() || "home").trim();
- return activeSection === "home";
+ return activeSection === "home" || activeSection === "sky";
}
function toApiAssetUrl(assetPath) {
diff --git a/app/styles.css b/app/styles.css
index f83eae8..64c59b6 100644
--- a/app/styles.css
+++ b/app/styles.css
@@ -5902,6 +5902,60 @@
font-size: 26px;
}
+ .alpha-text-settings-popover {
+ position: absolute;
+ top: 100%;
+ right: 0;
+ z-index: 100;
+ min-width: 280px;
+ max-width: 380px;
+ margin-top: 6px;
+ }
+
+ .alpha-text-settings-popover[hidden] {
+ display: none;
+ }
+
+ .alpha-text-settings-popover-inner {
+ background: #1a1a26;
+ border: 1px solid #3f3f46;
+ border-radius: 10px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
+ overflow: hidden;
+ }
+
+ .alpha-text-settings-popover-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 14px;
+ border-bottom: 1px solid #27272a;
+ color: #e4e4e7;
+ font-size: 13px;
+ }
+
+ .alpha-text-settings-close {
+ background: none;
+ border: none;
+ color: #a1a1aa;
+ font-size: 18px;
+ cursor: pointer;
+ padding: 2px 6px;
+ line-height: 1;
+ }
+
+ .alpha-text-settings-close:hover {
+ color: #e4e4e7;
+ }
+
+ .alpha-text-settings-popover .alpha-text-reader-toggle-list {
+ padding: 10px 14px;
+ }
+
+ .alpha-text-settings-popover .alpha-text-reader-toggle {
+ padding: 4px 0;
+ }
+
.alpha-text-search-controls--detail {
padding: 14px;
border: 1px solid #2f2f39;
@@ -6032,6 +6086,20 @@
display: none !important;
}
+ .planet-layout.alpha-text-global-search-only > .planet-detail-panel {
+ display: grid !important;
+ grid-template-rows: 1fr !important;
+ min-height: 0 !important;
+ overflow: auto !important;
+ }
+
+ .planet-layout.alpha-text-global-search-only .alpha-text-detail-body {
+ display: block !important;
+ min-height: 120px !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ }
+
@media (max-width: 1040px) {
.alpha-text-heading-tools {
grid-template-columns: 1fr;
@@ -6257,6 +6325,25 @@
box-shadow: inset 0 0 0 1px rgba(165, 180, 252, 0.22);
}
+ .alpha-text-verse.is-highlighted-fresh {
+ animation: alpha-text-verse-pulse 2.5s ease-out;
+ }
+
+ @keyframes alpha-text-verse-pulse {
+ 0% {
+ background: rgba(99, 102, 241, 0.35);
+ box-shadow: inset 0 0 0 2px rgba(165, 180, 252, 0.5), 0 0 24px rgba(99, 102, 241, 0.3);
+ }
+ 60% {
+ background: rgba(99, 102, 241, 0.2);
+ box-shadow: inset 0 0 0 1px rgba(165, 180, 252, 0.3), 0 0 8px rgba(99, 102, 241, 0.1);
+ }
+ 100% {
+ background: rgba(67, 56, 202, 0.18);
+ box-shadow: inset 0 0 0 1px rgba(165, 180, 252, 0.22);
+ }
+ }
+
.alpha-text-verse:first-child {
border-top: none;
padding-top: 0;
diff --git a/app/ui-alphabet-text.js b/app/ui-alphabet-text.js
index e4be4e9..6fa7695 100644
--- a/app/ui-alphabet-text.js
+++ b/app/ui-alphabet-text.js
@@ -85,6 +85,7 @@
searchError: "",
searchRequestId: 0,
highlightedVerseId: "",
+ highlightedVerseFresh: false,
displayPreferencesBySource: {},
showVerseHeads: readStoredBoolean(STORAGE_KEYS.showVerseHeads, true),
showSourceOverview: readStoredBoolean(STORAGE_KEYS.showSourceOverview, true),
@@ -125,6 +126,8 @@
let showExtraCardEl;
let readerFontSizeSelectEl;
let exportButtonEl;
+ let settingsBtnEl;
+ let settingsPopoverEl;
let lexiconPopupEl;
let lexiconPopupTitleEl;
let lexiconPopupSubtitleEl;
@@ -132,6 +135,15 @@
let lexiconPopupCloseEl;
let lexiconReturnFocusEl = null;
+ let searchTimer = null;
+
+ function debounceRunSearch(scope) {
+ clearTimeout(searchTimer);
+ searchTimer = setTimeout(() => {
+ void runSearch(scope);
+ }, 300);
+ }
+
function normalizeId(value) {
return String(value || "")
.trim()
@@ -155,6 +167,8 @@
showExtraCardEl = document.getElementById("alpha-text-show-extra-card");
readerFontSizeSelectEl = document.getElementById("alpha-text-font-size-select");
exportButtonEl = document.querySelector("#alphabet-text-section .detail-export-btn");
+ settingsBtnEl = document.getElementById("alpha-text-settings-btn");
+ settingsPopoverEl = document.getElementById("alpha-text-settings-popover");
translationSelectEl = document.getElementById("alpha-text-translation-select");
translationControlEl = translationSelectEl?.closest?.(".alpha-text-control") || null;
compareSelectEl = document.getElementById("alpha-text-compare-select");
@@ -757,7 +771,9 @@
input.value = "";
}
- state.searchQuery = "";
+ const keepQuery = preserveHighlight && state.searchQuery ? state.searchQuery : "";
+
+ state.searchQuery = preserveHighlight ? keepQuery : "";
state.searchResults = null;
state.searchLoading = false;
state.searchError = "";
@@ -766,8 +782,6 @@
if (!preserveHighlight) {
state.highlightedVerseId = "";
}
-
- updateSearchControls();
}
function getSourceDisplayCapabilities(source, passage) {
@@ -1924,10 +1938,34 @@
return null;
}
- const card = createCard("Search Results");
+ const payload = state.searchResults;
+ const totalMatches = Number(payload?.totalMatches) || 0;
const scopeLabel = state.activeSearchScope === "global"
? "all texts"
- : (state.searchResults?.scope?.source?.title || getSelectedSource()?.title || "current source");
+ : (payload?.scope?.source?.title || getSelectedSource()?.title || "current source");
+
+ const card = createCard("Search Results");
+
+ const diagnostic = document.createElement("div");
+ diagnostic.className = "alpha-text-search-diagnostic";
+ diagnostic.style.cssText = "padding:12px 16px;border:2px solid #ef4444;border-radius:8px;margin-bottom:12px;font-size:13px;line-height:1.6;color:#fca5a5;background:#1a0a0a";
+
+ const lines = [
+ `Query: "${state.searchQuery || "--"}"`,
+ `Scope: ${scopeLabel}`,
+ state.searchLoading
+ ? `Status: Searching...`
+ : state.searchError
+ ? `Status: Error: ${state.searchError}`
+ : totalMatches > 0
+ ? `Status: Found ${totalMatches} match${totalMatches !== 1 ? "es" : ""}${payload?.truncated ? ` (showing ${payload.resultCount})` : ""}`
+ : `Status: No matches found`,
+ payload ? `API Response: totalMatches=${totalMatches} resultCount=${payload.resultCount} truncated=${payload.truncated} results.length=${Array.isArray(payload.results) ? payload.results.length : "N/A"}` : "",
+ ``
+ ].filter(Boolean);
+
+ diagnostic.innerHTML = lines.join("
");
+ card.appendChild(diagnostic);
const summary = document.createElement("p");
summary.className = "alpha-text-search-summary";
@@ -1957,12 +1995,19 @@
return card;
}
- const payload = state.searchResults;
- const totalMatches = Number(payload?.totalMatches) || 0;
- const truncatedNote = payload?.truncated ? ` Showing the first ${payload.resultCount} results.` : "";
- summary.textContent = `${totalMatches} matches in ${scopeLabel}.${truncatedNote}`;
+ summary.textContent = "";
card.append(summary, actions);
+ if (state.searchLoading) {
+ card.appendChild(createEmptyMessage("Searching..."));
+ return card;
+ }
+
+ if (state.searchError) {
+ card.appendChild(createEmptyMessage(state.searchError));
+ return card;
+ }
+
if (!Array.isArray(payload?.results) || !payload.results.length) {
card.appendChild(createEmptyMessage(`No matches found for \"${state.searchQuery}\".`));
return card;
@@ -2027,7 +2072,18 @@
const globalSearchOnlyMode = isGlobalSearchOnlyMode();
setGlobalSearchHeadingMode(globalSearchOnlyMode);
- if (!source || !work || !section) {
+ if (!detailBodyEl) {
+ return;
+ }
+
+ if (!globalSearchOnlyMode && (!source || !work || !section)) {
+ if (state.searchQuery) {
+ const fallbackCard = createCard("Search");
+ fallbackCard.appendChild(createEmptyMessage(`Searching for "${state.searchQuery}"...`));
+ detailBodyEl.replaceChildren();
+ detailBodyEl.appendChild(fallbackCard);
+ return;
+ }
renderPlaceholder("Text Reader", "Select a source to begin", "Choose a text source and section from the left panel.");
renderLexiconPopup();
return;
@@ -2047,11 +2103,99 @@
return;
}
- detailBodyEl.replaceChildren();
+ if (globalSearchOnlyMode) {
+ const existing = document.getElementById("alpha-text-search-overlay");
+ if (existing) {
+ existing.remove();
+ }
+
+ if (!state.searchResults && !state.searchError && state.searchLoading) {
+ return;
+ }
+
+ const banner = document.createElement("div");
+ banner.id = "alpha-text-search-overlay";
+ banner.style.cssText = "position:fixed;inset:0;z-index:9999;background:#0c0c12;overflow:auto;padding:24px";
+ const header = document.createElement("div");
+ header.style.cssText = "margin-bottom:16px;display:flex;align-items:center;justify-content:space-between";
+ const title = document.createElement("h2");
+ title.style.cssText = "color:#e4e4e7;margin:0;font-size:20px";
+ title.textContent = state.searchQuery ? `Search: "${state.searchQuery}"` : "Search Results";
+ const closeBtn = document.createElement("button");
+ closeBtn.type = "button";
+ closeBtn.textContent = "✕ Close";
+ closeBtn.style.cssText = "padding:8px 16px;border:1px solid #3f3f46;border-radius:6px;background:#18181b;color:#d4d4d8;cursor:pointer";
+ closeBtn.addEventListener("click", () => {
+ banner.remove();
+ clearTimeout(searchTimer);
+ clearSearchState();
+ clearScopedSearch("global");
+ clearScopedSearch("source");
+ state.searchRequestId += 1;
+ setGlobalSearchHeadingMode(false);
+ void loadSelectedPassage();
+ });
+ header.append(title, closeBtn);
+
+ const content = document.createElement("div");
+ if (state.searchLoading) {
+ content.textContent = "Searching...";
+ content.style.cssText = "color:#a1a1aa;padding:24px;text-align:center";
+ } else if (state.searchError) {
+ content.textContent = state.searchError;
+ content.style.cssText = "color:#f87171;padding:24px";
+ } else if (state.searchResults) {
+ const p = state.searchResults;
+ const summary = document.createElement("p");
+ summary.style.cssText = "color:#a1a1aa;margin:0 0 16px";
+ const matchCount = p.truncated ? p.resultCount : p.totalMatches;
+ summary.textContent = `${matchCount}${p.truncated ? "+" : ""} match${matchCount !== 1 ? "es" : ""}`;
+ content.appendChild(summary);
+ if (Array.isArray(p.results)) {
+ p.results.forEach((r) => {
+ const btn = document.createElement("button");
+ btn.type = "button";
+ const isActive = normalizeId(r?.sourceId) === normalizeId(state.selectedSourceId)
+ && normalizeId(r?.workId) === normalizeId(state.selectedWorkId)
+ && normalizeId(r?.sectionId) === normalizeId(state.selectedSectionId)
+ && normalizeId(r?.verseId) === normalizeId(state.highlightedVerseId);
+ btn.style.cssText = "display:block;width:100%;text-align:left;padding:10px 14px;margin-bottom:8px;border:2px solid "
+ + (isActive ? "#818cf8" : "#3f3f46")
+ + ";border-radius:8px;background:" + (isActive ? "rgba(99,102,241,0.15)" : "#18181b")
+ + ";color:#d4d4d8;cursor:pointer";
+
+ const refEl = document.createElement("strong");
+ refEl.textContent = r.reference || "";
+ btn.appendChild(refEl);
+ btn.appendChild(document.createElement("br"));
+
+ const previewEl = document.createElement("small");
+ appendHighlightedText(previewEl, (r.preview || "").slice(0, 200), state.searchQuery);
+ btn.appendChild(previewEl);
+ btn.addEventListener("click", () => {
+ state.highlightedVerseFresh = true;
+ banner.remove();
+ void openSearchResult(r);
+ });
+ content.appendChild(btn);
+ });
+ }
+ } else {
+ content.textContent = "No results.";
+ content.style.cssText = "color:#a1a1aa;padding:24px";
+ }
+
+ banner.append(header, content);
+ document.body.appendChild(banner);
+ return;
+ }
const searchCard = createSearchCard();
- if (searchCard) {
+
+ if (searchCard && (globalSearchOnlyMode || state.searchLoading)) {
+ detailBodyEl.replaceChildren();
detailBodyEl.appendChild(searchCard);
+ return;
}
if (globalSearchOnlyMode) {
@@ -2059,6 +2203,8 @@
return;
}
+ detailBodyEl.replaceChildren();
+
if (!state.currentPassage) {
const loadingCard = createCard("Text Reader");
loadingCard.appendChild(createEmptyMessage("Loading section…"));
@@ -2160,7 +2306,19 @@
renderDetail();
if (state.highlightedVerseId) {
- requestAnimationFrame(scrollHighlightedVerseIntoView);
+ requestAnimationFrame(() => {
+ scrollHighlightedVerseIntoView();
+ if (state.highlightedVerseFresh) {
+ const verseEl = detailBodyEl?.querySelector?.(".alpha-text-verse.is-highlighted");
+ if (verseEl instanceof HTMLElement) {
+ verseEl.classList.add("is-highlighted-fresh");
+ setTimeout(() => {
+ verseEl.classList.remove("is-highlighted-fresh");
+ }, 2600);
+ }
+ state.highlightedVerseFresh = false;
+ }
+ });
}
}
@@ -2185,15 +2343,13 @@
updateSearchControls();
if (!query) {
- clearSearchState();
+ state.searchLoading = false;
+ state.searchError = `Search query is empty (scope: ${normalizedScope}, input: ${getSearchInput(normalizedScope) ? "found" : "missing"}, stored: "${getStoredSearchQuery(normalizedScope) || "none"}")`;
+ state.searchResults = null;
renderDetail();
return;
}
- if (normalizedScope === "global" || normalizedScope === "source") {
- showDetailOnlyMode();
- }
-
const requestId = state.searchRequestId + 1;
state.searchRequestId = requestId;
state.searchLoading = true;
@@ -2242,7 +2398,7 @@
showDetailOnlyMode();
await loadSelectedPassage();
clearActiveSearchUi({ preserveHighlight: true });
- renderDetail();
+ updateSearchControls();
}
function bindControls() {
@@ -2253,7 +2409,7 @@
if (globalSearchFormEl instanceof HTMLFormElement) {
globalSearchFormEl.addEventListener("submit", (event) => {
event.preventDefault();
- void runSearch("global");
+ debounceRunSearch("global");
});
}
@@ -2278,7 +2434,7 @@
if (localSearchFormEl instanceof HTMLFormElement) {
localSearchFormEl.addEventListener("submit", (event) => {
event.preventDefault();
- void runSearch("source");
+ void debounceRunSearch("source");
});
}
@@ -2441,6 +2597,35 @@
});
}
+ if (settingsBtnEl instanceof HTMLButtonElement && settingsPopoverEl instanceof HTMLElement) {
+ settingsBtnEl.addEventListener("click", () => {
+ const isOpen = settingsPopoverEl.hidden === false;
+ settingsPopoverEl.hidden = isOpen;
+ settingsBtnEl.setAttribute("aria-expanded", isOpen ? "false" : "true");
+ });
+
+ const closeBtn = settingsPopoverEl.querySelector(".alpha-text-settings-close");
+ if (closeBtn instanceof HTMLButtonElement) {
+ closeBtn.addEventListener("click", () => {
+ settingsPopoverEl.hidden = true;
+ settingsBtnEl.setAttribute("aria-expanded", "false");
+ });
+ }
+
+ document.addEventListener("click", (event) => {
+ if (settingsPopoverEl.hidden) {
+ return;
+ }
+ const target = event.target;
+ if (target instanceof Node) {
+ if (!settingsPopoverEl.contains(target) && target !== settingsBtnEl) {
+ settingsPopoverEl.hidden = true;
+ settingsBtnEl.setAttribute("aria-expanded", "false");
+ }
+ }
+ });
+ }
+
state.initialized = true;
}
diff --git a/app/ui-chrome.js b/app/ui-chrome.js
index 587eac2..1af91fa 100644
--- a/app/ui-chrome.js
+++ b/app/ui-chrome.js
@@ -488,62 +488,99 @@
throw new Error("Detail pane is not ready to export.");
}
- exportClone.querySelectorAll(".detail-pane-export-controls").forEach((node) => node.remove());
- exportClone.querySelectorAll(".detail-sequence-btn:not(.detail-export-btn)").forEach((node) => { node.style.display = "none"; });
- exportClone.querySelectorAll(".detail-sequence-position").forEach((node) => { node.style.display = "none"; });
- exportClone.querySelectorAll(".alpha-text-reader-nav-btn").forEach((node) => { node.style.display = "none"; });
- exportClone.querySelectorAll(".alpha-text-reader-panel").forEach((node) => { node.style.display = "none"; });
+ const exportStripSelectors = [
+ ".detail-sequence-nav",
+ ".detail-pane-export-controls",
+ ".alpha-text-reader-nav-btn",
+ ".alpha-text-reader-panel",
+ ".alpha-text-heading-tools",
+ ".alpha-text-search-controls",
+ ".alpha-text-controls--heading",
+ ".alpha-text-reader-toggle-control",
+ ".alpha-text-compare-toggle",
+ ".alpha-text-translation-select",
+ ".alpha-text-compare-select",
+ ".alpha-text-work-select",
+ ".alpha-text-section-select",
+ ".planet-detail-summary"
+ ];
- const contentWidth = Math.max(
- 320,
- Math.ceil(detailPanel.clientWidth || detailPanel.getBoundingClientRect().width || 0)
- );
- const contentHeight = Math.max(
- 240,
- Math.ceil(detailPanel.scrollHeight || detailPanel.getBoundingClientRect().height || 0)
- );
+ exportStripSelectors.forEach((selector) => {
+ exportClone.querySelectorAll(selector).forEach((node) => node.remove());
+ });
+
+ exportClone.querySelectorAll("*").forEach((node) => {
+ if (node instanceof HTMLElement && node.hidden) {
+ node.remove();
+ }
+ });
+
+ exportClone.style.position = "fixed";
+ exportClone.style.left = "-100000px";
+ exportClone.style.top = "0";
+ exportClone.style.visibility = "hidden";
+ exportClone.style.width = "max-content";
+ exportClone.style.maxWidth = "900px";
+ exportClone.style.minWidth = "280px";
+ exportClone.style.height = "auto";
+ exportClone.style.overflow = "visible";
+ exportClone.style.padding = "24px";
+ exportClone.style.boxSizing = "border-box";
+ document.body.appendChild(exportClone);
+
+ const measureWidth = Math.ceil(exportClone.getBoundingClientRect().width || 280);
+ document.body.removeChild(exportClone);
+
+ exportClone.style.position = "";
+ exportClone.style.left = "";
+ exportClone.style.top = "";
+ exportClone.style.visibility = "";
sandboxEl = document.createElement("div");
sandboxEl.style.position = "fixed";
sandboxEl.style.left = "-100000px";
sandboxEl.style.top = "0";
- sandboxEl.style.width = `${contentWidth}px`;
- sandboxEl.style.height = `${contentHeight}px`;
- sandboxEl.style.overflow = "hidden";
sandboxEl.style.pointerEvents = "none";
sandboxEl.style.opacity = "0";
- exportClone.style.width = `${contentWidth}px`;
- exportClone.style.minWidth = `${contentWidth}px`;
- exportClone.style.maxWidth = `${contentWidth}px`;
- exportClone.style.height = `${contentHeight}px`;
- exportClone.style.minHeight = `${contentHeight}px`;
- exportClone.style.maxHeight = `${contentHeight}px`;
+ exportClone.style.width = `${measureWidth}px`;
+ exportClone.style.minWidth = "";
+ exportClone.style.maxWidth = "";
+ exportClone.style.height = "auto";
+ exportClone.style.minHeight = "";
+ exportClone.style.maxHeight = "";
exportClone.style.overflow = "visible";
+ exportClone.style.padding = "24px";
+ exportClone.style.boxSizing = "border-box";
sandboxEl.appendChild(exportClone);
document.body.appendChild(sandboxEl);
- const exportScale = Math.max(2, Math.min(2.5, Number(window.devicePixelRatio) || 1));
+ const exportScale = 2;
const canvas = await html2canvas(exportClone, {
- backgroundColor: null,
+ backgroundColor: "#0c0c12",
scale: exportScale,
useCORS: true,
allowTaint: false,
logging: false,
- width: contentWidth,
- height: contentHeight,
- windowWidth: contentWidth,
- windowHeight: contentHeight,
- scrollX: 0,
- scrollY: 0,
imageTimeout: 12000,
onclone(clonedDocument) {
- clonedDocument.querySelectorAll(".detail-pane-export-controls").forEach((node) => node.remove());
- clonedDocument.querySelectorAll(".detail-sequence-btn:not(.detail-export-btn)").forEach((node) => { node.style.display = "none"; });
- clonedDocument.querySelectorAll(".detail-sequence-position").forEach((node) => { node.style.display = "none"; });
- clonedDocument.querySelectorAll(".alpha-text-reader-nav-btn").forEach((node) => { node.style.display = "none"; });
- clonedDocument.querySelectorAll(".alpha-text-reader-panel").forEach((node) => { node.style.display = "none"; });
+ clonedDocument.querySelectorAll(
+ ".detail-sequence-nav, .detail-pane-export-controls, .alpha-text-reader-nav-btn, .alpha-text-reader-panel, .alpha-text-heading-tools, .alpha-text-search-controls, .alpha-text-controls--heading, .alpha-text-reader-toggle-control, .alpha-text-compare-toggle, .planet-detail-summary"
+ ).forEach((node) => node.remove());
+ clonedDocument.querySelectorAll("*").forEach((node) => {
+ if (node instanceof HTMLElement && node.hidden) {
+ node.remove();
+ }
+ });
+ const detailClone = clonedDocument.querySelector(".planet-detail-panel, .tarot-detail-panel, .kab-detail-panel");
+ if (detailClone instanceof HTMLElement) {
+ detailClone.style.padding = "24px";
+ detailClone.style.boxSizing = "border-box";
+ detailClone.style.width = "max-content";
+ detailClone.style.maxWidth = "900px";
+ detailClone.style.minWidth = "280px";
+ }
}
});
diff --git a/app/ui-home-calendar.js b/app/ui-home-calendar.js
index dcd3511..b6dca2f 100644
--- a/app/ui-home-calendar.js
+++ b/app/ui-home-calendar.js
@@ -81,7 +81,7 @@
return;
}
- nowSkyLayerEl.removeAttribute("src");
+ nowSkyLayerEl.src = "about:blank";
lastNowSkyGeoKey = "";
lastNowSkySourceUrl = "";
}
@@ -164,6 +164,7 @@
...(window.TarotHomeUi || {}),
init,
syncNowSkyBackground,
- syncNowPanelTheme
+ syncNowPanelTheme,
+ clearNowSkyBackground
};
})();
diff --git a/app/ui-navigation.js b/app/ui-navigation.js
index 1fccc3c..aeb28f2 100644
--- a/app/ui-navigation.js
+++ b/app/ui-navigation.js
@@ -249,6 +249,10 @@
setActiveSection(getActiveSection() === "zodiac" ? "home" : "zodiac");
});
+ bindClick(elements.openSkyEl, () => {
+ setActiveSection(getActiveSection() === "sky" ? "home" : "sky");
+ });
+
bindClick(elements.openNatalEl, () => {
setActiveSection(getActiveSection() === "natal" ? "home" : "natal");
});
diff --git a/app/ui-section-state.js b/app/ui-section-state.js
index 6eac40e..18ac87c 100644
--- a/app/ui-section-state.js
+++ b/app/ui-section-state.js
@@ -15,6 +15,7 @@
"astronomy",
"planets",
"cycles",
+ "sky",
"natal",
"elements",
"iching",
@@ -78,7 +79,6 @@
requestAnimationFrame(() => {
config.calendar?.render?.();
config.calendarVisualsUi?.updateMonthStrip?.();
- config.homeUi?.syncNowPanelTheme?.(new Date());
});
}
@@ -116,11 +116,12 @@
const isTarotHouseOpen = activeSection === "tarot-house";
const isTarotMenuOpen = isTarotOpen || isTarotFrameOpen || isTarotHouseOpen;
const isAstronomyOpen = activeSection === "astronomy";
+ const isSkyOpen = activeSection === "sky";
const isPlanetOpen = activeSection === "planets";
const isCyclesOpen = activeSection === "cycles";
const isNatalOpen = activeSection === "natal";
const isZodiacOpen = activeSection === "zodiac";
- const isAstronomyMenuOpen = isAstronomyOpen || isPlanetOpen || isCyclesOpen || isZodiacOpen || isNatalOpen;
+ const isAstronomyMenuOpen = isAstronomyOpen || isPlanetOpen || isCyclesOpen || isZodiacOpen || isNatalOpen || isSkyOpen;
const isElementsOpen = activeSection === "elements";
const isIChingOpen = activeSection === "iching";
const isKabbalahOpen = activeSection === "kabbalah";
@@ -149,6 +150,7 @@
setHidden(elements.tarotFrameSectionEl, !isTarotFrameOpen);
setHidden(elements.tarotHouseSectionEl, !isTarotHouseOpen);
setHidden(elements.astronomySectionEl, !isAstronomyOpen);
+ setHidden(elements.skySectionEl, !isSkyOpen);
setHidden(elements.planetSectionEl, !isPlanetOpen);
setHidden(elements.cyclesSectionEl, !isCyclesOpen);
setHidden(elements.natalSectionEl, !isNatalOpen);
@@ -168,7 +170,8 @@
setHidden(elements.quizSectionEl, !isQuizOpen);
setHidden(elements.godsSectionEl, !isGodsOpen);
setHidden(elements.enochianSectionEl, !isEnochianOpen);
- setHidden(elements.nowPanelEl, !isHomeOpen);
+ setHidden(elements.nowPanelEl, !isSkyOpen);
+ setHidden(elements.homeWelcomeEl, !isHomeOpen);
setPressed(elements.openHomeEl, isHomeOpen);
setPressed(elements.openSettingsEl, isSettingsOpen);
diff --git a/index.html b/index.html
index e0c24da..3d7563a 100644
--- a/index.html
+++ b/index.html
@@ -39,6 +39,7 @@
+