diff --git a/app.js b/app.js
index dc199f2..747daf5 100644
--- a/app.js
+++ b/app.js
@@ -75,6 +75,7 @@ const openAudioNotesEl = document.getElementById("open-audio-notes");
const openTarotEl = document.getElementById("open-tarot");
const openTarotFrameEl = document.getElementById("open-tarot-frame");
const openTarotHouseEl = document.getElementById("open-tarot-house");
+const settingsTarotPanelEl = document.getElementById("settings-tarot-panel");
const openAstronomyEl = document.getElementById("open-astronomy");
const openPlanetsEl = document.getElementById("open-planets");
const openCyclesEl = document.getElementById("open-cycles");
@@ -107,6 +108,9 @@ const connectionGateBaseUrlEl = document.getElementById("connection-gate-base-ur
const connectionGateApiKeyEl = document.getElementById("connection-gate-api-key");
const connectionGateStatusEl = document.getElementById("connection-gate-status");
const connectionGateConnectEl = document.getElementById("connection-gate-connect");
+const tarotDropdownEl = openTarotEl?.closest(".topbar-dropdown") || null;
+
+const TAROT_RESTRICTED_SECTIONS = new Set(["tarot", "tarot-frame", "tarot-house"]);
const nowElements = {
nowHourEl: document.getElementById("now-hour"),
@@ -128,6 +132,34 @@ const nowElements = {
nowStatsPlanetsEl: document.getElementById("now-stats-planets")
};
+function hasTarotFeatureAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+}
+
+function isSectionAccessible(section) {
+ if (TAROT_RESTRICTED_SECTIONS.has(String(section || "").trim())) {
+ return hasTarotFeatureAccess();
+ }
+
+ return true;
+}
+
+function syncTarotFeatureVisibility() {
+ const tarotVisible = hasTarotFeatureAccess();
+
+ if (tarotDropdownEl) {
+ tarotDropdownEl.hidden = !tarotVisible;
+ }
+
+ if (settingsTarotPanelEl) {
+ settingsTarotPanelEl.hidden = !tarotVisible;
+ }
+
+ if (!tarotVisible && TAROT_RESTRICTED_SECTIONS.has(sectionStateUi.getActiveSection?.())) {
+ sectionStateUi.setActiveSection?.("home");
+ }
+}
+
const baseWeekOptions = {
hourStart: 0,
hourEnd: 24,
@@ -245,6 +277,7 @@ appRuntime.init?.({
nowElements,
calendarVisualsUi,
homeUi,
+ hasTarotAccess: () => hasTarotFeatureAccess(),
onStatus: (text) => setStatus(text),
services: {
getCenteredWeekStartDay,
@@ -390,6 +423,10 @@ function getConnectionSettingsFromGate() {
}
function warmAllDeckImagesInBackground() {
+ if (!hasTarotFeatureAccess()) {
+ return;
+ }
+
const activeDeckId = String(window.TarotCardImages?.getActiveDeck?.() || "").trim();
window.TarotCardImages?.scheduleAllDeckImagePreload?.({
@@ -426,6 +463,9 @@ async function ensureConnectedApp(nextConnectionSettings = null) {
syncConnectionGateInputs(configuredConnection);
}
+ window.TarotAppConfig?.updateConnectionAccess?.(probeResult);
+ syncTarotFeatureVisibility();
+
hideConnectionGate();
if (!hasRenderedConnectedShell) {
sectionStateUi.setActiveSection?.("home");
@@ -500,6 +540,7 @@ sectionStateUi.init?.({
settingsUi,
calendarVisualsUi,
homeUi,
+ isSectionAccessible: (section) => isSectionAccessible(section),
getReferenceData: () => appRuntime.getReferenceData?.() || null,
getMagickDataset: () => appRuntime.getMagickDataset?.() || null,
elements: {
@@ -637,6 +678,10 @@ if (nowOverlayToggleEl && nowPanelEl) {
syncNowOverlayVisibility();
}
+document.addEventListener("connection:access-updated", () => {
+ syncTarotFeatureVisibility();
+});
+
navigationUi.init?.({
tarotSpreadUi,
getActiveSection: () => sectionStateUi.getActiveSection?.() || "home",
@@ -714,5 +759,6 @@ window.TarotNatal = {
const initialSettings = settingsUi.loadInitialSettingsAndApply?.() || { ...DEFAULT_SETTINGS };
homeUi.syncNowSkyBackground?.({ latitude: initialSettings.latitude, longitude: initialSettings.longitude }, true);
+syncTarotFeatureVisibility();
bindConnectionGate();
void ensureConnectedApp();
diff --git a/app/app-config.js b/app/app-config.js
index 360cd0b..1ac4eea 100644
--- a/app/app-config.js
+++ b/app/app-config.js
@@ -1,6 +1,20 @@
(function () {
const apiBaseUrlStorageKey = "tarot-time-api-base-url";
const apiKeyStorageKey = "tarot-time-api-key";
+ const defaultConnectionAccess = Object.freeze({
+ connected: false,
+ apiKeyRequired: false,
+ authenticated: false,
+ clientId: "",
+ accountId: "",
+ accessLevel: "",
+ roles: Object.freeze([]),
+ scopes: Object.freeze([]),
+ capabilities: Object.freeze({
+ tarot: false,
+ adminApiManagement: false
+ })
+ });
function normalizeBaseUrl(value) {
return String(value || "")
@@ -12,6 +26,55 @@
return String(value || "").trim();
}
+ function normalizeStringList(values) {
+ return Array.isArray(values)
+ ? values.map((value) => String(value || "").trim()).filter(Boolean)
+ : [];
+ }
+
+ function normalizeAccessLevel(value) {
+ return String(value || "").trim().toLowerCase();
+ }
+
+ function normalizeConnectionAccess(source = null) {
+ const health = source?.health || {};
+ const auth = health?.auth || source?.auth || {};
+ const roles = normalizeStringList(auth?.roles);
+ const scopes = normalizeStringList(auth?.scopes);
+ const apiKeyRequired = health?.apiKeyRequired === true || source?.apiKeyRequired === true;
+ const connected = source?.ok === true || source?.connected === true;
+ const accessLevel = normalizeAccessLevel(
+ auth?.accessLevel
+ || source?.accessLevel
+ || (connected && !apiKeyRequired ? "premium" : "")
+ );
+ const tarotCapability = source?.capabilities?.tarot === true
+ || (connected && !apiKeyRequired)
+ || accessLevel === "premium";
+ const adminApiManagementCapability = source?.capabilities?.adminApiManagement === true
+ || roles.includes("admin")
+ || scopes.includes("api:admin");
+
+ return {
+ connected,
+ apiKeyRequired,
+ authenticated: auth?.authenticated === true,
+ clientId: String(auth?.clientId || source?.clientId || "").trim(),
+ accountId: String(auth?.accountId || source?.accountId || "").trim(),
+ accessLevel,
+ roles,
+ scopes,
+ capabilities: {
+ tarot: tarotCapability,
+ adminApiManagement: adminApiManagementCapability
+ }
+ };
+ }
+
+ function sameConnectionAccess(left, right) {
+ return JSON.stringify(left) === JSON.stringify(right);
+ }
+
function normalizeConnectionSettings(settings) {
return {
apiBaseUrl: normalizeBaseUrl(settings?.apiBaseUrl),
@@ -90,11 +153,13 @@
stripLegacyCredentialParams();
const initialConnectionSettings = readConfiguredConnectionSettings();
+ const initialConnectionAccess = normalizeConnectionAccess(null);
window.TarotAppConfig = {
...(window.TarotAppConfig || {}),
apiBaseUrl: initialConnectionSettings.apiBaseUrl,
apiKey: initialConnectionSettings.apiKey,
+ connectionAccess: initialConnectionAccess,
getApiBaseUrl() {
return normalizeBaseUrl(this.apiBaseUrl);
},
@@ -110,6 +175,31 @@
apiKey: this.getApiKey()
};
},
+ getConnectionAccess() {
+ return normalizeConnectionAccess(this.connectionAccess || defaultConnectionAccess);
+ },
+ hasTarotAccess() {
+ return this.getConnectionAccess().capabilities.tarot === true;
+ },
+ hasAdminApiManagementAccess() {
+ return this.getConnectionAccess().capabilities.adminApiManagement === true;
+ },
+ updateConnectionAccess(nextAccess = null) {
+ const previous = this.getConnectionAccess();
+ const current = normalizeConnectionAccess(nextAccess);
+ this.connectionAccess = current;
+
+ if (!sameConnectionAccess(previous, current)) {
+ document.dispatchEvent(new CustomEvent("connection:access-updated", {
+ detail: {
+ previous,
+ current: { ...current, roles: [...current.roles], scopes: [...current.scopes], capabilities: { ...current.capabilities } }
+ }
+ }));
+ }
+
+ return current;
+ },
updateConnectionSettings(nextSettings = {}) {
const previous = this.getConnectionSettings();
const current = normalizeConnectionSettings({
diff --git a/app/app-runtime.js b/app/app-runtime.js
index c3d2553..048af24 100644
--- a/app/app-runtime.js
+++ b/app/app-runtime.js
@@ -11,6 +11,7 @@
calendarVisualsUi: null,
homeUi: null,
onStatus: null,
+ hasTarotAccess: () => false,
services: {},
ensure: {}
};
@@ -112,12 +113,15 @@
renderInProgress = true;
try {
+ const tarotAccessEnabled = config.hasTarotAccess?.() === true;
currentGeo = parseGeoInput();
config.homeUi?.syncNowPanelTheme?.(new Date());
config.homeUi?.syncNowSkyBackground?.(currentGeo);
if (!referenceData || !magickDataset) {
- setStatus("Loading planetary, sign and decan tarot correspondences...");
+ setStatus(tarotAccessEnabled
+ ? "Loading planetary, sign and decan tarot correspondences..."
+ : "Loading planetary, sign, and calendar correspondences...");
const [loadedReference, loadedMagick] = await Promise.all([
referenceData ? Promise.resolve(referenceData) : config.services.loadReferenceData?.(),
magickDataset
@@ -129,7 +133,9 @@
magickDataset = loadedMagick;
}
- config.ensure.ensureTarotSection?.(referenceData, magickDataset);
+ if (tarotAccessEnabled) {
+ config.ensure.ensureTarotSection?.(referenceData, magickDataset);
+ }
config.ensure.ensurePlanetSection?.(referenceData, magickDataset);
config.ensure.ensureCyclesSection?.(referenceData);
config.ensure.ensureIChingSection?.(referenceData);
@@ -152,7 +158,9 @@
config.calendarVisualsUi?.updateMonthStrip?.();
});
- setStatus(`Rendered ${events.length} planetary + tarot events for lat ${currentGeo.latitude}, lng ${currentGeo.longitude}.`);
+ setStatus(tarotAccessEnabled
+ ? `Rendered ${events.length} planetary + tarot events for lat ${currentGeo.latitude}, lng ${currentGeo.longitude}.`
+ : `Rendered ${events.length} planetary events for lat ${currentGeo.latitude}, lng ${currentGeo.longitude}.`);
startNowTicker();
} catch (error) {
setStatus(error?.message || "Failed to render calendar.");
diff --git a/app/data-service.js b/app/data-service.js
index f2b58cc..c8533d6 100644
--- a/app/data-service.js
+++ b/app/data-service.js
@@ -168,6 +168,12 @@
.trim();
}
+ function hasAdminCapability(auth) {
+ const roles = Array.isArray(auth?.roles) ? auth.roles.map((value) => String(value || "").trim()) : [];
+ const scopes = Array.isArray(auth?.scopes) ? auth.scopes.map((value) => String(value || "").trim()) : [];
+ return roles.includes("admin") || scopes.includes("api:admin");
+ }
+
function isApiEnabled() {
return Boolean(getApiBaseUrl());
}
@@ -614,7 +620,7 @@
}
const health = await healthResponse.json().catch(() => null);
- const protectedResponse = await fetch(buildApiUrl("/api/v1/decks/options", {}, resolvedConnection), requestOptions);
+ const protectedResponse = await fetch(buildApiUrl("/api/v1/astrology/planets", {}, resolvedConnection), requestOptions);
if (protectedResponse.status === 401 || protectedResponse.status === 403) {
return {
@@ -630,18 +636,32 @@
return {
ok: false,
reason: "protected-route-failed",
- message: `The API responded with ${protectedResponse.status} when loading protected data.`
+ message: `The API responded with ${protectedResponse.status} when loading connected data.`
};
}
- const decksPayload = await protectedResponse.json().catch(() => null);
+ let tarotAvailable = false;
+ let deckCount = null;
+ const tarotResponse = await fetch(buildApiUrl("/api/v1/decks/options", {}, resolvedConnection), requestOptions);
+ if (tarotResponse.ok) {
+ const decksPayload = await tarotResponse.json().catch(() => null);
+ deckCount = Array.isArray(decksPayload?.decks) ? decksPayload.decks.length : null;
+ tarotAvailable = true;
+ } else if (tarotResponse.status !== 401 && tarotResponse.status !== 403) {
+ deckCount = null;
+ }
+
return {
ok: true,
reason: "connected",
- message: "Connected.",
+ message: tarotAvailable ? "Connected." : "Connected. Tarot features are unavailable for this API key.",
health,
auth: health?.auth || null,
- deckCount: Array.isArray(decksPayload?.decks) ? decksPayload.decks.length : null
+ deckCount,
+ capabilities: {
+ tarot: tarotAvailable,
+ adminApiManagement: hasAdminCapability(health?.auth)
+ }
};
} catch (_error) {
return {
diff --git a/app/ui-alphabet-detail.js b/app/ui-alphabet-detail.js
index 960fdca..9d7a346 100644
--- a/app/ui-alphabet-detail.js
+++ b/app/ui-alphabet-detail.js
@@ -29,6 +29,10 @@
return `${String(normalized).split("").join(" + ")} = ${digitalRoot}`;
}
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function renderPositionDigitalRootCard(letter, alphabet, context, orderLabel) {
const index = Number(letter?.index);
if (!Number.isFinite(index)) {
@@ -145,6 +149,10 @@
}
function renderTarotValue(value, context) {
+ if (!hasTarotAccess()) {
+ return "";
+ }
+
const label = String(value || "").trim();
if (!label) {
return "—";
@@ -408,11 +416,9 @@
}
if (letter.kabbalahPathNumber) {
- const tarotPart = letter.tarot
- ? `
Tarot Card${letter.tarot.card} (Trump ${letter.tarot.trumpNumber})`
- : "";
+ const tarotAccessEnabled = hasTarotAccess();
const kabBtn = context.inlineNavBtn(`Path ${letter.kabbalahPathNumber}`, "tarot:view-kab-path", { "path-number": letter.kabbalahPathNumber });
- const tarotBtn = letter.tarot
+ const tarotBtn = tarotAccessEnabled && letter.tarot
? context.inlineNavBtn(letter.tarot.card, "kab:view-trump", { "trump-number": letter.tarot.trumpNumber })
: "";
const cubePlacement = context.getCubePlacementForHebrewLetter(letter.hebrewLetterId, letter.kabbalahPathNumber);
@@ -420,10 +426,10 @@
"hebrew-letter-id": letter.hebrewLetterId,
"path-no": letter.kabbalahPathNumber
});
- sections.push(context.card("Kabbalah & Tarot", `
+ sections.push(context.card(tarotAccessEnabled ? "Kabbalah & Tarot" : "Kabbalah", `
- Path Number
- ${kabBtn}
- ${letter.tarot ? `- Tarot Card
- ${tarotBtn} (Trump ${letter.tarot.trumpNumber})
` : ""}
+ ${tarotAccessEnabled && letter.tarot ? `- Tarot Card
- ${tarotBtn} (Trump ${letter.tarot.trumpNumber})
` : ""}
${cubeBtn ? `Cube placement ${cubeBtn}
` : ""}
`));
@@ -580,7 +586,7 @@
English Letters${englishRefs.join(" / ") || "—"}
Transliteration${letter.transliteration || "—"}
Element / Planet${renderElementOrPlanetValue(letter.elementOrPlanet, context)}
- Tarot${renderTarotValue(letter.tarot, context)}
+ ${hasTarotAccess() ? `Tarot${renderTarotValue(letter.tarot, context)}` : ""}
Numerology${letter.numerology || "—"}
Glyph SourceAPI asset: img/enochian (sourced from dCode set)
Position#${letter.index} of 21
diff --git a/app/ui-calendar-detail-panels.js b/app/ui-calendar-detail-panels.js
index c4860e3..76231dc 100644
--- a/app/ui-calendar-detail-panels.js
+++ b/app/ui-calendar-detail-panels.js
@@ -1,6 +1,10 @@
(function () {
"use strict";
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function findSignIdByAstrologyName(name, context) {
const { api, getState } = context;
const token = api.normalizeCalendarText(name);
@@ -140,6 +144,10 @@
}
function renderMajorArcanaCard(context) {
+ if (!hasTarotAccess()) {
+ return "";
+ }
+
const { month, api } = context;
const selectedDay = api.getSelectedDayFilterContext(month);
const allRows = buildMajorArcanaRowsForMonth(context);
@@ -191,6 +199,10 @@
}
function renderDecanTarotCard(context) {
+ if (!hasTarotAccess()) {
+ return "";
+ }
+
const { month, api } = context;
const selectedDay = api.getSelectedDayFilterContext(month);
const allRows = api.buildDecanTarotRowsForMonth(month);
diff --git a/app/ui-calendar-detail.js b/app/ui-calendar-detail.js
index a1f2745..d2f8b27 100644
--- a/app/ui-calendar-detail.js
+++ b/app/ui-calendar-detail.js
@@ -52,6 +52,10 @@
Object.assign(api, config || {});
}
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function getState() {
return api.getState?.() || {};
}
@@ -158,7 +162,7 @@
}
}
- if (associations.tarotCard) {
+ if (associations.tarotCard && hasTarotAccess()) {
const explicitTrumpNumber = Number(associations.tarotTrumpNumber);
const tarotTrumpNumber = Number.isFinite(explicitTrumpNumber) ? explicitTrumpNumber : null;
const tarotLabel = api.getDisplayTarotName(associations.tarotCard, tarotTrumpNumber);
diff --git a/app/ui-cube-detail.js b/app/ui-cube-detail.js
index 27a7683..94d04fb 100644
--- a/app/ui-cube-detail.js
+++ b/app/ui-cube-detail.js
@@ -104,6 +104,10 @@
return list;
}
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function createAstrologyValue(astrology, context) {
const type = toDisplayText(astrology?.type).toLowerCase();
const name = toDisplayText(astrology?.name);
@@ -150,7 +154,7 @@
const tarotCard = toDisplayText(pathEntry?.tarot?.card);
const tarotTrumpNumber = context.toFiniteNumber(pathEntry?.tarot?.trumpNumber);
- if (tarotCard || tarotTrumpNumber != null) {
+ if (hasTarotAccess() && (tarotCard || tarotTrumpNumber != null)) {
rows.push({
label: "Tarot",
value: createInlineEventLink(tarotCard || `Trump ${tarotTrumpNumber}`, "nav:tarot-trump", {
@@ -223,7 +227,7 @@
{ label: "Element", value: createElementValue(center?.element, context) || center?.element }
];
- if (centerTarotCard || centerTrumpNo != null) {
+ if (hasTarotAccess() && (centerTarotCard || centerTrumpNo != null)) {
summaryRows.push({
label: "Tarot",
value: createInlineEventLink(centerTarotCard || `Trump ${centerTrumpNo}`, "nav:tarot-trump", {
@@ -327,7 +331,7 @@
}
];
- if (tarotCard || tarotTrumpNumber != null) {
+ if (hasTarotAccess() && (tarotCard || tarotTrumpNumber != null)) {
connectorRows.push({
label: "Tarot",
value: createInlineEventLink(tarotCard || `Trump ${tarotTrumpNumber}`, "nav:tarot-trump", {
@@ -428,7 +432,7 @@
}
];
- if (tarotCard || tarotTrumpNumber != null) {
+ if (hasTarotAccess() && (tarotCard || tarotTrumpNumber != null)) {
edgeRows.push({
label: "Tarot",
value: createInlineEventLink(tarotCard || `Trump ${tarotTrumpNumber}`, "nav:tarot-trump", {
@@ -562,7 +566,7 @@
} else {
const directTarotCard = toDisplayText(wallAssociations?.tarotCard || wall?.tarotCard);
const directTrumpNumber = toFiniteNumber(wallAssociations?.tarotTrumpNumber);
- if (directTarotCard || directTrumpNumber != null) {
+ if (hasTarotAccess() && (directTarotCard || directTrumpNumber != null)) {
wallRows.push({
label: "Tarot",
value: createInlineEventLink(directTarotCard || `Trump ${directTrumpNumber}`, "nav:tarot-trump", {
@@ -734,7 +738,7 @@
}
];
- if (tarotCard || tarotTrumpNumber != null) {
+ if (hasTarotAccess() && (tarotCard || tarotTrumpNumber != null)) {
edgeRows.push({
label: "Tarot",
value: createInlineEventLink(tarotCard || `Trump ${tarotTrumpNumber}`, "nav:tarot-trump", {
diff --git a/app/ui-elements.js b/app/ui-elements.js
index 5f377ff..6fdfc41 100644
--- a/app/ui-elements.js
+++ b/app/ui-elements.js
@@ -42,6 +42,10 @@
earth: "Disks"
};
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
const SMALL_CARD_GROUPS = [
{ label: "2–4", modality: "Cardinal", numbers: [2, 3, 4] },
{ label: "5–7", modality: "Fixed", numbers: [5, 6, 7] },
@@ -324,106 +328,111 @@
detailsCard.append(detailsTitle, detailsList);
- const tarotCard = document.createElement("div");
- tarotCard.className = "planet-meta-card";
+ const detailCards = [detailsCard];
- const tarotTitle = document.createElement("strong");
- tarotTitle.textContent = "Tarot Correspondence";
+ if (hasTarotAccess()) {
+ const tarotCard = document.createElement("div");
+ tarotCard.className = "planet-meta-card";
- const tarotParts = [];
- if (entry.aceCardName) {
- tarotParts.push(
- "Ace: ",
- createInlineButton(entry.aceCardName, () => {
- document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
- detail: { cardName: entry.aceCardName }
- }));
- })
- );
- }
- if (entry.courtRank) {
- if (tarotParts.length) {
- tarotParts.push(" · ");
- }
- tarotParts.push(`Court Rank: ${entry.courtRank} (all suits)`);
- }
+ const tarotTitle = document.createElement("strong");
+ tarotTitle.textContent = "Tarot Correspondence";
- const tarotText = tarotParts.length
- ? createInlineParagraph(tarotParts)
- : document.createTextNode("--");
-
- tarotCard.append(tarotTitle, tarotText);
-
- if (entry.courtCardNames.length) {
- const courtParts = ["Court cards: "];
- entry.courtCardNames.forEach((cardName, index) => {
- if (index > 0) {
- courtParts.push(", ");
- }
- courtParts.push(createInlineButton(cardName, () => {
- document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
- detail: { cardName }
- }));
- }));
- });
-
- tarotCard.appendChild(createInlineParagraph(courtParts));
- }
-
- const smallCardCard = document.createElement("div");
- smallCardCard.className = "planet-meta-card";
-
- const smallCardTitle = document.createElement("strong");
- smallCardTitle.textContent = "Small Card Sign Types";
- smallCardCard.appendChild(smallCardTitle);
-
- const smallCardStack = document.createElement("div");
- smallCardStack.className = "cal-item-stack";
-
- (entry.smallCardGroups || []).forEach((group) => {
- const row = document.createElement("div");
- row.className = "cal-item-row";
-
- const head = document.createElement("div");
- head.className = "cal-item-head";
- head.innerHTML = `
- ${group.rangeLabel} · ${group.modality}
- ${group.signName || "--"}
- `;
- row.appendChild(head);
-
- if (group.signId) {
- row.appendChild(createInlineParagraph([
- "Sign: ",
- createInlineButton(group.signName, () => {
- document.dispatchEvent(new CustomEvent("nav:zodiac", {
- detail: { signId: group.signId }
+ const tarotParts = [];
+ if (entry.aceCardName) {
+ tarotParts.push(
+ "Ace: ",
+ createInlineButton(entry.aceCardName, () => {
+ document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
+ detail: { cardName: entry.aceCardName }
}));
})
- ]));
+ );
+ }
+ if (entry.courtRank) {
+ if (tarotParts.length) {
+ tarotParts.push(" · ");
+ }
+ tarotParts.push(`Court Rank: ${entry.courtRank} (all suits)`);
}
- if ((group.cardNames || []).length) {
- const cardParts = ["Cards: "];
- group.cardNames.forEach((cardName, index) => {
+ const tarotText = tarotParts.length
+ ? createInlineParagraph(tarotParts)
+ : document.createTextNode("--");
+
+ tarotCard.append(tarotTitle, tarotText);
+
+ if (entry.courtCardNames.length) {
+ const courtParts = ["Court cards: "];
+ entry.courtCardNames.forEach((cardName, index) => {
if (index > 0) {
- cardParts.push(", ");
+ courtParts.push(", ");
}
- cardParts.push(createInlineButton(cardName, () => {
+ courtParts.push(createInlineButton(cardName, () => {
document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
detail: { cardName }
}));
}));
});
- row.appendChild(createInlineParagraph(cardParts));
+
+ tarotCard.appendChild(createInlineParagraph(courtParts));
}
- smallCardStack.appendChild(row);
- });
+ const smallCardCard = document.createElement("div");
+ smallCardCard.className = "planet-meta-card";
- smallCardCard.appendChild(smallCardStack);
+ const smallCardTitle = document.createElement("strong");
+ smallCardTitle.textContent = "Small Card Sign Types";
+ smallCardCard.appendChild(smallCardTitle);
- grid.append(detailsCard, tarotCard, smallCardCard);
+ const smallCardStack = document.createElement("div");
+ smallCardStack.className = "cal-item-stack";
+
+ (entry.smallCardGroups || []).forEach((group) => {
+ const row = document.createElement("div");
+ row.className = "cal-item-row";
+
+ const head = document.createElement("div");
+ head.className = "cal-item-head";
+ head.innerHTML = `
+ ${group.rangeLabel} · ${group.modality}
+ ${group.signName || "--"}
+ `;
+ row.appendChild(head);
+
+ if (group.signId) {
+ row.appendChild(createInlineParagraph([
+ "Sign: ",
+ createInlineButton(group.signName, () => {
+ document.dispatchEvent(new CustomEvent("nav:zodiac", {
+ detail: { signId: group.signId }
+ }));
+ })
+ ]));
+ }
+
+ if ((group.cardNames || []).length) {
+ const cardParts = ["Cards: "];
+ group.cardNames.forEach((cardName, index) => {
+ if (index > 0) {
+ cardParts.push(", ");
+ }
+ cardParts.push(createInlineButton(cardName, () => {
+ document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
+ detail: { cardName }
+ }));
+ }));
+ });
+ row.appendChild(createInlineParagraph(cardParts));
+ }
+
+ smallCardStack.appendChild(row);
+ });
+
+ smallCardCard.appendChild(smallCardStack);
+ detailCards.push(tarotCard, smallCardCard);
+ }
+
+ grid.append(...detailCards);
elements.detailBodyEl.appendChild(grid);
}
diff --git a/app/ui-enochian.js b/app/ui-enochian.js
index d02c01a..cddf52c 100644
--- a/app/ui-enochian.js
+++ b/app/ui-enochian.js
@@ -30,6 +30,10 @@
lettersById: new Map()
};
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function getElements() {
return {
listEl: document.getElementById("enochian-list"),
@@ -306,10 +310,13 @@
const detailRows = [
["Pronunciation", letterData.pronounciation],
["Planet / Element", letterData["planet/element"]],
- ["Tarot", letterData.tarot],
["Gematria", letterData.gematria]
];
+ if (hasTarotAccess()) {
+ detailRows.splice(2, 0, ["Tarot", letterData.tarot]);
+ }
+
detailRows.forEach(([label, value]) => {
if (value === undefined || value === null || String(value).trim() === "") {
return;
@@ -324,7 +331,7 @@
navRow.className = "enoch-letter-row";
const tarotCardName = resolveTarotCardName(letterData.tarot);
- if (tarotCardName) {
+ if (hasTarotAccess() && tarotCardName) {
const tarotBtn = document.createElement("button");
tarotBtn.type = "button";
tarotBtn.className = "enoch-nav-btn";
diff --git a/app/ui-holidays-render.js b/app/ui-holidays-render.js
index b931e46..8888aa7 100644
--- a/app/ui-holidays-render.js
+++ b/app/ui-holidays-render.js
@@ -73,6 +73,10 @@
return current;
}
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function buildInlineNavButton(label, nav, attrs = {}) {
const dataAttrs = Object.entries(attrs)
.map(([key, value]) => `data-${key}="${value}"`)
@@ -109,7 +113,7 @@
}
}
- if (associations.tarotCard) {
+ if (associations.tarotCard && hasTarotAccess()) {
const trumpNumber = resolveTarotTrumpNumber(associations.tarotCard);
const explicitTrumpNumber = Number(associations.tarotTrumpNumber);
const tarotTrumpNumber = Number.isFinite(explicitTrumpNumber) ? explicitTrumpNumber : trumpNumber;
diff --git a/app/ui-iching.js b/app/ui-iching.js
index 72f83c6..ad4c43d 100644
--- a/app/ui-iching.js
+++ b/app/ui-iching.js
@@ -20,6 +20,10 @@
selectedNumber: null
};
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
const ICHING_PLANET_BY_PLANET_ID = {
sol: "Sun",
luna: "Moon",
@@ -235,7 +239,8 @@
elements.ichingDetailPlanetEl.textContent = "--";
}
if (elements.ichingDetailTarotEl) {
- elements.ichingDetailTarotEl.textContent = "--";
+ elements.ichingDetailTarotEl.hidden = !hasTarotAccess();
+ elements.ichingDetailTarotEl.textContent = hasTarotAccess() ? "--" : "";
}
if (elements.ichingDetailCalendarEl) {
clearChildren(elements.ichingDetailCalendarEl);
@@ -327,6 +332,15 @@
return;
}
+ if (!hasTarotAccess()) {
+ clearChildren(elements.ichingDetailTarotEl);
+ elements.ichingDetailTarotEl.hidden = true;
+ elements.ichingDetailTarotEl.textContent = "";
+ return;
+ }
+
+ elements.ichingDetailTarotEl.hidden = false;
+
clearChildren(elements.ichingDetailTarotEl);
const upperKey = normalizeSearchValue(entry?.upperTrigram);
diff --git a/app/ui-kabbalah-detail.js b/app/ui-kabbalah-detail.js
index 5a5b2d9..add5de5 100644
--- a/app/ui-kabbalah-detail.js
+++ b/app/ui-kabbalah-detail.js
@@ -26,6 +26,10 @@
const MINOR_SUITS = ["Wands", "Cups", "Swords", "Disks"];
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
const DEFAULT_FOUR_QABALISTIC_WORLD_LAYERS = [
{
slot: "Yod",
@@ -167,6 +171,10 @@
}
function buildTarotAttributionCard(attribution) {
+ if (!hasTarotAccess()) {
+ return null;
+ }
+
const minorCards = buildMinorTarotNames(attribution);
if (minorCards.length) {
const parts = [];
@@ -485,7 +493,10 @@
elements.detailBodyEl.innerHTML = "";
elements.detailBodyEl.appendChild(buildPlanetLuminaryCard(seph.planet, context));
elements.detailBodyEl.appendChild(metaCard("Intelligence", seph.intelligence));
- elements.detailBodyEl.appendChild(buildTarotAttributionCard(seph.tarot));
+ const tarotAttributionCard = buildTarotAttributionCard(seph.tarot);
+ if (tarotAttributionCard) {
+ elements.detailBodyEl.appendChild(tarotAttributionCard);
+ }
if (seph.description) {
elements.detailBodyEl.appendChild(
@@ -500,7 +511,7 @@
const card = document.createElement("div");
card.className = "planet-meta-card kab-wide-card";
const chips = connected.map((entry) =>
- ``
+ ``
+ `${entry.hebrewLetter?.char || ""} ${entry.pathNumber}`
+ ``
).join("");
@@ -533,39 +544,42 @@
const fromName = tree.sephiroth.find((entry) => entry.number === path.connects.from)?.name || path.connects.from;
const toName = tree.sephiroth.find((entry) => entry.number === path.connects.to)?.name || path.connects.to;
const astro = path.astrology ? `${path.astrology.name} (${path.astrology.type})` : "—";
- const tarotStr = path.tarot?.card
+ const tarotAccessEnabled = hasTarotAccess();
+ const tarotStr = tarotAccessEnabled && path.tarot?.card
? `${path.tarot.card}${path.tarot.trumpNumber != null ? " · Trump " + path.tarot.trumpNumber : ""}`
: "—";
elements.detailNameEl.textContent =
`Path ${path.pathNumber} · ${letter.char || ""} ${letter.transliteration || ""}`;
- elements.detailSubEl.textContent = [path.tarot?.card, astro].filter(Boolean).join(" · ");
+ elements.detailSubEl.textContent = [tarotAccessEnabled ? path.tarot?.card : "", astro].filter(Boolean).join(" · ");
elements.detailBodyEl.innerHTML = "";
elements.detailBodyEl.appendChild(buildConnectsCard(path, fromName, toName));
elements.detailBodyEl.appendChild(buildHebrewLetterCard(letter, context));
elements.detailBodyEl.appendChild(buildAstrologyCard(path.astrology, context));
- const tarotMetaCard = document.createElement("div");
- tarotMetaCard.className = "planet-meta-card";
- const tarotLabel = document.createElement("strong");
- tarotLabel.textContent = "Tarot";
- tarotMetaCard.appendChild(tarotLabel);
- if (path.tarot?.card && path.tarot.trumpNumber != null) {
- const tarotBtn = createInlineEventLink(
- `${path.tarot.card} · Trump ${path.tarot.trumpNumber}`,
- "kab:view-trump",
- { trumpNumber: path.tarot.trumpNumber }
- );
- tarotBtn.title = "Open in Tarot section";
- tarotMetaCard.appendChild(tarotBtn);
- } else {
- const tarotP = document.createElement("p");
- tarotP.className = "planet-text";
- tarotP.textContent = tarotStr || "—";
- tarotMetaCard.appendChild(tarotP);
+ if (tarotAccessEnabled) {
+ const tarotMetaCard = document.createElement("div");
+ tarotMetaCard.className = "planet-meta-card";
+ const tarotLabel = document.createElement("strong");
+ tarotLabel.textContent = "Tarot";
+ tarotMetaCard.appendChild(tarotLabel);
+ if (path.tarot?.card && path.tarot.trumpNumber != null) {
+ const tarotBtn = createInlineEventLink(
+ `${path.tarot.card} · Trump ${path.tarot.trumpNumber}`,
+ "kab:view-trump",
+ { trumpNumber: path.tarot.trumpNumber }
+ );
+ tarotBtn.title = "Open in Tarot section";
+ tarotMetaCard.appendChild(tarotBtn);
+ } else {
+ const tarotP = document.createElement("p");
+ tarotP.className = "planet-text";
+ tarotP.textContent = tarotStr || "—";
+ tarotMetaCard.appendChild(tarotP);
+ }
+ elements.detailBodyEl.appendChild(tarotMetaCard);
}
- elements.detailBodyEl.appendChild(tarotMetaCard);
elements.detailBodyEl.appendChild(metaCard("Intelligence", path.intelligence));
elements.detailBodyEl.appendChild(metaCard("Pillar", path.pillar));
diff --git a/app/ui-navigation.js b/app/ui-navigation.js
index 1a3ca2d..f055b8a 100644
--- a/app/ui-navigation.js
+++ b/app/ui-navigation.js
@@ -93,6 +93,10 @@
}
async function prepareTarotBrowseDetailView() {
+ if (window.TarotAppConfig?.hasTarotAccess?.() !== true) {
+ return false;
+ }
+
const ensure = config.ensure || {};
const referenceData = getReferenceData();
const magickDataset = getMagickDataset();
@@ -113,6 +117,7 @@
});
showSectionDetailOnly("tarot");
+ return true;
}
function bindClick(element, handler) {
@@ -491,7 +496,11 @@
});
document.addEventListener("nav:tarot-trump", async (event) => {
- await prepareTarotBrowseDetailView();
+ const tarotReady = await prepareTarotBrowseDetailView();
+ if (!tarotReady) {
+ return;
+ }
+
const { trumpNumber, cardName } = event?.detail || {};
if (trumpNumber != null) {
@@ -502,7 +511,11 @@
});
document.addEventListener("kab:view-trump", async (event) => {
- await prepareTarotBrowseDetailView();
+ const tarotReady = await prepareTarotBrowseDetailView();
+ if (!tarotReady) {
+ return;
+ }
+
const trumpNumber = event?.detail?.trumpNumber;
if (trumpNumber != null) {
diff --git a/app/ui-now.js b/app/ui-now.js
index 6fa3926..6ca756c 100644
--- a/app/ui-now.js
+++ b/app/ui-now.js
@@ -58,14 +58,29 @@
}
}
+ function setNowTarotVisibility(imageEl, labelEl, visible) {
+ if (imageEl) {
+ imageEl.hidden = !visible;
+ }
+
+ if (labelEl) {
+ labelEl.hidden = !visible;
+ }
+ }
+
function applyNowSnapshot(elements, snapshot, timeFormat) {
const timestamp = snapshot?.timestamp ? new Date(snapshot.timestamp) : new Date();
const dayKey = String(snapshot?.dayKey || getDateKey(timestamp));
const currentHour = snapshot?.currentHour || null;
+ const tarotAccessEnabled = window.TarotAppConfig?.hasTarotAccess?.() === true;
+
+ setNowTarotVisibility(elements.nowHourCardEl, elements.nowHourTarotEl, tarotAccessEnabled);
+ setNowTarotVisibility(elements.nowMoonCardEl, elements.nowMoonTarotEl, tarotAccessEnabled);
+ setNowTarotVisibility(elements.nowDecanCardEl, elements.nowDecanTarotEl, tarotAccessEnabled);
if (currentHour?.planet) {
elements.nowHourEl.textContent = `${currentHour.planet.symbol} ${currentHour.planet.name}`;
- if (elements.nowHourTarotEl) {
+ if (tarotAccessEnabled && elements.nowHourTarotEl) {
const hourCardName = currentHour.planet?.tarot?.majorArcana || "";
const hourTrumpNumber = currentHour.planet?.tarot?.number;
elements.nowHourTarotEl.textContent = hourCardName
@@ -84,7 +99,7 @@
nowUiHelpers.setNowCardImage(
elements.nowHourCardEl,
- currentHour.planet?.tarot?.majorArcana,
+ tarotAccessEnabled ? currentHour.planet?.tarot?.majorArcana : null,
"Current planetary hour card",
currentHour.planet?.tarot?.number
);
@@ -92,7 +107,7 @@
elements.nowHourEl.textContent = "--";
elements.nowCountdownEl.textContent = "--";
if (elements.nowHourTarotEl) {
- elements.nowHourTarotEl.textContent = "--";
+ elements.nowHourTarotEl.textContent = tarotAccessEnabled ? "--" : "";
}
if (elements.nowHourNextEl) {
elements.nowHourNextEl.textContent = "> --";
@@ -106,12 +121,12 @@
elements.nowMoonEl.textContent = moon
? `${moon.phase} (${Math.round(illuminationFraction * 100)}%)`
: "--";
- elements.nowMoonTarotEl.textContent = moon
- ? nowUiHelpers.getDisplayTarotName(moonTarot, moon?.tarot?.number)
- : "--";
+ elements.nowMoonTarotEl.textContent = tarotAccessEnabled
+ ? (moon ? nowUiHelpers.getDisplayTarotName(moonTarot, moon?.tarot?.number) : "--")
+ : "";
nowUiHelpers.setNowCardImage(
elements.nowMoonCardEl,
- moon?.tarot?.majorArcana,
+ tarotAccessEnabled ? moon?.tarot?.majorArcana : null,
"Current moon phase card",
moon?.tarot?.number
);
@@ -140,12 +155,14 @@
? Number(decanInfo.signDegree).toFixed(1)
: "0.0";
- elements.nowDecanEl.textContent = `${decanInfo.sign.symbol} ${decanInfo.sign.name} · ${signMajorName} (${signDegree}°)`;
+ elements.nowDecanEl.textContent = tarotAccessEnabled
+ ? `${decanInfo.sign.symbol} ${decanInfo.sign.name} · ${signMajorName} (${signDegree}°)`
+ : `${decanInfo.sign.symbol} ${decanInfo.sign.name} (${signDegree}°)`;
- if (decanInfo.decan?.tarotMinorArcana) {
+ if (tarotAccessEnabled && decanInfo.decan?.tarotMinorArcana) {
elements.nowDecanTarotEl.textContent = nowUiHelpers.getDisplayTarotName(decanInfo.decan.tarotMinorArcana);
nowUiHelpers.setNowCardImage(elements.nowDecanCardEl, decanInfo.decan.tarotMinorArcana, "Current decan card");
- } else {
+ } else if (tarotAccessEnabled) {
const signTarotName = decanInfo.sign?.tarot?.majorArcana || "--";
elements.nowDecanTarotEl.textContent = signTarotName === "--"
? "--"
@@ -156,6 +173,9 @@
"Current decan card",
decanInfo.sign?.tarot?.number
);
+ } else {
+ elements.nowDecanTarotEl.textContent = "";
+ nowUiHelpers.setNowCardImage(elements.nowDecanCardEl, null, "Current decan card");
}
if (elements.nowDecanCountdownEl) {
@@ -173,7 +193,7 @@
}
} else {
elements.nowDecanEl.textContent = "--";
- elements.nowDecanTarotEl.textContent = "--";
+ elements.nowDecanTarotEl.textContent = tarotAccessEnabled ? "--" : "";
nowUiHelpers.setNowCardImage(elements.nowDecanCardEl, null, "Current decan card");
if (elements.nowDecanCountdownEl) {
elements.nowDecanCountdownEl.textContent = "--";
diff --git a/app/ui-numbers-detail.js b/app/ui-numbers-detail.js
index be2e413..eb4bfc8 100644
--- a/app/ui-numbers-detail.js
+++ b/app/ui-numbers-detail.js
@@ -1,6 +1,10 @@
(function () {
"use strict";
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function getCalendarMonthLinksForNumber(context, value) {
const { getReferenceData, normalizeNumberValue, computeDigitalRoot } = context;
const referenceData = getReferenceData();
@@ -406,6 +410,10 @@
}
function getTarotCardsForDigitalRoot(context, targetRoot, numberEntry = null) {
+ if (!hasTarotAccess()) {
+ return [];
+ }
+
const { getReferenceData, getMagickDataset, ensureTarotSection, computeDigitalRoot } = context;
const referenceData = getReferenceData();
const magickDataset = getMagickDataset();
@@ -558,38 +566,6 @@
alphabetCardEl.append(alphabetHeadingEl, alphabetLinksWrapEl);
- const tarotCardEl = document.createElement("div");
- tarotCardEl.className = "numbers-detail-card";
-
- const tarotHeadingEl = document.createElement("strong");
- tarotHeadingEl.textContent = "Tarot Links";
-
- const tarotLinksWrapEl = document.createElement("div");
- tarotLinksWrapEl.className = "numbers-links-wrap";
-
- const tarotCards = getTarotCardsForDigitalRoot(context, rootTarget, entry);
- if (!tarotCards.length) {
- const emptyEl = document.createElement("div");
- emptyEl.className = "numbers-detail-text numbers-detail-text--muted";
- emptyEl.textContent = "No tarot numeric entries found yet for this root. Add card numbers to map them.";
- tarotLinksWrapEl.appendChild(emptyEl);
- } else {
- tarotCards.forEach((card) => {
- const button = document.createElement("button");
- button.type = "button";
- button.className = "numbers-nav-btn";
- button.textContent = `${card.name}`;
- button.addEventListener("click", () => {
- document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
- detail: { cardName: card.name }
- }));
- });
- tarotLinksWrapEl.appendChild(button);
- });
- }
-
- tarotCardEl.append(tarotHeadingEl, tarotLinksWrapEl);
-
const calendarCardEl = document.createElement("div");
calendarCardEl.className = "numbers-detail-card";
@@ -625,7 +601,45 @@
calendarCardEl.append(calendarHeadingEl, calendarLinksWrapEl);
- detailBodyEl.append(pairCardEl, kabbalahCardEl, alphabetCardEl, tarotCardEl, calendarCardEl);
+ const detailCards = [pairCardEl, kabbalahCardEl, alphabetCardEl];
+
+ if (hasTarotAccess()) {
+ const tarotCardEl = document.createElement("div");
+ tarotCardEl.className = "numbers-detail-card";
+
+ const tarotHeadingEl = document.createElement("strong");
+ tarotHeadingEl.textContent = "Tarot Links";
+
+ const tarotLinksWrapEl = document.createElement("div");
+ tarotLinksWrapEl.className = "numbers-links-wrap";
+
+ const tarotCards = getTarotCardsForDigitalRoot(context, rootTarget, entry);
+ if (!tarotCards.length) {
+ const emptyEl = document.createElement("div");
+ emptyEl.className = "numbers-detail-text numbers-detail-text--muted";
+ emptyEl.textContent = "No tarot numeric entries found yet for this root. Add card numbers to map them.";
+ tarotLinksWrapEl.appendChild(emptyEl);
+ } else {
+ tarotCards.forEach((card) => {
+ const button = document.createElement("button");
+ button.type = "button";
+ button.className = "numbers-nav-btn";
+ button.textContent = `${card.name}`;
+ button.addEventListener("click", () => {
+ document.dispatchEvent(new CustomEvent("nav:tarot-trump", {
+ detail: { cardName: card.name }
+ }));
+ });
+ tarotLinksWrapEl.appendChild(button);
+ });
+ }
+
+ tarotCardEl.append(tarotHeadingEl, tarotLinksWrapEl);
+ detailCards.push(tarotCardEl);
+ }
+
+ detailCards.push(calendarCardEl);
+ detailBodyEl.append(...detailCards);
}
window.NumbersDetailUi = {
diff --git a/app/ui-planets.js b/app/ui-planets.js
index 0ace64c..d60aaaa 100644
--- a/app/ui-planets.js
+++ b/app/ui-planets.js
@@ -23,6 +23,10 @@
};
let detailNavigator = null;
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function normalizePlanetToken(value) {
return String(value || "")
.trim()
@@ -290,7 +294,7 @@
if (!correspondence || typeof correspondence !== "object") {
const fallback = document.createElement("span");
fallback.className = "planet-text";
- fallback.textContent = "No tarot/day correspondence in current local dataset.";
+ fallback.textContent = "No day correspondence in current local dataset.";
containerEl.appendChild(fallback);
return;
}
@@ -309,7 +313,7 @@
containerEl.appendChild(line);
}
- if (arcana) {
+ if (arcana && hasTarotAccess()) {
const btn = createInlineButton(
trumpNo != null ? `${arcanaLabel} · Trump ${trumpNo}` : arcanaLabel,
() => {
diff --git a/app/ui-section-state.js b/app/ui-section-state.js
index 9e5a991..91cad42 100644
--- a/app/ui-section-state.js
+++ b/app/ui-section-state.js
@@ -38,6 +38,7 @@
let config = {
elements: {},
ensure: {},
+ isSectionAccessible: () => true,
getReferenceData: () => null,
getMagickDataset: () => null,
calendarVisualsUi: null,
@@ -82,7 +83,10 @@
}
function setActiveSection(nextSection) {
- const normalized = VALID_SECTIONS.has(nextSection) ? nextSection : "home";
+ const requestedSection = VALID_SECTIONS.has(nextSection) ? nextSection : "home";
+ const normalized = config.isSectionAccessible?.(requestedSection) === false
+ ? "home"
+ : requestedSection;
activeSection = normalized;
const elements = config.elements || {};
diff --git a/app/ui-settings.js b/app/ui-settings.js
index 5655b0e..5462373 100644
--- a/app/ui-settings.js
+++ b/app/ui-settings.js
@@ -181,6 +181,10 @@
};
}
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
function syncConnectionInputs() {
const { apiBaseUrlEl, apiKeyEl } = getElements();
const connectionSettings = getConnectionSettings();
@@ -251,8 +255,9 @@
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" : ""}`
+ ? `${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)}`
@@ -399,6 +404,10 @@
}
function getKnownTarotDeckIds() {
+ if (!hasTarotAccess()) {
+ return new Set([String(config.defaultSettings?.tarotDeck || "ceremonial-magick").trim().toLowerCase()]);
+ }
+
const knownDeckIds = new Set();
const deckOptions = window.TarotCardImages?.getDeckOptions?.();
@@ -419,6 +428,10 @@
}
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) {
@@ -433,6 +446,11 @@
}
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();
@@ -571,6 +589,16 @@
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 = "";
@@ -602,6 +630,24 @@
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, stellariumBackgroundEl } = getElements();
syncTarotDeckInputOptions();
@@ -619,10 +665,7 @@
stellariumBackgroundEl.checked = normalized.stellariumBackgroundEnabled;
}
syncStellariumBackgroundAvailability();
- if (window.TarotCardImages?.setActiveDeck) {
- window.TarotCardImages.setActiveDeck(normalized.tarotDeck);
- }
- syncDeckCacheStatus(window.TarotCardImages?.getDeckPreloadStatus?.());
+ syncActiveTarotDeck(normalized.tarotDeck);
applyExternalSettings(normalized);
return normalized;
}
@@ -842,13 +885,19 @@
document.addEventListener("connection:updated", () => {
syncConnectionInputs();
+ void refreshConnectionSummary(getConnectionSettings());
+ });
+
+ document.addEventListener("connection:access-updated", () => {
syncTarotDeckInputOptions();
- syncDeckCacheStatus(window.TarotCardImages?.getDeckPreloadStatus?.());
+ syncActiveTarotDeck(getElements().tarotDeckEl?.value || loadSavedSettings().tarotDeck);
void refreshConnectionSummary(getConnectionSettings());
});
document.addEventListener("tarot:deck-cache-status", (event) => {
- syncDeckCacheStatus(event?.detail);
+ if (hasTarotAccess()) {
+ syncDeckCacheStatus(event?.detail);
+ }
});
}
diff --git a/app/ui-zodiac.js b/app/ui-zodiac.js
index 1288c67..37f23b9 100644
--- a/app/ui-zodiac.js
+++ b/app/ui-zodiac.js
@@ -37,6 +37,10 @@
cubePlacementBySignId: new Map()
};
+ function hasTarotAccess() {
+ return window.TarotAppConfig?.hasTarotAccess?.() === true;
+ }
+
// ── Elements ──────────────────────────────────────────────────────────
function getElements() {
return {
@@ -184,12 +188,14 @@
}
// ── Kabbalah Path + Trump ─────────────────────────────────────────
+ const tarotAccessEnabled = hasTarotAccess();
+
if (kabPath) {
const hl = kabPath.hebrewLetter || {};
const hebrewLetterId = normalizeHebrewLetterId(hl.transliteration);
const hebrewLetterLabel = hl.transliteration || hl.char || "";
sections.push(`
@@ -211,6 +217,13 @@
const decanRows = decans.map((d) => {
const ord = ["1st","2nd","3rd"][d.index - 1] || d.index;
const sym = PLANET_SYMBOLS[d.rulerPlanetId] || "";
+ if (!tarotAccessEnabled) {
+ return `
+ ${ord}
+ ${sym ? `${sym} ` : ""}
+
`;
+ }
+
return `
${ord}
${sym ? `${sym} ` : ""}
@@ -220,7 +233,7 @@
`;
}).join("");
sections.push(``);
}
diff --git a/index.html b/index.html
index bd61431..da672c8 100644
--- a/index.html
+++ b/index.html
@@ -113,7 +113,7 @@
-
+
Location And Time
Controls calendar rendering, the Now panel, and optional sky background.
@@ -1311,45 +1311,45 @@
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1357,12 +1357,12 @@
-
+
-
+
@@ -1372,20 +1372,20 @@
-
-
+
+
-
+
-
-
-
+
+
+