various ui improvements, including a new sequence nav component and a new kabbalah detail view
This commit is contained in:
@@ -50,6 +50,9 @@ const cyclesSectionEl = document.getElementById("cycles-section");
|
|||||||
const elementsSectionEl = document.getElementById("elements-section");
|
const elementsSectionEl = document.getElementById("elements-section");
|
||||||
const ichingSectionEl = document.getElementById("iching-section");
|
const ichingSectionEl = document.getElementById("iching-section");
|
||||||
const kabbalahSectionEl = document.getElementById("kabbalah-section");
|
const kabbalahSectionEl = document.getElementById("kabbalah-section");
|
||||||
|
const kabbalahWorldsSectionEl = document.getElementById("kabbalah-worlds-section");
|
||||||
|
const kabbalahPathsSectionEl = document.getElementById("kabbalah-paths-section");
|
||||||
|
const kabbalahCrossSectionEl = document.getElementById("kabbalah-cross-section");
|
||||||
const kabbalahTreeSectionEl = document.getElementById("kabbalah-tree-section");
|
const kabbalahTreeSectionEl = document.getElementById("kabbalah-tree-section");
|
||||||
const cubeSectionEl = document.getElementById("cube-section");
|
const cubeSectionEl = document.getElementById("cube-section");
|
||||||
const alphabetSectionEl = document.getElementById("alphabet-section");
|
const alphabetSectionEl = document.getElementById("alphabet-section");
|
||||||
@@ -78,6 +81,10 @@ const openCyclesEl = document.getElementById("open-cycles");
|
|||||||
const openElementsEl = document.getElementById("open-elements");
|
const openElementsEl = document.getElementById("open-elements");
|
||||||
const openIChingEl = document.getElementById("open-iching");
|
const openIChingEl = document.getElementById("open-iching");
|
||||||
const openKabbalahEl = document.getElementById("open-kabbalah");
|
const openKabbalahEl = document.getElementById("open-kabbalah");
|
||||||
|
const openKabbalahSephirotEl = document.getElementById("open-kabbalah-sephirot");
|
||||||
|
const openKabbalahWorldsEl = document.getElementById("open-kabbalah-worlds");
|
||||||
|
const openKabbalahPathsEl = document.getElementById("open-kabbalah-paths");
|
||||||
|
const openKabbalahCrossEl = document.getElementById("open-kabbalah-cross");
|
||||||
const openKabbalahTreeEl = document.getElementById("open-kabbalah-tree");
|
const openKabbalahTreeEl = document.getElementById("open-kabbalah-tree");
|
||||||
const openKabbalahCubeEl = document.getElementById("open-kabbalah-cube");
|
const openKabbalahCubeEl = document.getElementById("open-kabbalah-cube");
|
||||||
const openAlphabetEl = document.getElementById("open-alphabet");
|
const openAlphabetEl = document.getElementById("open-alphabet");
|
||||||
@@ -327,15 +334,22 @@ function getConnectionSettings() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncConnectionGateInputs() {
|
function normalizeConnectionSettingsInput(connectionSettings = null) {
|
||||||
const connectionSettings = getConnectionSettings();
|
return {
|
||||||
|
apiBaseUrl: String(connectionSettings?.apiBaseUrl || "").trim().replace(/\/+$/, ""),
|
||||||
|
apiKey: String(connectionSettings?.apiKey || "").trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncConnectionGateInputs(connectionSettings = getConnectionSettings()) {
|
||||||
|
const normalizedConnectionSettings = normalizeConnectionSettingsInput(connectionSettings);
|
||||||
|
|
||||||
if (connectionGateBaseUrlEl) {
|
if (connectionGateBaseUrlEl) {
|
||||||
connectionGateBaseUrlEl.value = String(connectionSettings.apiBaseUrl || "");
|
connectionGateBaseUrlEl.value = normalizedConnectionSettings.apiBaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionGateApiKeyEl) {
|
if (connectionGateApiKeyEl) {
|
||||||
connectionGateApiKeyEl.value = String(connectionSettings.apiKey || "");
|
connectionGateApiKeyEl.value = normalizedConnectionSettings.apiKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,8 +366,8 @@ function setConnectionGateStatus(text, tone = "default") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showConnectionGate(message, tone = "default") {
|
function showConnectionGate(message, tone = "default", connectionSettings = null) {
|
||||||
syncConnectionGateInputs();
|
syncConnectionGateInputs(connectionSettings || getConnectionSettings());
|
||||||
if (connectionGateEl) {
|
if (connectionGateEl) {
|
||||||
connectionGateEl.hidden = false;
|
connectionGateEl.hidden = false;
|
||||||
}
|
}
|
||||||
@@ -369,33 +383,49 @@ function hideConnectionGate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getConnectionSettingsFromGate() {
|
function getConnectionSettingsFromGate() {
|
||||||
return {
|
return normalizeConnectionSettingsInput({
|
||||||
apiBaseUrl: String(connectionGateBaseUrlEl?.value || "").trim(),
|
apiBaseUrl: String(connectionGateBaseUrlEl?.value || "").trim(),
|
||||||
apiKey: String(connectionGateApiKeyEl?.value || "").trim()
|
apiKey: String(connectionGateApiKeyEl?.value || "").trim()
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function warmAllDeckImagesInBackground() {
|
||||||
|
const activeDeckId = String(window.TarotCardImages?.getActiveDeck?.() || "").trim();
|
||||||
|
|
||||||
|
window.TarotCardImages?.scheduleAllDeckImagePreload?.({
|
||||||
|
startDeckId: activeDeckId,
|
||||||
|
background: true,
|
||||||
|
includeThumbnails: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureConnectedApp(nextConnectionSettings = null) {
|
async function ensureConnectedApp(nextConnectionSettings = null) {
|
||||||
if (nextConnectionSettings) {
|
const configuredConnection = nextConnectionSettings
|
||||||
window.TarotAppConfig?.updateConnectionSettings?.(nextConnectionSettings);
|
? normalizeConnectionSettingsInput(nextConnectionSettings)
|
||||||
|
: getConnectionSettings();
|
||||||
|
|
||||||
|
if (!nextConnectionSettings) {
|
||||||
|
syncConnectionGateInputs(configuredConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
syncConnectionGateInputs();
|
|
||||||
|
|
||||||
const configuredConnection = getConnectionSettings();
|
|
||||||
if (!configuredConnection.apiBaseUrl) {
|
if (!configuredConnection.apiBaseUrl) {
|
||||||
showConnectionGate("Enter an API Base URL to load TaroTime.", "error");
|
showConnectionGate("Enter an API Base URL to load TaroTime.", "error", configuredConnection);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
showConnectionGate("Connecting to the API...", "pending");
|
showConnectionGate("Connecting to the API...", "pending", configuredConnection);
|
||||||
|
|
||||||
const probeResult = await window.TarotDataService?.probeConnection?.();
|
const probeResult = await window.TarotDataService?.probeConnection?.(configuredConnection);
|
||||||
if (!probeResult?.ok) {
|
if (!probeResult?.ok) {
|
||||||
showConnectionGate(probeResult?.message || "Unable to reach the API.", "error");
|
showConnectionGate(probeResult?.message || "Unable to reach the API.", "error", configuredConnection);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nextConnectionSettings) {
|
||||||
|
window.TarotAppConfig?.updateConnectionSettings?.(configuredConnection);
|
||||||
|
syncConnectionGateInputs(configuredConnection);
|
||||||
|
}
|
||||||
|
|
||||||
hideConnectionGate();
|
hideConnectionGate();
|
||||||
if (!hasRenderedConnectedShell) {
|
if (!hasRenderedConnectedShell) {
|
||||||
sectionStateUi.setActiveSection?.("home");
|
sectionStateUi.setActiveSection?.("home");
|
||||||
@@ -405,6 +435,7 @@ async function ensureConnectedApp(nextConnectionSettings = null) {
|
|||||||
setConnectionGateStatus("Connected.", "success");
|
setConnectionGateStatus("Connected.", "success");
|
||||||
setStatus(`Connected to ${configuredConnection.apiBaseUrl}.`);
|
setStatus(`Connected to ${configuredConnection.apiBaseUrl}.`);
|
||||||
await appRuntime.renderWeek?.();
|
await appRuntime.renderWeek?.();
|
||||||
|
warmAllDeckImagesInBackground();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +522,9 @@ sectionStateUi.init?.({
|
|||||||
elementsSectionEl,
|
elementsSectionEl,
|
||||||
ichingSectionEl,
|
ichingSectionEl,
|
||||||
kabbalahSectionEl,
|
kabbalahSectionEl,
|
||||||
|
kabbalahWorldsSectionEl,
|
||||||
|
kabbalahPathsSectionEl,
|
||||||
|
kabbalahCrossSectionEl,
|
||||||
kabbalahTreeSectionEl,
|
kabbalahTreeSectionEl,
|
||||||
cubeSectionEl,
|
cubeSectionEl,
|
||||||
alphabetSectionEl,
|
alphabetSectionEl,
|
||||||
@@ -519,6 +553,10 @@ sectionStateUi.init?.({
|
|||||||
openElementsEl,
|
openElementsEl,
|
||||||
openIChingEl,
|
openIChingEl,
|
||||||
openKabbalahEl,
|
openKabbalahEl,
|
||||||
|
openKabbalahSephirotEl,
|
||||||
|
openKabbalahWorldsEl,
|
||||||
|
openKabbalahPathsEl,
|
||||||
|
openKabbalahCrossEl,
|
||||||
openKabbalahTreeEl,
|
openKabbalahTreeEl,
|
||||||
openKabbalahCubeEl,
|
openKabbalahCubeEl,
|
||||||
openAlphabetEl,
|
openAlphabetEl,
|
||||||
@@ -626,6 +664,10 @@ navigationUi.init?.({
|
|||||||
openElementsEl,
|
openElementsEl,
|
||||||
openIChingEl,
|
openIChingEl,
|
||||||
openKabbalahEl,
|
openKabbalahEl,
|
||||||
|
openKabbalahSephirotEl,
|
||||||
|
openKabbalahWorldsEl,
|
||||||
|
openKabbalahPathsEl,
|
||||||
|
openKabbalahCrossEl,
|
||||||
openKabbalahTreeEl,
|
openKabbalahTreeEl,
|
||||||
openKabbalahCubeEl,
|
openKabbalahCubeEl,
|
||||||
openAlphabetEl,
|
openAlphabetEl,
|
||||||
|
|||||||
+34
-1
@@ -137,7 +137,7 @@
|
|||||||
const standardMinorSuits = ["Wands", "Cups", "Swords", "Disks"];
|
const standardMinorSuits = ["Wands", "Cups", "Swords", "Disks"];
|
||||||
const standardDeckCardNames = buildStandardDeckCardNames();
|
const standardDeckCardNames = buildStandardDeckCardNames();
|
||||||
|
|
||||||
let deckManifestSources = buildDeckManifestSources();
|
let deckManifestSources = null;
|
||||||
|
|
||||||
const manifestCache = new Map();
|
const manifestCache = new Map();
|
||||||
const cardBackCache = new Map();
|
const cardBackCache = new Map();
|
||||||
@@ -161,6 +161,25 @@
|
|||||||
.replace(/\/+$/, "");
|
.replace(/\/+$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildManifestRequestHeaders(path) {
|
||||||
|
const normalizedPath = String(path || "").trim();
|
||||||
|
const apiBaseUrl = getApiBaseUrl();
|
||||||
|
const apiKey = String(
|
||||||
|
window.TarotDataService?.getApiKey?.()
|
||||||
|
|| window.TarotAppConfig?.getApiKey?.()
|
||||||
|
|| window.TarotAppConfig?.apiKey
|
||||||
|
|| ""
|
||||||
|
).trim();
|
||||||
|
|
||||||
|
if (!normalizedPath || !apiBaseUrl || !apiKey || !normalizedPath.startsWith(apiBaseUrl)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"x-api-key": apiKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function rewriteBasePathForApi(basePath) {
|
function rewriteBasePathForApi(basePath) {
|
||||||
const normalizedBasePath = String(basePath || "").trim();
|
const normalizedBasePath = String(basePath || "").trim();
|
||||||
if (!normalizedBasePath) {
|
if (!normalizedBasePath) {
|
||||||
@@ -530,6 +549,9 @@
|
|||||||
try {
|
try {
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
request.open("GET", encodeURI(path), false);
|
request.open("GET", encodeURI(path), false);
|
||||||
|
Object.entries(buildManifestRequestHeaders(path)).forEach(([headerName, headerValue]) => {
|
||||||
|
request.setRequestHeader(headerName, headerValue);
|
||||||
|
});
|
||||||
request.send(null);
|
request.send(null);
|
||||||
|
|
||||||
const okStatus = (request.status >= 200 && request.status < 300) || request.status === 0;
|
const okStatus = (request.status >= 200 && request.status < 300) || request.status === 0;
|
||||||
@@ -1164,6 +1186,9 @@
|
|||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
markDeckAsWarmed(normalizedDeckId);
|
markDeckAsWarmed(normalizedDeckId);
|
||||||
|
if (options.background) {
|
||||||
|
emitDeckPreloadStatus();
|
||||||
|
}
|
||||||
if (!options.background) {
|
if (!options.background) {
|
||||||
setDeckPreloadStatus({
|
setDeckPreloadStatus({
|
||||||
activeDeckId: normalizedDeckId,
|
activeDeckId: normalizedDeckId,
|
||||||
@@ -1211,6 +1236,13 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleAllDeckImagePreload(options = {}) {
|
||||||
|
return deferPreload(() => preloadAllDeckImages({
|
||||||
|
...defaultDeckWarmupOptions,
|
||||||
|
...options
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function resolveDisplayNameWithDeck(deckId, cardName, trumpNumber) {
|
function resolveDisplayNameWithDeck(deckId, cardName, trumpNumber) {
|
||||||
const manifest = getDeckManifest(deckId);
|
const manifest = getDeckManifest(deckId);
|
||||||
const fallbackName = String(cardName || "").trim();
|
const fallbackName = String(cardName || "").trim();
|
||||||
@@ -1333,6 +1365,7 @@
|
|||||||
resolveTarotCardBackThumbnail,
|
resolveTarotCardBackThumbnail,
|
||||||
preloadDeckImages,
|
preloadDeckImages,
|
||||||
preloadAllDeckImages,
|
preloadAllDeckImages,
|
||||||
|
scheduleAllDeckImagePreload,
|
||||||
ensureImageLoaded,
|
ensureImageLoaded,
|
||||||
isImageLoaded,
|
isImageLoaded,
|
||||||
getDeckPreloadStatus: () => emitDeckPreloadStatus(),
|
getDeckPreloadStatus: () => emitDeckPreloadStatus(),
|
||||||
|
|||||||
+24
-9
@@ -102,8 +102,22 @@
|
|||||||
pluto: "Pluto"
|
pluto: "Pluto"
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildRequestHeaders() {
|
function resolveConnectionSettings(connectionSettings = null) {
|
||||||
const apiKey = getApiKey();
|
if (!connectionSettings || typeof connectionSettings !== "object") {
|
||||||
|
return {
|
||||||
|
apiBaseUrl: getApiBaseUrl(),
|
||||||
|
apiKey: getApiKey()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
apiBaseUrl: normalizeApiBaseUrl(connectionSettings.apiBaseUrl),
|
||||||
|
apiKey: String(connectionSettings.apiKey || "").trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRequestHeaders(connectionSettings = null) {
|
||||||
|
const { apiKey } = resolveConnectionSettings(connectionSettings);
|
||||||
return apiKey
|
return apiKey
|
||||||
? {
|
? {
|
||||||
"x-api-key": apiKey
|
"x-api-key": apiKey
|
||||||
@@ -172,8 +186,8 @@
|
|||||||
.join("/");
|
.join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildApiUrl(path, query = {}) {
|
function buildApiUrl(path, query = {}, connectionSettings = null) {
|
||||||
const apiBaseUrl = getApiBaseUrl();
|
const { apiBaseUrl } = resolveConnectionSettings(connectionSettings);
|
||||||
if (!apiBaseUrl) {
|
if (!apiBaseUrl) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -569,8 +583,9 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function probeConnection() {
|
async function probeConnection(connectionSettings = null) {
|
||||||
const apiBaseUrl = getApiBaseUrl();
|
const resolvedConnection = resolveConnectionSettings(connectionSettings);
|
||||||
|
const apiBaseUrl = resolvedConnection.apiBaseUrl;
|
||||||
if (!apiBaseUrl) {
|
if (!apiBaseUrl) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -580,11 +595,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
headers: buildRequestHeaders()
|
headers: buildRequestHeaders(resolvedConnection)
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const healthResponse = await fetch(buildApiUrl("/api/v1/health"), requestOptions);
|
const healthResponse = await fetch(buildApiUrl("/api/v1/health", {}, resolvedConnection), requestOptions);
|
||||||
if (!healthResponse.ok) {
|
if (!healthResponse.ok) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -594,7 +609,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const health = await healthResponse.json().catch(() => null);
|
const health = await healthResponse.json().catch(() => null);
|
||||||
const protectedResponse = await fetch(buildApiUrl("/api/v1/decks/options"), requestOptions);
|
const protectedResponse = await fetch(buildApiUrl("/api/v1/decks/options", {}, resolvedConnection), requestOptions);
|
||||||
|
|
||||||
if (protectedResponse.status === 401 || protectedResponse.status === 403) {
|
if (protectedResponse.status === 401 || protectedResponse.status === 403) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+161
-1
@@ -792,7 +792,7 @@
|
|||||||
}
|
}
|
||||||
.tarot-detail-top {
|
.tarot-detail-top {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 150px minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
@@ -866,6 +866,93 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
|
.tarot-deck-gallery-card {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
.tarot-deck-gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.tarot-deck-variant {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
align-content: start;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #3f3f46;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #18181b;
|
||||||
|
color: #e4e4e7;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
transition: background 120ms, border-color 120ms, transform 120ms;
|
||||||
|
}
|
||||||
|
.tarot-deck-variant:hover {
|
||||||
|
background: #27272a;
|
||||||
|
border-color: #52525b;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.tarot-deck-variant.is-active {
|
||||||
|
border-color: #a5b4fc;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(165, 180, 252, 0.28);
|
||||||
|
}
|
||||||
|
.tarot-deck-variant-image {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 2 / 3;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center;
|
||||||
|
display: block;
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #3f3f46;
|
||||||
|
background: #09090b;
|
||||||
|
}
|
||||||
|
.tarot-deck-variant-label {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.tarot-deck-variant-deck {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f4f4f5;
|
||||||
|
}
|
||||||
|
.tarot-deck-variant-name {
|
||||||
|
color: #a1a1aa;
|
||||||
|
}
|
||||||
|
.tarot-deck-variant-active {
|
||||||
|
color: #a5b4fc;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.tarot-deck-gallery {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-deck-variant {
|
||||||
|
padding: 6px;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-deck-variant-label {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tarot-deck-variant-active {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
.tarot-deck-gallery {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
.tarot-keywords {
|
.tarot-keywords {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -3190,6 +3277,43 @@
|
|||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
color: #e4e4e7;
|
color: #e4e4e7;
|
||||||
}
|
}
|
||||||
|
.detail-sequence-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.detail-sequence-btn {
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid #3f3f46;
|
||||||
|
background: #111118;
|
||||||
|
color: #f4f4f5;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
transition: background 120ms, border-color 120ms, color 120ms;
|
||||||
|
}
|
||||||
|
.detail-sequence-btn:hover {
|
||||||
|
background: #27272a;
|
||||||
|
border-color: #52525b;
|
||||||
|
}
|
||||||
|
.detail-sequence-btn:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.detail-sequence-btn:disabled:hover {
|
||||||
|
background: #111118;
|
||||||
|
border-color: #3f3f46;
|
||||||
|
}
|
||||||
|
.detail-sequence-position {
|
||||||
|
min-width: 78px;
|
||||||
|
color: #a1a1aa;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
.planet-meta-grid {
|
.planet-meta-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
@@ -3520,6 +3644,34 @@
|
|||||||
}
|
}
|
||||||
#kabbalah-section[hidden] { display: none; }
|
#kabbalah-section[hidden] { display: none; }
|
||||||
|
|
||||||
|
#kabbalah-worlds-section {
|
||||||
|
height: calc(100vh - 61px);
|
||||||
|
background: #18181b;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#kabbalah-worlds-section[hidden] { display: none; }
|
||||||
|
|
||||||
|
#kabbalah-paths-section {
|
||||||
|
height: calc(100vh - 61px);
|
||||||
|
background: #18181b;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#kabbalah-paths-section[hidden] { display: none; }
|
||||||
|
|
||||||
|
#kabbalah-cross-section {
|
||||||
|
height: calc(100vh - 61px);
|
||||||
|
background: #18181b;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#kabbalah-cross-section[hidden] { display: none; }
|
||||||
|
|
||||||
|
.kab-browser-intro {
|
||||||
|
padding: 0 12px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
#kabbalah-tree-section {
|
#kabbalah-tree-section {
|
||||||
height: calc(100vh - 61px);
|
height: calc(100vh - 61px);
|
||||||
background: #18181b;
|
background: #18181b;
|
||||||
@@ -5692,6 +5844,14 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-sequence-nav {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-sequence-position {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.alpha-text-controls--heading {
|
.alpha-text-controls--heading {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
hebrewById: new Map(),
|
hebrewById: new Map(),
|
||||||
dayLinksCache: new Map()
|
dayLinksCache: new Map()
|
||||||
};
|
};
|
||||||
|
let detailNavigator = null;
|
||||||
|
|
||||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||||
"the fool": 0,
|
"the fool": 0,
|
||||||
@@ -200,6 +201,7 @@
|
|||||||
|
|
||||||
function getElements() {
|
function getElements() {
|
||||||
return {
|
return {
|
||||||
|
sectionEl: document.getElementById("calendar-section"),
|
||||||
monthListEl: document.getElementById("calendar-month-list"),
|
monthListEl: document.getElementById("calendar-month-list"),
|
||||||
monthCountEl: document.getElementById("calendar-month-count"),
|
monthCountEl: document.getElementById("calendar-month-count"),
|
||||||
listTitleEl: document.getElementById("calendar-list-title"),
|
listTitleEl: document.getElementById("calendar-list-title"),
|
||||||
@@ -210,6 +212,9 @@
|
|||||||
searchClearEl: document.getElementById("calendar-search-clear"),
|
searchClearEl: document.getElementById("calendar-search-clear"),
|
||||||
detailNameEl: document.getElementById("calendar-detail-name"),
|
detailNameEl: document.getElementById("calendar-detail-name"),
|
||||||
detailSubEl: document.getElementById("calendar-detail-sub"),
|
detailSubEl: document.getElementById("calendar-detail-sub"),
|
||||||
|
detailPrevEl: document.getElementById("calendar-detail-prev"),
|
||||||
|
detailPositionEl: document.getElementById("calendar-detail-position"),
|
||||||
|
detailNextEl: document.getElementById("calendar-detail-next"),
|
||||||
detailBodyEl: document.getElementById("calendar-detail-body")
|
detailBodyEl: document.getElementById("calendar-detail-body")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -523,6 +528,66 @@
|
|||||||
|
|
||||||
function renderDetail(elements) {
|
function renderDetail(elements) {
|
||||||
calendarDetailUi.renderDetail?.(elements);
|
calendarDetailUi.renderDetail?.(elements);
|
||||||
|
syncDetailNavigation(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMonthSequenceState() {
|
||||||
|
const total = state.filteredMonths.length;
|
||||||
|
const currentIndex = state.filteredMonths.findIndex((month) => month.id === state.selectedMonthId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
currentIndex,
|
||||||
|
previousId: currentIndex > 0 ? state.filteredMonths[currentIndex - 1].id : "",
|
||||||
|
nextId: currentIndex >= 0 && currentIndex < total - 1 ? state.filteredMonths[currentIndex + 1].id : ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetailNavigator() {
|
||||||
|
if (detailNavigator || typeof window.TarotSequenceNav?.createSequenceNavigator !== "function") {
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
detailNavigator = window.TarotSequenceNav.createSequenceNavigator({
|
||||||
|
getElements,
|
||||||
|
isActive: (elements) => Boolean(elements?.sectionEl && elements.sectionEl.hidden === false),
|
||||||
|
getSequenceState: getMonthSequenceState,
|
||||||
|
getPrevButton: (elements) => elements?.detailPrevEl,
|
||||||
|
getNextButton: (elements) => elements?.detailNextEl,
|
||||||
|
getPositionEl: (elements) => elements?.detailPositionEl,
|
||||||
|
formatPositionText: ({ total, currentIndex }) => {
|
||||||
|
if (total > 0 && currentIndex >= 0) {
|
||||||
|
const suffix = state.searchQuery ? " shown" : "";
|
||||||
|
return `${currentIndex + 1} of ${total}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total > 0 ? `${total} months` : "No months";
|
||||||
|
},
|
||||||
|
selectTarget: (targetId, elements) => selectByMonthId(targetId, elements) !== false,
|
||||||
|
afterSelect: (targetId, elements) => {
|
||||||
|
scrollMonthIntoView(targetId, elements);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncDetailNavigation(elements = getElements()) {
|
||||||
|
getDetailNavigator()?.sync(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollMonthIntoView(monthId, elements = getElements()) {
|
||||||
|
elements?.monthListEl
|
||||||
|
?.querySelector(`[data-month-id="${monthId}"]`)
|
||||||
|
?.scrollIntoView({ block: "nearest" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAdjacentMonth(offset, elements = getElements()) {
|
||||||
|
return getDetailNavigator()?.step(offset, elements) === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindKeyboardNavigation(elements) {
|
||||||
|
getDetailNavigator()?.bind(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applySearchFilter(elements) {
|
function applySearchFilter(elements) {
|
||||||
@@ -613,6 +678,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bindDetailNavigation(elements) {
|
||||||
|
getDetailNavigator()?.bind(elements);
|
||||||
|
}
|
||||||
|
|
||||||
function loadCalendarType(calendarId, elements) {
|
function loadCalendarType(calendarId, elements) {
|
||||||
const months = state.calendarData[calendarId];
|
const months = state.calendarData[calendarId];
|
||||||
if (!Array.isArray(months)) {
|
if (!Array.isArray(months)) {
|
||||||
@@ -745,6 +814,8 @@
|
|||||||
bindYearInput(elements);
|
bindYearInput(elements);
|
||||||
bindSearchInput(elements);
|
bindSearchInput(elements);
|
||||||
bindCalendarTypeSelect(elements);
|
bindCalendarTypeSelect(elements);
|
||||||
|
bindDetailNavigation(elements);
|
||||||
|
bindKeyboardNavigation(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
applySearchFilter(elements);
|
applySearchFilter(elements);
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
hebrewById: new Map(),
|
hebrewById: new Map(),
|
||||||
calendarData: {}
|
calendarData: {}
|
||||||
};
|
};
|
||||||
|
let detailNavigator = null;
|
||||||
|
|
||||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||||
"the fool": 0,
|
"the fool": 0,
|
||||||
@@ -93,6 +94,7 @@
|
|||||||
|
|
||||||
function getElements() {
|
function getElements() {
|
||||||
return {
|
return {
|
||||||
|
sectionEl: document.getElementById("holiday-section"),
|
||||||
sourceSelectEl: document.getElementById("holiday-source-select"),
|
sourceSelectEl: document.getElementById("holiday-source-select"),
|
||||||
yearInputEl: document.getElementById("holiday-year-input"),
|
yearInputEl: document.getElementById("holiday-year-input"),
|
||||||
searchInputEl: document.getElementById("holiday-search-input"),
|
searchInputEl: document.getElementById("holiday-search-input"),
|
||||||
@@ -101,6 +103,9 @@
|
|||||||
listEl: document.getElementById("holiday-list"),
|
listEl: document.getElementById("holiday-list"),
|
||||||
detailNameEl: document.getElementById("holiday-detail-name"),
|
detailNameEl: document.getElementById("holiday-detail-name"),
|
||||||
detailSubEl: document.getElementById("holiday-detail-sub"),
|
detailSubEl: document.getElementById("holiday-detail-sub"),
|
||||||
|
detailPrevEl: document.getElementById("holiday-detail-prev"),
|
||||||
|
detailPositionEl: document.getElementById("holiday-detail-position"),
|
||||||
|
detailNextEl: document.getElementById("holiday-detail-next"),
|
||||||
detailBodyEl: document.getElementById("holiday-detail-body")
|
detailBodyEl: document.getElementById("holiday-detail-body")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -225,6 +230,7 @@
|
|||||||
detailNameEl.textContent = "--";
|
detailNameEl.textContent = "--";
|
||||||
detailSubEl.textContent = "Select a holiday to explore";
|
detailSubEl.textContent = "Select a holiday to explore";
|
||||||
detailBodyEl.innerHTML = "";
|
detailBodyEl.innerHTML = "";
|
||||||
|
syncDetailNavigation(elements);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +238,66 @@
|
|||||||
detailSubEl.textContent = `${holidayDataUi.calendarLabel(holiday?.calendarId)} - ${holidayDataUi.monthLabelForCalendar(state.calendarData, holiday?.calendarId, holiday?.monthId)}`;
|
detailSubEl.textContent = `${holidayDataUi.calendarLabel(holiday?.calendarId)} - ${holidayDataUi.monthLabelForCalendar(state.calendarData, holiday?.calendarId, holiday?.monthId)}`;
|
||||||
detailBodyEl.innerHTML = renderHolidayDetail(holiday);
|
detailBodyEl.innerHTML = renderHolidayDetail(holiday);
|
||||||
attachNavHandlers(detailBodyEl);
|
attachNavHandlers(detailBodyEl);
|
||||||
|
syncDetailNavigation(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHolidaySequenceState() {
|
||||||
|
const total = state.filteredHolidays.length;
|
||||||
|
const currentIndex = state.filteredHolidays.findIndex((holiday) => holiday.id === state.selectedHolidayId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
currentIndex,
|
||||||
|
previousId: currentIndex > 0 ? state.filteredHolidays[currentIndex - 1].id : "",
|
||||||
|
nextId: currentIndex >= 0 && currentIndex < total - 1 ? state.filteredHolidays[currentIndex + 1].id : ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetailNavigator() {
|
||||||
|
if (detailNavigator || typeof window.TarotSequenceNav?.createSequenceNavigator !== "function") {
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
detailNavigator = window.TarotSequenceNav.createSequenceNavigator({
|
||||||
|
getElements,
|
||||||
|
isActive: (elements) => Boolean(elements?.sectionEl && elements.sectionEl.hidden === false),
|
||||||
|
getSequenceState: getHolidaySequenceState,
|
||||||
|
getPrevButton: (elements) => elements?.detailPrevEl,
|
||||||
|
getNextButton: (elements) => elements?.detailNextEl,
|
||||||
|
getPositionEl: (elements) => elements?.detailPositionEl,
|
||||||
|
formatPositionText: ({ total, currentIndex }) => {
|
||||||
|
if (total > 0 && currentIndex >= 0) {
|
||||||
|
const suffix = state.searchQuery ? " shown" : "";
|
||||||
|
return `${currentIndex + 1} of ${total}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total > 0 ? `${total} holidays` : "No holidays";
|
||||||
|
},
|
||||||
|
selectTarget: (targetId, elements) => selectByHolidayId(targetId, elements) !== false,
|
||||||
|
afterSelect: (targetId, elements) => {
|
||||||
|
scrollHolidayIntoView(targetId, elements);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncDetailNavigation(elements = getElements()) {
|
||||||
|
getDetailNavigator()?.sync(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollHolidayIntoView(holidayId, elements = getElements()) {
|
||||||
|
elements?.listEl
|
||||||
|
?.querySelector(`[data-holiday-id="${holidayId}"]`)
|
||||||
|
?.scrollIntoView({ block: "nearest" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAdjacentHoliday(offset, elements = getElements()) {
|
||||||
|
return getDetailNavigator()?.step(offset, elements) === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindKeyboardNavigation(elements) {
|
||||||
|
getDetailNavigator()?.bind(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyFilters(elements) {
|
function applyFilters(elements) {
|
||||||
@@ -307,6 +373,8 @@
|
|||||||
elements.searchInputEl.focus();
|
elements.searchInputEl.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindKeyboardNavigation(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachNavHandlers(detailBodyEl) {
|
function attachNavHandlers(detailBodyEl) {
|
||||||
|
|||||||
@@ -309,6 +309,62 @@
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderWorldLayerDetail(context) {
|
||||||
|
const { worldLayer, tree, elements } = context;
|
||||||
|
if (!worldLayer || !elements?.detailBodyEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.detailNameEl.textContent = String(worldLayer.world || "Qabalistic World");
|
||||||
|
elements.detailSubEl.textContent = [
|
||||||
|
worldLayer.slot ? `${worldLayer.slot}: ${worldLayer.letterChar || ""}`.trim() : "",
|
||||||
|
worldLayer.soulLayer
|
||||||
|
].filter(Boolean).join(" · ");
|
||||||
|
|
||||||
|
elements.detailBodyEl.innerHTML = "";
|
||||||
|
elements.detailBodyEl.appendChild(metaCard(
|
||||||
|
"World Layer",
|
||||||
|
`${worldLayer.worldLayer || "—"}${worldLayer.worldDescription ? ` · ${worldLayer.worldDescription}` : ""}`,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
elements.detailBodyEl.appendChild(metaCard(
|
||||||
|
"Soul Layer",
|
||||||
|
`${worldLayer.soulLayer || "—"}${worldLayer.soulTitle ? ` — ${worldLayer.soulTitle}` : ""}${worldLayer.soulDescription ? `: ${worldLayer.soulDescription}` : ""}`,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
|
||||||
|
const linkedParts = [];
|
||||||
|
const hebrewLetterId = context.resolveHebrewLetterId(worldLayer.hebrewToken);
|
||||||
|
if (hebrewLetterId) {
|
||||||
|
linkedParts.push(createInlineEventLink(
|
||||||
|
`${worldLayer.letterChar || ""} ${worldLayer.hebrewToken || ""}`.replace(/\s+/g, " ").trim(),
|
||||||
|
"nav:alphabet",
|
||||||
|
{
|
||||||
|
alphabet: "hebrew",
|
||||||
|
hebrewLetterId
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkedPath = context.findPathByHebrewToken(tree, worldLayer.hebrewToken);
|
||||||
|
if (linkedPath?.pathNumber != null) {
|
||||||
|
if (linkedParts.length) {
|
||||||
|
linkedParts.push(" · ");
|
||||||
|
}
|
||||||
|
linkedParts.push(createInlineEventLink(
|
||||||
|
`Path ${linkedPath.pathNumber}`,
|
||||||
|
"nav:kabbalah-path",
|
||||||
|
{ pathNo: Number(linkedPath.pathNumber) }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkedParts.length) {
|
||||||
|
elements.detailBodyEl.appendChild(metaCard("Linked Attributions", inlineValue(linkedParts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.detailBodyEl.appendChild(buildFourWorldsCard(tree, worldLayer.hebrewToken, context));
|
||||||
|
}
|
||||||
|
|
||||||
function splitCorrespondenceNames(value) {
|
function splitCorrespondenceNames(value) {
|
||||||
return String(value || "")
|
return String(value || "")
|
||||||
.split(/,|;|·|\/|\bor\b|\band\b|\+/i)
|
.split(/,|;|·|\/|\bor\b|\band\b|\+/i)
|
||||||
@@ -419,12 +475,14 @@
|
|||||||
|
|
||||||
function renderSephiraDetail(context) {
|
function renderSephiraDetail(context) {
|
||||||
const { seph, tree, elements } = context;
|
const { seph, tree, elements } = context;
|
||||||
elements.detailNameEl.textContent = `${seph.number} · ${seph.name}`;
|
const displayNumber = String(seph.displayNumber || seph.number || "").trim();
|
||||||
|
elements.detailNameEl.textContent = displayNumber
|
||||||
|
? `${displayNumber} · ${seph.name}`
|
||||||
|
: `${seph.name}`;
|
||||||
elements.detailSubEl.textContent =
|
elements.detailSubEl.textContent =
|
||||||
[seph.nameHebrew, seph.translation, seph.planet].filter(Boolean).join(" · ");
|
[seph.nameHebrew, seph.translation, seph.planet].filter(Boolean).join(" · ");
|
||||||
|
|
||||||
elements.detailBodyEl.innerHTML = "";
|
elements.detailBodyEl.innerHTML = "";
|
||||||
elements.detailBodyEl.appendChild(buildFourWorldsCard(tree, "", context));
|
|
||||||
elements.detailBodyEl.appendChild(buildPlanetLuminaryCard(seph.planet, context));
|
elements.detailBodyEl.appendChild(buildPlanetLuminaryCard(seph.planet, context));
|
||||||
elements.detailBodyEl.appendChild(metaCard("Intelligence", seph.intelligence));
|
elements.detailBodyEl.appendChild(metaCard("Intelligence", seph.intelligence));
|
||||||
elements.detailBodyEl.appendChild(buildTarotAttributionCard(seph.tarot));
|
elements.detailBodyEl.appendChild(buildTarotAttributionCard(seph.tarot));
|
||||||
@@ -484,7 +542,6 @@
|
|||||||
elements.detailSubEl.textContent = [path.tarot?.card, astro].filter(Boolean).join(" · ");
|
elements.detailSubEl.textContent = [path.tarot?.card, astro].filter(Boolean).join(" · ");
|
||||||
|
|
||||||
elements.detailBodyEl.innerHTML = "";
|
elements.detailBodyEl.innerHTML = "";
|
||||||
elements.detailBodyEl.appendChild(buildFourWorldsCard(tree, context.activeHebrewToken, context));
|
|
||||||
elements.detailBodyEl.appendChild(buildConnectsCard(path, fromName, toName));
|
elements.detailBodyEl.appendChild(buildConnectsCard(path, fromName, toName));
|
||||||
elements.detailBodyEl.appendChild(buildHebrewLetterCard(letter, context));
|
elements.detailBodyEl.appendChild(buildHebrewLetterCard(letter, context));
|
||||||
elements.detailBodyEl.appendChild(buildAstrologyCard(path.astrology, context));
|
elements.detailBodyEl.appendChild(buildAstrologyCard(path.astrology, context));
|
||||||
@@ -543,6 +600,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.KabbalahDetailUi = {
|
window.KabbalahDetailUi = {
|
||||||
|
renderWorldLayerDetail,
|
||||||
renderSephiraDetail,
|
renderSephiraDetail,
|
||||||
renderPathDetail,
|
renderPathDetail,
|
||||||
renderRoseLandingIntro
|
renderRoseLandingIntro
|
||||||
|
|||||||
+992
-32
File diff suppressed because it is too large
Load Diff
+45
-7
@@ -22,6 +22,20 @@
|
|||||||
return config.getMagickDataset?.() || null;
|
return config.getMagickDataset?.() || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getKabbalahPathNo(detail) {
|
||||||
|
if (!detail || typeof detail !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawValue = detail.pathNo ?? detail["path-no"] ?? null;
|
||||||
|
if (rawValue === null || rawValue === undefined || rawValue === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numericValue = Number(rawValue);
|
||||||
|
return Number.isFinite(numericValue) ? numericValue : null;
|
||||||
|
}
|
||||||
|
|
||||||
const DETAIL_VIEW_SELECTOR_BY_SECTION = {
|
const DETAIL_VIEW_SELECTOR_BY_SECTION = {
|
||||||
tarot: "#tarot-browse-view .tarot-layout",
|
tarot: "#tarot-browse-view .tarot-layout",
|
||||||
cube: "#cube-layout",
|
cube: "#cube-layout",
|
||||||
@@ -31,6 +45,10 @@
|
|||||||
iching: "#iching-section .planet-layout",
|
iching: "#iching-section .planet-layout",
|
||||||
gods: "#gods-section .planet-layout",
|
gods: "#gods-section .planet-layout",
|
||||||
calendar: "#calendar-section .planet-layout",
|
calendar: "#calendar-section .planet-layout",
|
||||||
|
kabbalah: "#kabbalah-section .planet-layout",
|
||||||
|
"kabbalah-worlds": "#kabbalah-worlds-section .planet-layout",
|
||||||
|
"kabbalah-paths": "#kabbalah-paths-section .planet-layout",
|
||||||
|
"kabbalah-cross": "#kabbalah-cross-section .kab-rose-layout",
|
||||||
"kabbalah-tree": "#kabbalah-tree-section .kab-layout",
|
"kabbalah-tree": "#kabbalah-tree-section .kab-layout",
|
||||||
planets: "#planet-section .planet-layout",
|
planets: "#planet-section .planet-layout",
|
||||||
elements: "#elements-section .planet-layout"
|
elements: "#elements-section .planet-layout"
|
||||||
@@ -167,12 +185,28 @@
|
|||||||
setActiveSection(getActiveSection() === "kabbalah" ? "home" : "kabbalah");
|
setActiveSection(getActiveSection() === "kabbalah" ? "home" : "kabbalah");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bindClick(elements.openKabbalahSephirotEl, () => {
|
||||||
|
setActiveSection("kabbalah");
|
||||||
|
});
|
||||||
|
|
||||||
|
bindClick(elements.openKabbalahWorldsEl, () => {
|
||||||
|
setActiveSection("kabbalah-worlds");
|
||||||
|
});
|
||||||
|
|
||||||
|
bindClick(elements.openKabbalahPathsEl, () => {
|
||||||
|
setActiveSection("kabbalah-paths");
|
||||||
|
});
|
||||||
|
|
||||||
|
bindClick(elements.openKabbalahCrossEl, () => {
|
||||||
|
setActiveSection("kabbalah-cross");
|
||||||
|
});
|
||||||
|
|
||||||
bindClick(elements.openKabbalahTreeEl, () => {
|
bindClick(elements.openKabbalahTreeEl, () => {
|
||||||
setActiveSection(getActiveSection() === "kabbalah-tree" ? "home" : "kabbalah-tree");
|
setActiveSection("kabbalah-tree");
|
||||||
});
|
});
|
||||||
|
|
||||||
bindClick(elements.openKabbalahCubeEl, () => {
|
bindClick(elements.openKabbalahCubeEl, () => {
|
||||||
setActiveSection(getActiveSection() === "cube" ? "home" : "cube");
|
setActiveSection("cube");
|
||||||
});
|
});
|
||||||
|
|
||||||
bindClick(elements.openAlphabetWordEl, () => {
|
bindClick(elements.openAlphabetWordEl, () => {
|
||||||
@@ -403,17 +437,21 @@
|
|||||||
|
|
||||||
document.addEventListener("nav:kabbalah-path", (event) => {
|
document.addEventListener("nav:kabbalah-path", (event) => {
|
||||||
const magickDataset = getMagickDataset();
|
const magickDataset = getMagickDataset();
|
||||||
const pathNo = event?.detail?.pathNo;
|
const pathNo = getKabbalahPathNo(event?.detail);
|
||||||
if (typeof ensure.ensureKabbalahSection === "function" && magickDataset) {
|
if (typeof ensure.ensureKabbalahSection === "function" && magickDataset) {
|
||||||
ensure.ensureKabbalahSection(magickDataset);
|
ensure.ensureKabbalahSection(magickDataset);
|
||||||
}
|
}
|
||||||
setActiveSection("kabbalah-tree");
|
|
||||||
if (pathNo != null) {
|
if (pathNo != null) {
|
||||||
|
const targetSection = Number(pathNo) >= 11 ? "kabbalah-paths" : "kabbalah";
|
||||||
|
setActiveSection(targetSection);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
window.KabbalahSectionUi?.selectNode?.(pathNo);
|
window.KabbalahSectionUi?.selectNode?.(pathNo);
|
||||||
scheduleSectionDetailOnly("kabbalah-tree");
|
scheduleSectionDetailOnly(targetSection);
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setActiveSection("kabbalah-paths");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("nav:planet", (event) => {
|
document.addEventListener("nav:planet", (event) => {
|
||||||
@@ -473,7 +511,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("tarot:view-kab-path", (event) => {
|
document.addEventListener("tarot:view-kab-path", (event) => {
|
||||||
setActiveSection("kabbalah-tree");
|
setActiveSection("kabbalah-paths");
|
||||||
const pathNumber = event?.detail?.pathNumber;
|
const pathNumber = event?.detail?.pathNumber;
|
||||||
if (pathNumber != null) {
|
if (pathNumber != null) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -483,7 +521,7 @@
|
|||||||
} else {
|
} else {
|
||||||
kabbalahUi?.selectPathByNumber?.(pathNumber);
|
kabbalahUi?.selectPathByNumber?.(pathNumber);
|
||||||
}
|
}
|
||||||
scheduleSectionDetailOnly("kabbalah-tree");
|
scheduleSectionDetailOnly("kabbalah-paths");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+74
-3
@@ -21,6 +21,7 @@
|
|||||||
monthRefsByPlanetId: new Map(),
|
monthRefsByPlanetId: new Map(),
|
||||||
cubePlacementsByPlanetId: new Map()
|
cubePlacementsByPlanetId: new Map()
|
||||||
};
|
};
|
||||||
|
let detailNavigator = null;
|
||||||
|
|
||||||
function normalizePlanetToken(value) {
|
function normalizePlanetToken(value) {
|
||||||
return String(value || "")
|
return String(value || "")
|
||||||
@@ -80,12 +81,16 @@
|
|||||||
|
|
||||||
function getElements() {
|
function getElements() {
|
||||||
return {
|
return {
|
||||||
|
planetSectionEl: document.getElementById("planet-section"),
|
||||||
planetCardListEl: document.getElementById("planet-card-list"),
|
planetCardListEl: document.getElementById("planet-card-list"),
|
||||||
planetSearchInputEl: document.getElementById("planet-search-input"),
|
planetSearchInputEl: document.getElementById("planet-search-input"),
|
||||||
planetSearchClearEl: document.getElementById("planet-search-clear"),
|
planetSearchClearEl: document.getElementById("planet-search-clear"),
|
||||||
planetCountEl: document.getElementById("planet-card-count"),
|
planetCountEl: document.getElementById("planet-card-count"),
|
||||||
planetDetailNameEl: document.getElementById("planet-detail-name"),
|
planetDetailNameEl: document.getElementById("planet-detail-name"),
|
||||||
planetDetailTypeEl: document.getElementById("planet-detail-type"),
|
planetDetailTypeEl: document.getElementById("planet-detail-type"),
|
||||||
|
planetDetailPrevEl: document.getElementById("planet-detail-prev"),
|
||||||
|
planetDetailPositionEl: document.getElementById("planet-detail-position"),
|
||||||
|
planetDetailNextEl: document.getElementById("planet-detail-next"),
|
||||||
planetDetailSummaryEl: document.getElementById("planet-detail-summary"),
|
planetDetailSummaryEl: document.getElementById("planet-detail-summary"),
|
||||||
planetDetailFactsEl: document.getElementById("planet-detail-facts"),
|
planetDetailFactsEl: document.getElementById("planet-detail-facts"),
|
||||||
planetDetailAtmosphereEl: document.getElementById("planet-detail-atmosphere"),
|
planetDetailAtmosphereEl: document.getElementById("planet-detail-atmosphere"),
|
||||||
@@ -156,11 +161,14 @@
|
|||||||
if (!state.filteredEntries.some((entry) => entry.id === state.selectedId)) {
|
if (!state.filteredEntries.some((entry) => entry.id === state.selectedId)) {
|
||||||
if (state.filteredEntries.length > 0) {
|
if (state.filteredEntries.length > 0) {
|
||||||
selectById(state.filteredEntries[0].id, elements);
|
selectById(state.filteredEntries[0].id, elements);
|
||||||
|
} else {
|
||||||
|
syncDetailNavigation(elements);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelection(elements);
|
updateSelection(elements);
|
||||||
|
syncDetailNavigation(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearChildren(element) {
|
function clearChildren(element) {
|
||||||
@@ -434,6 +442,68 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSequenceState() {
|
||||||
|
const total = state.filteredEntries.length;
|
||||||
|
const currentIndex = state.filteredEntries.findIndex((entry) => entry.id === state.selectedId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
currentIndex,
|
||||||
|
previousId: currentIndex > 0 ? state.filteredEntries[currentIndex - 1].id : "",
|
||||||
|
nextId: currentIndex >= 0 && currentIndex < total - 1 ? state.filteredEntries[currentIndex + 1].id : ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetailNavigator() {
|
||||||
|
if (detailNavigator || typeof window.TarotSequenceNav?.createSequenceNavigator !== "function") {
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
detailNavigator = window.TarotSequenceNav.createSequenceNavigator({
|
||||||
|
getElements,
|
||||||
|
isActive: (elements) => Boolean(elements?.planetSectionEl && elements.planetSectionEl.hidden === false),
|
||||||
|
getSequenceState,
|
||||||
|
getPrevButton: (elements) => elements?.planetDetailPrevEl,
|
||||||
|
getNextButton: (elements) => elements?.planetDetailNextEl,
|
||||||
|
getPositionEl: (elements) => elements?.planetDetailPositionEl,
|
||||||
|
formatPositionText: ({ total, currentIndex }) => {
|
||||||
|
if (total > 0 && currentIndex >= 0) {
|
||||||
|
const suffix = state.searchQuery ? " shown" : "";
|
||||||
|
return `${currentIndex + 1} of ${total}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total > 0 ? `${total} bodies` : "No bodies";
|
||||||
|
},
|
||||||
|
selectTarget: (targetId, elements) => {
|
||||||
|
selectById(targetId, elements);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
afterSelect: (targetId, elements) => {
|
||||||
|
scrollEntryIntoView(targetId, elements);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncDetailNavigation(elements) {
|
||||||
|
getDetailNavigator()?.sync(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollEntryIntoView(id, elements) {
|
||||||
|
elements?.planetCardListEl
|
||||||
|
?.querySelector(`[data-planet-id="${id}"]`)
|
||||||
|
?.scrollIntoView({ block: "nearest" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAdjacentEntry(offset, elements) {
|
||||||
|
return getDetailNavigator()?.step(offset, elements) === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindKeyboardNavigation(elements) {
|
||||||
|
getDetailNavigator()?.bind(elements);
|
||||||
|
}
|
||||||
|
|
||||||
function selectById(id, elements) {
|
function selectById(id, elements) {
|
||||||
const entry = state.entries.find((planet) => planet.id === id);
|
const entry = state.entries.find((planet) => planet.id === id);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
@@ -443,6 +513,7 @@
|
|||||||
state.selectedId = entry.id;
|
state.selectedId = entry.id;
|
||||||
updateSelection(elements);
|
updateSelection(elements);
|
||||||
renderDetail(entry, elements);
|
renderDetail(entry, elements);
|
||||||
|
syncDetailNavigation(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderList(elements) {
|
function renderList(elements) {
|
||||||
@@ -580,6 +651,8 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindKeyboardNavigation(elements);
|
||||||
|
|
||||||
state.initialized = true;
|
state.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,9 +667,7 @@
|
|||||||
);
|
);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
selectById(entry.id, el);
|
selectById(entry.id, el);
|
||||||
el.planetCardListEl
|
scrollEntryIntoView(entry.id, el);
|
||||||
?.querySelector(`[data-planet-id="${entry.id}"]`)
|
|
||||||
?.scrollIntoView({ block: "nearest" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.PlanetSectionUi = {
|
window.PlanetSectionUi = {
|
||||||
|
|||||||
+15
-2
@@ -19,6 +19,9 @@
|
|||||||
"elements",
|
"elements",
|
||||||
"iching",
|
"iching",
|
||||||
"kabbalah",
|
"kabbalah",
|
||||||
|
"kabbalah-worlds",
|
||||||
|
"kabbalah-paths",
|
||||||
|
"kabbalah-cross",
|
||||||
"kabbalah-tree",
|
"kabbalah-tree",
|
||||||
"cube",
|
"cube",
|
||||||
"alphabet",
|
"alphabet",
|
||||||
@@ -109,9 +112,12 @@
|
|||||||
const isElementsOpen = activeSection === "elements";
|
const isElementsOpen = activeSection === "elements";
|
||||||
const isIChingOpen = activeSection === "iching";
|
const isIChingOpen = activeSection === "iching";
|
||||||
const isKabbalahOpen = activeSection === "kabbalah";
|
const isKabbalahOpen = activeSection === "kabbalah";
|
||||||
|
const isKabbalahWorldsOpen = activeSection === "kabbalah-worlds";
|
||||||
|
const isKabbalahPathsOpen = activeSection === "kabbalah-paths";
|
||||||
|
const isKabbalahCrossOpen = activeSection === "kabbalah-cross";
|
||||||
const isKabbalahTreeOpen = activeSection === "kabbalah-tree";
|
const isKabbalahTreeOpen = activeSection === "kabbalah-tree";
|
||||||
const isCubeOpen = activeSection === "cube";
|
const isCubeOpen = activeSection === "cube";
|
||||||
const isKabbalahMenuOpen = isKabbalahOpen || isKabbalahTreeOpen || isCubeOpen;
|
const isKabbalahMenuOpen = isKabbalahOpen || isKabbalahWorldsOpen || isKabbalahPathsOpen || isKabbalahCrossOpen || isKabbalahTreeOpen || isCubeOpen;
|
||||||
const isAlphabetOpen = activeSection === "alphabet";
|
const isAlphabetOpen = activeSection === "alphabet";
|
||||||
const isAlphabetLettersOpen = activeSection === "alphabet-letters";
|
const isAlphabetLettersOpen = activeSection === "alphabet-letters";
|
||||||
const isAlphabetTextOpen = activeSection === "alphabet-text";
|
const isAlphabetTextOpen = activeSection === "alphabet-text";
|
||||||
@@ -137,6 +143,9 @@
|
|||||||
setHidden(elements.elementsSectionEl, !isElementsOpen);
|
setHidden(elements.elementsSectionEl, !isElementsOpen);
|
||||||
setHidden(elements.ichingSectionEl, !isIChingOpen);
|
setHidden(elements.ichingSectionEl, !isIChingOpen);
|
||||||
setHidden(elements.kabbalahSectionEl, !isKabbalahOpen);
|
setHidden(elements.kabbalahSectionEl, !isKabbalahOpen);
|
||||||
|
setHidden(elements.kabbalahWorldsSectionEl, !isKabbalahWorldsOpen);
|
||||||
|
setHidden(elements.kabbalahPathsSectionEl, !isKabbalahPathsOpen);
|
||||||
|
setHidden(elements.kabbalahCrossSectionEl, !isKabbalahCrossOpen);
|
||||||
setHidden(elements.kabbalahTreeSectionEl, !isKabbalahTreeOpen);
|
setHidden(elements.kabbalahTreeSectionEl, !isKabbalahTreeOpen);
|
||||||
setHidden(elements.cubeSectionEl, !isCubeOpen);
|
setHidden(elements.cubeSectionEl, !isCubeOpen);
|
||||||
setHidden(elements.alphabetSectionEl, !isAlphabetOpen);
|
setHidden(elements.alphabetSectionEl, !isAlphabetOpen);
|
||||||
@@ -168,6 +177,10 @@
|
|||||||
setPressed(elements.openElementsEl, isElementsOpen);
|
setPressed(elements.openElementsEl, isElementsOpen);
|
||||||
setPressed(elements.openIChingEl, isIChingOpen);
|
setPressed(elements.openIChingEl, isIChingOpen);
|
||||||
setPressed(elements.openKabbalahEl, isKabbalahMenuOpen);
|
setPressed(elements.openKabbalahEl, isKabbalahMenuOpen);
|
||||||
|
toggleActive(elements.openKabbalahSephirotEl, isKabbalahOpen);
|
||||||
|
toggleActive(elements.openKabbalahWorldsEl, isKabbalahWorldsOpen);
|
||||||
|
toggleActive(elements.openKabbalahPathsEl, isKabbalahPathsOpen);
|
||||||
|
toggleActive(elements.openKabbalahCrossEl, isKabbalahCrossOpen);
|
||||||
toggleActive(elements.openKabbalahTreeEl, isKabbalahTreeOpen);
|
toggleActive(elements.openKabbalahTreeEl, isKabbalahTreeOpen);
|
||||||
toggleActive(elements.openKabbalahCubeEl, isCubeOpen);
|
toggleActive(elements.openKabbalahCubeEl, isCubeOpen);
|
||||||
setPressed(elements.openAlphabetEl, isAlphabetMenuOpen);
|
setPressed(elements.openAlphabetEl, isAlphabetMenuOpen);
|
||||||
@@ -249,7 +262,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKabbalahOpen || isKabbalahTreeOpen) {
|
if (isKabbalahOpen || isKabbalahWorldsOpen || isKabbalahPathsOpen || isKabbalahCrossOpen || isKabbalahTreeOpen) {
|
||||||
ensure.ensureKabbalahSection?.(magickDataset);
|
ensure.ensureKabbalahSection?.(magickDataset);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function normalizeSequenceState(sequence) {
|
||||||
|
return {
|
||||||
|
total: Math.max(0, Number(sequence?.total) || 0),
|
||||||
|
currentIndex: Number.isFinite(Number(sequence?.currentIndex)) ? Number(sequence.currentIndex) : -1,
|
||||||
|
previousKey: String(sequence?.previousKey ?? sequence?.previousId ?? "").trim(),
|
||||||
|
nextKey: String(sequence?.nextKey ?? sequence?.nextId ?? "").trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditableKeyTarget(target) {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target instanceof HTMLInputElement
|
||||||
|
|| target instanceof HTMLTextAreaElement
|
||||||
|
|| target instanceof HTMLSelectElement
|
||||||
|
|| target.isContentEditable
|
||||||
|
|| Boolean(target.closest("[contenteditable='true']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOpenModalDialog() {
|
||||||
|
return Boolean(document.querySelector("[role='dialog'][aria-modal='true'][aria-hidden='false']"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSequenceNavigator(config = {}) {
|
||||||
|
const getElements = typeof config.getElements === "function"
|
||||||
|
? config.getElements
|
||||||
|
: () => ({});
|
||||||
|
|
||||||
|
let buttonsBound = false;
|
||||||
|
let keyboardBound = false;
|
||||||
|
|
||||||
|
function getSequenceState() {
|
||||||
|
return normalizeSequenceState(
|
||||||
|
typeof config.getSequenceState === "function"
|
||||||
|
? config.getSequenceState()
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrevButton(elements) {
|
||||||
|
return typeof config.getPrevButton === "function" ? config.getPrevButton(elements) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextButton(elements) {
|
||||||
|
return typeof config.getNextButton === "function" ? config.getNextButton(elements) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPositionEl(elements) {
|
||||||
|
return typeof config.getPositionEl === "function" ? config.getPositionEl(elements) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActive(elements) {
|
||||||
|
return typeof config.isActive === "function" ? config.isActive(elements) !== false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTargetKey(sequence, offset) {
|
||||||
|
return offset < 0 ? sequence.previousKey : sequence.nextKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPositionText(sequence, elements) {
|
||||||
|
return typeof config.formatPositionText === "function"
|
||||||
|
? String(config.formatPositionText(sequence, elements) || "")
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectTarget(targetKey, elements, offset) {
|
||||||
|
if (!targetKey || typeof config.selectTarget !== "function") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.selectTarget(targetKey, elements, offset) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterSelect(targetKey, elements, offset) {
|
||||||
|
if (typeof config.afterSelect === "function") {
|
||||||
|
config.afterSelect(targetKey, elements, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldHandleKeyEvent(event, elements) {
|
||||||
|
if (!isActive(elements)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOpenModalDialog()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isEditableKeyTarget(event.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sync(elements = getElements()) {
|
||||||
|
const sequence = getSequenceState();
|
||||||
|
const previousKey = getTargetKey(sequence, -1);
|
||||||
|
const nextKey = getTargetKey(sequence, 1);
|
||||||
|
const prevButton = getPrevButton(elements);
|
||||||
|
const nextButton = getNextButton(elements);
|
||||||
|
const positionEl = getPositionEl(elements);
|
||||||
|
|
||||||
|
if (prevButton) {
|
||||||
|
prevButton.disabled = !previousKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextButton) {
|
||||||
|
nextButton.disabled = !nextKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positionEl) {
|
||||||
|
positionEl.textContent = formatPositionText(sequence, elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function step(offset, elements = getElements()) {
|
||||||
|
const sequence = getSequenceState();
|
||||||
|
const targetKey = getTargetKey(sequence, offset);
|
||||||
|
if (!targetKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const didSelect = selectTarget(targetKey, elements, offset);
|
||||||
|
if (didSelect) {
|
||||||
|
afterSelect(targetKey, elements, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return didSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(elements = getElements()) {
|
||||||
|
if (!buttonsBound) {
|
||||||
|
getPrevButton(elements)?.addEventListener("click", () => {
|
||||||
|
step(-1, getElements());
|
||||||
|
});
|
||||||
|
|
||||||
|
getNextButton(elements)?.addEventListener("click", () => {
|
||||||
|
step(1, getElements());
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonsBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keyboardBound) {
|
||||||
|
document.addEventListener("keydown", (event) => {
|
||||||
|
const latestElements = getElements();
|
||||||
|
if (!shouldHandleKeyEvent(event, latestElements)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = event.key === "ArrowRight" ? 1 : -1;
|
||||||
|
const sequence = getSequenceState();
|
||||||
|
if (!getTargetKey(sequence, offset)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
step(offset, latestElements);
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboardBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sync(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bind,
|
||||||
|
step,
|
||||||
|
sync,
|
||||||
|
getSequenceState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.TarotSequenceNav = {
|
||||||
|
...(window.TarotSequenceNav || {}),
|
||||||
|
createSequenceNavigator
|
||||||
|
};
|
||||||
|
})();
|
||||||
+31
-2
@@ -209,12 +209,17 @@
|
|||||||
const activeDeckId = String(status?.activeDeckId || normalizeTarotDeck(getElements().tarotDeckEl?.value)).trim().toLowerCase();
|
const activeDeckId = String(status?.activeDeckId || normalizeTarotDeck(getElements().tarotDeckEl?.value)).trim().toLowerCase();
|
||||||
const loadedCount = Math.max(0, Number(status?.selectedDeckLoadedCount) || 0);
|
const loadedCount = Math.max(0, Number(status?.selectedDeckLoadedCount) || 0);
|
||||||
const totalCount = Math.max(0, Number(status?.selectedDeckTotalCount) || 0);
|
const totalCount = Math.max(0, Number(status?.selectedDeckTotalCount) || 0);
|
||||||
|
const warmedDeckCount = Math.max(0, Number(status?.warmedDeckCount) || 0);
|
||||||
|
const totalDeckCount = Math.max(0, Number(status?.totalDeckCount) || 0);
|
||||||
|
const backgroundProgress = totalDeckCount > 1
|
||||||
|
? ` (${Math.min(warmedDeckCount, totalDeckCount)}/${totalDeckCount} decks warmed)`
|
||||||
|
: "";
|
||||||
|
|
||||||
if (status?.selectedDeckPhase === "loading") {
|
if (status?.selectedDeckPhase === "loading") {
|
||||||
if (totalCount > 0) {
|
if (totalCount > 0) {
|
||||||
return `Caching selected deck images to this browser... (${loadedCount}/${totalCount})`;
|
return `Caching selected deck images to this browser... (${loadedCount}/${totalCount})${backgroundProgress}`;
|
||||||
}
|
}
|
||||||
return "Caching selected deck images to this browser...";
|
return `Caching selected deck images to this browser...${backgroundProgress}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status?.selectedDeckPhase === "error") {
|
if (status?.selectedDeckPhase === "error") {
|
||||||
@@ -222,9 +227,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status?.selectedDeckPhase === "ready") {
|
if (status?.selectedDeckPhase === "ready") {
|
||||||
|
if (totalDeckCount > 1 && warmedDeckCount < totalDeckCount) {
|
||||||
|
return `Selected deck cached and ready for fullscreen use (${activeDeckId}). Warming the rest of the decks in background${backgroundProgress}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalDeckCount > 1) {
|
||||||
|
return `All connected deck images cached and ready (${totalDeckCount}/${totalDeckCount} decks warmed).`;
|
||||||
|
}
|
||||||
|
|
||||||
return `Selected deck cached and ready for fullscreen use (${activeDeckId}).`;
|
return `Selected deck cached and ready for fullscreen use (${activeDeckId}).`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (totalDeckCount > 1 && warmedDeckCount > 0) {
|
||||||
|
return `Deck cache idle. Background warmup has ${Math.min(warmedDeckCount, totalDeckCount)}/${totalDeckCount} decks ready.`;
|
||||||
|
}
|
||||||
|
|
||||||
return "Deck cache idle.";
|
return "Deck cache idle.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,6 +587,18 @@
|
|||||||
const previousConnectionSettings = getConnectionSettings();
|
const previousConnectionSettings = getConnectionSettings();
|
||||||
const connectionSettings = getConnectionSettingsFromInputs();
|
const connectionSettings = getConnectionSettingsFromInputs();
|
||||||
const connectionChanged = hasConnectionChanged(previousConnectionSettings, connectionSettings);
|
const connectionChanged = hasConnectionChanged(previousConnectionSettings, connectionSettings);
|
||||||
|
|
||||||
|
if (connectionChanged) {
|
||||||
|
setSettingsPageStatus("Validating API connection...", "info", {
|
||||||
|
savedAt: loadLastSavedAt()
|
||||||
|
});
|
||||||
|
|
||||||
|
const probeResult = await window.TarotDataService?.probeConnection?.(connectionSettings);
|
||||||
|
if (!probeResult?.ok) {
|
||||||
|
throw new Error(probeResult?.message || "Unable to validate the API connection.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const connectionResult = window.TarotAppConfig?.updateConnectionSettings?.(connectionSettings) || { didPersist: true };
|
const connectionResult = window.TarotAppConfig?.updateConnectionSettings?.(connectionSettings) || { didPersist: true };
|
||||||
const normalized = applySettingsToInputs(settings);
|
const normalized = applySettingsToInputs(settings);
|
||||||
syncSky(
|
syncSky(
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
getMagickDataset,
|
getMagickDataset,
|
||||||
resolveTarotCardImage,
|
resolveTarotCardImage,
|
||||||
resolveTarotCardThumbnail,
|
resolveTarotCardThumbnail,
|
||||||
|
getDeckVariantsForCard,
|
||||||
|
openDeckVariantLightbox,
|
||||||
getDisplayCardName,
|
getDisplayCardName,
|
||||||
buildTypeLabel,
|
buildTypeLabel,
|
||||||
clearChildren,
|
clearChildren,
|
||||||
@@ -407,6 +409,73 @@
|
|||||||
.filter((group) => group.items.length);
|
.filter((group) => group.items.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderDeckVariants(card, elements, cardDisplayName) {
|
||||||
|
const galleryCardEl = elements?.tarotMetaDeckGalleryCardEl;
|
||||||
|
const galleryEl = elements?.tarotDetailDeckGalleryEl;
|
||||||
|
if (!galleryCardEl || !galleryEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearChildren(galleryEl);
|
||||||
|
const variants = typeof getDeckVariantsForCard === "function"
|
||||||
|
? getDeckVariantsForCard(card)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!Array.isArray(variants) || variants.length < 1) {
|
||||||
|
galleryCardEl.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
variants.forEach((variant) => {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = `tarot-deck-variant${variant?.isActive ? " is-active" : ""}`;
|
||||||
|
button.dataset.deckId = String(variant?.deckId || "").trim().toLowerCase();
|
||||||
|
|
||||||
|
const imageEl = document.createElement("img");
|
||||||
|
imageEl.className = "tarot-deck-variant-image";
|
||||||
|
imageEl.src = String(variant?.src || "").trim();
|
||||||
|
imageEl.alt = `${String(variant?.label || "Deck").trim()} — ${cardDisplayName || card?.name || "Tarot card"}`;
|
||||||
|
imageEl.loading = "lazy";
|
||||||
|
imageEl.decoding = "async";
|
||||||
|
|
||||||
|
const labelEl = document.createElement("span");
|
||||||
|
labelEl.className = "tarot-deck-variant-label";
|
||||||
|
|
||||||
|
const deckNameEl = document.createElement("span");
|
||||||
|
deckNameEl.className = "tarot-deck-variant-deck";
|
||||||
|
deckNameEl.textContent = String(variant?.label || variant?.deckId || "Deck").trim() || "Deck";
|
||||||
|
labelEl.appendChild(deckNameEl);
|
||||||
|
|
||||||
|
const variantName = String(variant?.displayName || "").trim();
|
||||||
|
if (variantName && variantName !== (cardDisplayName || card?.name || "")) {
|
||||||
|
const variantNameEl = document.createElement("span");
|
||||||
|
variantNameEl.className = "tarot-deck-variant-name";
|
||||||
|
variantNameEl.textContent = variantName;
|
||||||
|
labelEl.appendChild(variantNameEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant?.isActive) {
|
||||||
|
const activeEl = document.createElement("span");
|
||||||
|
activeEl.className = "tarot-deck-variant-active";
|
||||||
|
activeEl.textContent = "Current deck";
|
||||||
|
labelEl.appendChild(activeEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.append(imageEl, labelEl);
|
||||||
|
|
||||||
|
if (typeof openDeckVariantLightbox === "function" && button.dataset.deckId) {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
openDeckVariantLightbox(card?.id, button.dataset.deckId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
galleryEl.appendChild(button);
|
||||||
|
});
|
||||||
|
|
||||||
|
galleryCardEl.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
function renderDetail(card, elements) {
|
function renderDetail(card, elements) {
|
||||||
if (!card || !elements) {
|
if (!card || !elements) {
|
||||||
return;
|
return;
|
||||||
@@ -454,6 +523,8 @@
|
|||||||
elements.tarotDetailReversedEl.textContent = card.meanings?.reversed || "--";
|
elements.tarotDetailReversedEl.textContent = card.meanings?.reversed || "--";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderDeckVariants(card, elements, cardDisplayName || card.name);
|
||||||
|
|
||||||
const meaningText = String(card.meaning || card.meanings?.upright || "").trim();
|
const meaningText = String(card.meaning || card.meanings?.upright || "").trim();
|
||||||
if (elements.tarotMetaMeaningCardEl && elements.tarotDetailMeaningEl) {
|
if (elements.tarotMetaMeaningCardEl && elements.tarotDetailMeaningEl) {
|
||||||
if (meaningText) {
|
if (meaningText) {
|
||||||
|
|||||||
+20
-12
@@ -837,12 +837,14 @@
|
|||||||
function normalizeCardRequest(request) {
|
function normalizeCardRequest(request) {
|
||||||
const normalized = normalizeOpenRequest(request);
|
const normalized = normalizeOpenRequest(request);
|
||||||
const label = String(normalized.label || normalized.altText || "Tarot card enlarged image").trim() || "Tarot card enlarged image";
|
const label = String(normalized.label || normalized.altText || "Tarot card enlarged image").trim() || "Tarot card enlarged image";
|
||||||
|
const cardId = String(normalized.cardId || "").trim();
|
||||||
return {
|
return {
|
||||||
src: String(normalized.src || "").trim(),
|
src: String(normalized.src || "").trim(),
|
||||||
previewSrc: String(normalized.previewSrc || "").trim(),
|
previewSrc: String(normalized.previewSrc || "").trim(),
|
||||||
altText: String(normalized.altText || label).trim() || label,
|
altText: String(normalized.altText || label).trim() || label,
|
||||||
label,
|
label,
|
||||||
cardId: String(normalized.cardId || "").trim(),
|
cardId,
|
||||||
|
sequenceId: String(normalized.sequenceId || cardId).trim(),
|
||||||
deckId: String(normalized.deckId || "").trim(),
|
deckId: String(normalized.deckId || "").trim(),
|
||||||
deckLabel: String(normalized.deckLabel || normalized.deckId || "").trim(),
|
deckLabel: String(normalized.deckLabel || normalized.deckId || "").trim(),
|
||||||
missingReason: String(normalized.missingReason || "").trim(),
|
missingReason: String(normalized.missingReason || "").trim(),
|
||||||
@@ -995,19 +997,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveCardRequestById(cardId) {
|
function resolveCardRequestById(sequenceId) {
|
||||||
if (!cardId || typeof lightboxState.resolveCardById !== "function") {
|
if (!sequenceId || typeof lightboxState.resolveCardById !== "function") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = lightboxState.resolveCardById(cardId);
|
const resolved = lightboxState.resolveCardById(sequenceId);
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalizeCardRequest({
|
return normalizeCardRequest({
|
||||||
...resolved,
|
...resolved,
|
||||||
cardId
|
sequenceId: String(resolved.sequenceId || sequenceId).trim() || String(sequenceId),
|
||||||
|
cardId: String(resolved.cardId || sequenceId).trim()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3277,7 +3280,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchorId = lightboxState.secondaryCard?.cardId || lightboxState.primaryCard?.cardId;
|
const anchorId = lightboxState.secondaryCard?.sequenceId
|
||||||
|
|| lightboxState.secondaryCard?.cardId
|
||||||
|
|| lightboxState.primaryCard?.sequenceId
|
||||||
|
|| lightboxState.primaryCard?.cardId;
|
||||||
const startIndex = sequence.indexOf(anchorId);
|
const startIndex = sequence.indexOf(anchorId);
|
||||||
if (startIndex < 0) {
|
if (startIndex < 0) {
|
||||||
return;
|
return;
|
||||||
@@ -3285,12 +3291,13 @@
|
|||||||
|
|
||||||
for (let offset = 1; offset <= sequence.length; offset += 1) {
|
for (let offset = 1; offset <= sequence.length; offset += 1) {
|
||||||
const nextIndex = (startIndex + direction * offset + sequence.length) % sequence.length;
|
const nextIndex = (startIndex + direction * offset + sequence.length) % sequence.length;
|
||||||
const nextCardId = sequence[nextIndex];
|
const nextSequenceId = sequence[nextIndex];
|
||||||
if (!nextCardId || nextCardId === lightboxState.primaryCard?.cardId) {
|
const primarySequenceId = lightboxState.primaryCard?.sequenceId || lightboxState.primaryCard?.cardId;
|
||||||
|
if (!nextSequenceId || nextSequenceId === primarySequenceId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextCard = resolveCardRequestById(nextCardId);
|
const nextCard = resolveCardRequestById(nextSequenceId);
|
||||||
if (nextCard && setSecondaryCard(nextCard, true)) {
|
if (nextCard && setSecondaryCard(nextCard, true)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3303,14 +3310,15 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const startIndex = sequence.indexOf(lightboxState.primaryCard?.cardId);
|
const primarySequenceId = lightboxState.primaryCard?.sequenceId || lightboxState.primaryCard?.cardId;
|
||||||
|
const startIndex = sequence.indexOf(primarySequenceId);
|
||||||
if (startIndex < 0) {
|
if (startIndex < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextIndex = (startIndex + direction + sequence.length) % sequence.length;
|
const nextIndex = (startIndex + direction + sequence.length) % sequence.length;
|
||||||
const nextCardId = sequence[nextIndex];
|
const nextSequenceId = sequence[nextIndex];
|
||||||
const nextCard = resolveCardRequestById(nextCardId);
|
const nextCard = resolveCardRequestById(nextSequenceId);
|
||||||
if (!nextCard?.src) {
|
if (!nextCard?.src) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+227
-12
@@ -47,6 +47,7 @@
|
|||||||
courtCardByDecanId: new Map(),
|
courtCardByDecanId: new Map(),
|
||||||
loadingPromise: null
|
loadingPromise: null
|
||||||
};
|
};
|
||||||
|
let detailNavigator = null;
|
||||||
|
|
||||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||||
"the fool": 0,
|
"the fool": 0,
|
||||||
@@ -255,9 +256,14 @@
|
|||||||
tarotDetailImageEl: document.getElementById("tarot-detail-image"),
|
tarotDetailImageEl: document.getElementById("tarot-detail-image"),
|
||||||
tarotDetailNameEl: document.getElementById("tarot-detail-name"),
|
tarotDetailNameEl: document.getElementById("tarot-detail-name"),
|
||||||
tarotDetailTypeEl: document.getElementById("tarot-detail-type"),
|
tarotDetailTypeEl: document.getElementById("tarot-detail-type"),
|
||||||
|
tarotDetailPrevEl: document.getElementById("tarot-detail-prev"),
|
||||||
|
tarotDetailPositionEl: document.getElementById("tarot-detail-position"),
|
||||||
|
tarotDetailNextEl: document.getElementById("tarot-detail-next"),
|
||||||
tarotDetailSummaryEl: document.getElementById("tarot-detail-summary"),
|
tarotDetailSummaryEl: document.getElementById("tarot-detail-summary"),
|
||||||
tarotDetailUprightEl: document.getElementById("tarot-detail-upright"),
|
tarotDetailUprightEl: document.getElementById("tarot-detail-upright"),
|
||||||
tarotDetailReversedEl: document.getElementById("tarot-detail-reversed"),
|
tarotDetailReversedEl: document.getElementById("tarot-detail-reversed"),
|
||||||
|
tarotMetaDeckGalleryCardEl: document.getElementById("tarot-meta-deck-gallery-card"),
|
||||||
|
tarotDetailDeckGalleryEl: document.getElementById("tarot-detail-deck-gallery"),
|
||||||
tarotMetaMeaningCardEl: document.getElementById("tarot-meta-meaning-card"),
|
tarotMetaMeaningCardEl: document.getElementById("tarot-meta-meaning-card"),
|
||||||
tarotDetailMeaningEl: document.getElementById("tarot-detail-meaning"),
|
tarotDetailMeaningEl: document.getElementById("tarot-detail-meaning"),
|
||||||
tarotDetailKeywordsEl: document.getElementById("tarot-detail-keywords"),
|
tarotDetailKeywordsEl: document.getElementById("tarot-detail-keywords"),
|
||||||
@@ -355,6 +361,8 @@
|
|||||||
getMagickDataset: () => state.magickDataset,
|
getMagickDataset: () => state.magickDataset,
|
||||||
resolveTarotCardImage,
|
resolveTarotCardImage,
|
||||||
resolveTarotCardThumbnail,
|
resolveTarotCardThumbnail,
|
||||||
|
getDeckVariantsForCard,
|
||||||
|
openDeckVariantLightbox,
|
||||||
getDisplayCardName,
|
getDisplayCardName,
|
||||||
buildTypeLabel,
|
buildTypeLabel,
|
||||||
clearChildren,
|
clearChildren,
|
||||||
@@ -523,11 +531,14 @@
|
|||||||
if (!state.filteredCards.some((card) => card.id === state.selectedCardId)) {
|
if (!state.filteredCards.some((card) => card.id === state.selectedCardId)) {
|
||||||
if (state.filteredCards.length > 0) {
|
if (state.filteredCards.length > 0) {
|
||||||
selectCardById(state.filteredCards[0].id, elements);
|
selectCardById(state.filteredCards[0].id, elements);
|
||||||
|
} else {
|
||||||
|
syncDetailNavigation(elements);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateListSelection(elements);
|
updateListSelection(elements);
|
||||||
|
syncDetailNavigation(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearChildren(element) {
|
function clearChildren(element) {
|
||||||
@@ -723,6 +734,62 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCardSequenceState() {
|
||||||
|
const total = state.filteredCards.length;
|
||||||
|
const currentIndex = state.filteredCards.findIndex((card) => card.id === state.selectedCardId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
currentIndex,
|
||||||
|
previousId: currentIndex > 0 ? state.filteredCards[currentIndex - 1].id : "",
|
||||||
|
nextId: currentIndex >= 0 && currentIndex < total - 1 ? state.filteredCards[currentIndex + 1].id : ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetailNavigator() {
|
||||||
|
if (detailNavigator || typeof window.TarotSequenceNav?.createSequenceNavigator !== "function") {
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
detailNavigator = window.TarotSequenceNav.createSequenceNavigator({
|
||||||
|
getElements,
|
||||||
|
isActive: (elements) => Boolean(elements?.tarotSectionEl && elements.tarotSectionEl.hidden === false),
|
||||||
|
getSequenceState: getCardSequenceState,
|
||||||
|
getPrevButton: (elements) => elements?.tarotDetailPrevEl,
|
||||||
|
getNextButton: (elements) => elements?.tarotDetailNextEl,
|
||||||
|
getPositionEl: (elements) => elements?.tarotDetailPositionEl,
|
||||||
|
formatPositionText: ({ total, currentIndex }) => {
|
||||||
|
if (total > 0 && currentIndex >= 0) {
|
||||||
|
const suffix = state.searchQuery ? " shown" : "";
|
||||||
|
return `${currentIndex + 1} of ${total}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total > 0 ? `${total} cards` : "No cards";
|
||||||
|
},
|
||||||
|
selectTarget: (targetId, elements) => {
|
||||||
|
selectCardById(targetId, elements);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
afterSelect: (targetId, elements) => {
|
||||||
|
scrollCardIntoView(targetId, elements);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return detailNavigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncDetailNavigation(elements) {
|
||||||
|
getDetailNavigator()?.sync(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAdjacentCard(offset, elements = getElements()) {
|
||||||
|
return getDetailNavigator()?.step(offset, elements) === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindKeyboardNavigation(elements) {
|
||||||
|
getDetailNavigator()?.bind(elements);
|
||||||
|
}
|
||||||
|
|
||||||
function selectCardById(cardIdToSelect, elements) {
|
function selectCardById(cardIdToSelect, elements) {
|
||||||
const card = state.cards.find((entry) => entry.id === cardIdToSelect);
|
const card = state.cards.find((entry) => entry.id === cardIdToSelect);
|
||||||
if (!card) {
|
if (!card) {
|
||||||
@@ -733,6 +800,7 @@
|
|||||||
updateListSelection(elements);
|
updateListSelection(elements);
|
||||||
updateHouseSelection(elements);
|
updateHouseSelection(elements);
|
||||||
renderDetail(card, elements);
|
renderDetail(card, elements);
|
||||||
|
syncDetailNavigation(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollCardIntoView(cardIdToReveal, elements) {
|
function scrollCardIntoView(cardIdToReveal, elements) {
|
||||||
@@ -758,6 +826,47 @@
|
|||||||
return Array.from(getRegisteredDeckOptionMap().values());
|
return Array.from(getRegisteredDeckOptionMap().values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDeckVariantsForCard(card) {
|
||||||
|
if (!card) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const trumpNumber = Number.isFinite(Number(card?.number)) ? Number(card.number) : undefined;
|
||||||
|
const activeDeckId = String(getActiveDeck?.() || "").trim().toLowerCase();
|
||||||
|
|
||||||
|
return getRegisteredDeckList()
|
||||||
|
.map((deck) => {
|
||||||
|
const deckId = String(deck?.id || "").trim().toLowerCase();
|
||||||
|
if (!deckId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageOptions = { deckId, trumpNumber };
|
||||||
|
const src = (typeof resolveTarotCardThumbnail === "function"
|
||||||
|
? resolveTarotCardThumbnail(card.name, imageOptions)
|
||||||
|
: "") || (typeof resolveTarotCardImage === "function"
|
||||||
|
? resolveTarotCardImage(card.name, imageOptions)
|
||||||
|
: "");
|
||||||
|
|
||||||
|
if (!src) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayName = (typeof getTarotCardDisplayName === "function"
|
||||||
|
? String(getTarotCardDisplayName(card.name, imageOptions) || "").trim()
|
||||||
|
: "") || getDisplayCardName(card) || card.name;
|
||||||
|
|
||||||
|
return {
|
||||||
|
deckId,
|
||||||
|
label: String(deck?.label || deckId).trim() || deckId,
|
||||||
|
src,
|
||||||
|
displayName,
|
||||||
|
isActive: deckId === activeDeckId
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
function buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve = "") {
|
function buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve = "") {
|
||||||
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
||||||
if (!card) {
|
if (!card) {
|
||||||
@@ -790,8 +899,8 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLightboxCardRequestById(cardIdToResolve) {
|
function buildLightboxCardRequestById(cardIdToResolve, deckIdToResolve = "") {
|
||||||
const request = buildDeckLightboxCardRequest(cardIdToResolve, getActiveDeck?.() || "");
|
const request = buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve || getActiveDeck?.() || "");
|
||||||
if (!request?.src) {
|
if (!request?.src) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -799,19 +908,67 @@
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDefaultLightboxSequenceIds(cardIdToOpen = "") {
|
||||||
|
const normalizedCardId = String(cardIdToOpen || "").trim();
|
||||||
|
const filteredIds = state.filteredCards
|
||||||
|
.map((card) => String(card?.id || "").trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (normalizedCardId && filteredIds.includes(normalizedCardId)) {
|
||||||
|
return filteredIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.cards
|
||||||
|
.map((card) => String(card?.id || "").trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeckVariantSequenceEntries(cardIdToResolve) {
|
||||||
|
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
||||||
|
if (!card) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDeckVariantsForCard(card)
|
||||||
|
.map((variant) => {
|
||||||
|
const deckId = String(variant?.deckId || "").trim().toLowerCase();
|
||||||
|
if (!deckId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sequenceId: deckId,
|
||||||
|
deckId
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
function openCardLightboxById(cardIdToOpen, options = {}) {
|
function openCardLightboxById(cardIdToOpen, options = {}) {
|
||||||
const normalizedCardId = String(cardIdToOpen || "").trim();
|
const normalizedCardId = String(cardIdToOpen || "").trim();
|
||||||
if (!normalizedCardId) {
|
if (!normalizedCardId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId);
|
const requestedDeckId = String(options?.deckId || getActiveDeck?.() || "").trim();
|
||||||
|
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId, requestedDeckId);
|
||||||
if (!primaryCardRequest?.src) {
|
if (!primaryCardRequest?.src) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeDeckId = String(getActiveDeck?.() || primaryCardRequest.deckId || "").trim();
|
const activeDeckId = String(primaryCardRequest.deckId || requestedDeckId || getActiveDeck?.() || "").trim();
|
||||||
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeDeckId);
|
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeDeckId);
|
||||||
|
const requestedSequenceIds = Array.isArray(options?.sequenceIds)
|
||||||
|
? options.sequenceIds
|
||||||
|
.map((sequenceId) => String(sequenceId || "").trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
const sequenceIds = requestedSequenceIds.length > 0
|
||||||
|
? requestedSequenceIds
|
||||||
|
: getDefaultLightboxSequenceIds(normalizedCardId);
|
||||||
|
const resolveCardById = typeof options?.resolveCardById === "function"
|
||||||
|
? options.resolveCardById
|
||||||
|
: (nextCardId) => buildLightboxCardRequestById(nextCardId, activeDeckId);
|
||||||
const onSelectCardId = typeof options?.onSelectCardId === "function"
|
const onSelectCardId = typeof options?.onSelectCardId === "function"
|
||||||
? options.onSelectCardId
|
? options.onSelectCardId
|
||||||
: (nextCardId) => {
|
: (nextCardId) => {
|
||||||
@@ -825,6 +982,7 @@
|
|||||||
altText: primaryCardRequest.altText,
|
altText: primaryCardRequest.altText,
|
||||||
label: primaryCardRequest.label,
|
label: primaryCardRequest.label,
|
||||||
cardId: primaryCardRequest.cardId,
|
cardId: primaryCardRequest.cardId,
|
||||||
|
sequenceId: String(options?.sequenceId || primaryCardRequest.sequenceId || normalizedCardId).trim(),
|
||||||
deckId: primaryCardRequest.deckId || activeDeckId,
|
deckId: primaryCardRequest.deckId || activeDeckId,
|
||||||
deckLabel: primaryCardRequest.deckLabel || "",
|
deckLabel: primaryCardRequest.deckLabel || "",
|
||||||
compareDetails: primaryCardRequest.compareDetails || [],
|
compareDetails: primaryCardRequest.compareDetails || [],
|
||||||
@@ -834,13 +992,72 @@
|
|||||||
activeDeckLabel: primaryCardRequest.deckLabel || "",
|
activeDeckLabel: primaryCardRequest.deckLabel || "",
|
||||||
availableCompareDecks,
|
availableCompareDecks,
|
||||||
maxCompareDecks: 2,
|
maxCompareDecks: 2,
|
||||||
sequenceIds: state.cards.map((card) => card.id),
|
sequenceIds,
|
||||||
resolveCardById: buildLightboxCardRequestById,
|
resolveCardById,
|
||||||
resolveDeckCardById: buildDeckLightboxCardRequest,
|
resolveDeckCardById: buildDeckLightboxCardRequest,
|
||||||
onSelectCardId
|
onSelectCardId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openDeckVariantLightbox(cardIdToOpen, deckIdToOpen = "") {
|
||||||
|
const normalizedCardId = String(cardIdToOpen || "").trim();
|
||||||
|
if (!normalizedCardId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantEntries = getDeckVariantSequenceEntries(normalizedCardId);
|
||||||
|
if (variantEntries.length < 1) {
|
||||||
|
openCardLightboxById(normalizedCardId, { deckId: deckIdToOpen });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestedDeckId = String(deckIdToOpen || getActiveDeck?.() || "").trim().toLowerCase();
|
||||||
|
const activeVariant = variantEntries.find((variant) => variant.deckId === requestedDeckId) || variantEntries[0];
|
||||||
|
if (!activeVariant?.deckId) {
|
||||||
|
openCardLightboxById(normalizedCardId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId, activeVariant.deckId);
|
||||||
|
if (!primaryCardRequest?.src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableCompareDecks = getRegisteredDeckList().filter((deck) => deck.id && deck.id !== activeVariant.deckId);
|
||||||
|
|
||||||
|
window.TarotUiLightbox?.open?.({
|
||||||
|
...primaryCardRequest,
|
||||||
|
deckId: activeVariant.deckId,
|
||||||
|
sequenceId: activeVariant.sequenceId,
|
||||||
|
activeDeckId: activeVariant.deckId,
|
||||||
|
activeDeckLabel: primaryCardRequest.deckLabel || "",
|
||||||
|
availableCompareDecks,
|
||||||
|
maxCompareDecks: 2,
|
||||||
|
allowOverlayCompare: true,
|
||||||
|
allowDeckCompare: true,
|
||||||
|
sequenceIds: variantEntries.map((variant) => variant.sequenceId),
|
||||||
|
resolveDeckCardById: buildDeckLightboxCardRequest,
|
||||||
|
resolveCardById: (sequenceId) => {
|
||||||
|
const normalizedSequenceId = String(sequenceId || "").trim().toLowerCase();
|
||||||
|
const matchingVariant = variantEntries.find((variant) => variant.sequenceId === normalizedSequenceId);
|
||||||
|
if (!matchingVariant?.deckId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = buildLightboxCardRequestById(normalizedCardId, matchingVariant.deckId);
|
||||||
|
return request
|
||||||
|
? {
|
||||||
|
...request,
|
||||||
|
sequenceId: matchingVariant.sequenceId,
|
||||||
|
deckId: matchingVariant.deckId
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
},
|
||||||
|
onSelectCardId: () => {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderList(elements) {
|
function renderList(elements) {
|
||||||
if (!elements?.tarotCardListEl) {
|
if (!elements?.tarotCardListEl) {
|
||||||
return;
|
return;
|
||||||
@@ -940,6 +1157,7 @@
|
|||||||
const selected = state.cards.find((card) => card.id === state.selectedCardId);
|
const selected = state.cards.find((card) => card.id === state.selectedCardId);
|
||||||
if (selected) {
|
if (selected) {
|
||||||
renderDetail(selected, elements);
|
renderDetail(selected, elements);
|
||||||
|
syncDetailNavigation(elements);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1007,6 +1225,8 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindKeyboardNavigation(elements);
|
||||||
|
|
||||||
if (elements.tarotHouseTopCardsVisibleEl) {
|
if (elements.tarotHouseTopCardsVisibleEl) {
|
||||||
elements.tarotHouseTopCardsVisibleEl.addEventListener("change", () => {
|
elements.tarotHouseTopCardsVisibleEl.addEventListener("change", () => {
|
||||||
state.houseTopCardsVisible = Boolean(elements.tarotHouseTopCardsVisibleEl.checked);
|
state.houseTopCardsVisible = Boolean(elements.tarotHouseTopCardsVisibleEl.checked);
|
||||||
@@ -1104,12 +1324,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = buildLightboxCardRequestById(state.selectedCardId);
|
openCardLightboxById(state.selectedCardId);
|
||||||
if (!request?.src) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.TarotUiLightbox?.open?.(request);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+132
-19
@@ -16,7 +16,7 @@
|
|||||||
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css">
|
<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/amiri/arabic-700.css">
|
||||||
<link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css">
|
<link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css">
|
||||||
<link rel="stylesheet" href="app/styles.css?v=20260424-detail-inline-links-02">
|
<link rel="stylesheet" href="app/styles.css?v=20260528-kabbalah-cross-split-06">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
@@ -63,8 +63,12 @@
|
|||||||
<div class="topbar-dropdown" aria-label="Kabbalah menu">
|
<div class="topbar-dropdown" aria-label="Kabbalah menu">
|
||||||
<button id="open-kabbalah" class="settings-trigger" type="button" aria-pressed="false" aria-haspopup="menu" aria-controls="kabbalah-subpages" aria-expanded="false">Kabbalah ▾</button>
|
<button id="open-kabbalah" class="settings-trigger" type="button" aria-pressed="false" aria-haspopup="menu" aria-controls="kabbalah-subpages" aria-expanded="false">Kabbalah ▾</button>
|
||||||
<div id="kabbalah-subpages" class="topbar-dropdown-menu" role="menu" aria-label="Kabbalah subpages">
|
<div id="kabbalah-subpages" class="topbar-dropdown-menu" role="menu" aria-label="Kabbalah subpages">
|
||||||
<button id="open-kabbalah-cube" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Cube</button>
|
<button id="open-kabbalah-sephirot" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Sefirot</button>
|
||||||
|
<button id="open-kabbalah-worlds" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Qabalistic Worlds</button>
|
||||||
|
<button id="open-kabbalah-paths" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Paths</button>
|
||||||
|
<button id="open-kabbalah-cross" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Rosicrucian Cross</button>
|
||||||
<button id="open-kabbalah-tree" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Tree</button>
|
<button id="open-kabbalah-tree" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Tree</button>
|
||||||
|
<button id="open-kabbalah-cube" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Cube</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="open-numbers" class="settings-trigger" type="button" aria-pressed="false">Numbers</button>
|
<button id="open-numbers" class="settings-trigger" type="button" aria-pressed="false">Numbers</button>
|
||||||
@@ -216,6 +220,11 @@
|
|||||||
<div class="planet-detail-heading">
|
<div class="planet-detail-heading">
|
||||||
<h2 id="calendar-detail-name">--</h2>
|
<h2 id="calendar-detail-name">--</h2>
|
||||||
<div id="calendar-detail-sub" class="planet-detail-type">Select a month to explore</div>
|
<div id="calendar-detail-sub" class="planet-detail-type">Select a month to explore</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse months">
|
||||||
|
<button id="calendar-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous month">Back</button>
|
||||||
|
<div id="calendar-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="calendar-detail-next" class="detail-sequence-btn" type="button" aria-label="Next month">Next</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="calendar-detail-body"></div>
|
<div id="calendar-detail-body"></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -252,6 +261,11 @@
|
|||||||
<div class="planet-detail-heading">
|
<div class="planet-detail-heading">
|
||||||
<h2 id="holiday-detail-name">--</h2>
|
<h2 id="holiday-detail-name">--</h2>
|
||||||
<div id="holiday-detail-sub" class="planet-detail-type">Select a holiday to explore</div>
|
<div id="holiday-detail-sub" class="planet-detail-type">Select a holiday to explore</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse holidays">
|
||||||
|
<button id="holiday-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous holiday">Back</button>
|
||||||
|
<div id="holiday-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="holiday-detail-next" class="detail-sequence-btn" type="button" aria-label="Next holiday">Next</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="holiday-detail-body"></div>
|
<div id="holiday-detail-body"></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -273,10 +287,14 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<section class="tarot-detail-panel" aria-live="polite">
|
<section class="tarot-detail-panel" aria-live="polite">
|
||||||
<div class="tarot-detail-top">
|
<div class="tarot-detail-top">
|
||||||
<img id="tarot-detail-image" class="tarot-detail-image" alt="Tarot card image" />
|
|
||||||
<div class="tarot-detail-heading">
|
<div class="tarot-detail-heading">
|
||||||
<h2 id="tarot-detail-name">--</h2>
|
<h2 id="tarot-detail-name">--</h2>
|
||||||
<div id="tarot-detail-type" class="tarot-detail-type">--</div>
|
<div id="tarot-detail-type" class="tarot-detail-type">--</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse tarot cards">
|
||||||
|
<button id="tarot-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous tarot card">Back</button>
|
||||||
|
<div id="tarot-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="tarot-detail-next" class="detail-sequence-btn" type="button" aria-label="Next tarot card">Next</button>
|
||||||
|
</div>
|
||||||
<div id="tarot-detail-summary" class="tarot-detail-summary">--</div>
|
<div id="tarot-detail-summary" class="tarot-detail-summary">--</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,6 +309,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tarot-meta-grid">
|
<div class="tarot-meta-grid">
|
||||||
|
<div id="tarot-meta-deck-gallery-card" class="tarot-meta-card tarot-deck-gallery-card" hidden>
|
||||||
|
<strong>Deck Variants</strong>
|
||||||
|
<div id="tarot-detail-deck-gallery" class="tarot-deck-gallery"></div>
|
||||||
|
</div>
|
||||||
<div id="tarot-meta-meaning-card" class="tarot-meta-card" hidden>
|
<div id="tarot-meta-meaning-card" class="tarot-meta-card" hidden>
|
||||||
<strong>Traditional Meaning</strong>
|
<strong>Traditional Meaning</strong>
|
||||||
<div id="tarot-detail-meaning" class="planet-text">--</div>
|
<div id="tarot-detail-meaning" class="planet-text">--</div>
|
||||||
@@ -539,6 +561,11 @@
|
|||||||
<div class="planet-detail-heading">
|
<div class="planet-detail-heading">
|
||||||
<h2 id="planet-detail-name">--</h2>
|
<h2 id="planet-detail-name">--</h2>
|
||||||
<div id="planet-detail-type" class="planet-detail-type">--</div>
|
<div id="planet-detail-type" class="planet-detail-type">--</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse planets">
|
||||||
|
<button id="planet-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous planet">Back</button>
|
||||||
|
<div id="planet-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="planet-detail-next" class="detail-sequence-btn" type="button" aria-label="Next planet">Next</button>
|
||||||
|
</div>
|
||||||
<div id="planet-detail-summary" class="planet-detail-summary">--</div>
|
<div id="planet-detail-summary" class="planet-detail-summary">--</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="planet-meta-grid">
|
<div class="planet-meta-grid">
|
||||||
@@ -774,19 +801,99 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="kabbalah-section" hidden>
|
<section id="kabbalah-section" hidden>
|
||||||
|
<div class="planet-layout">
|
||||||
|
<aside class="planet-list-panel">
|
||||||
|
<div class="planet-list-header">
|
||||||
|
<strong>Sefirot</strong>
|
||||||
|
<span id="kab-browser-count" class="planet-list-count">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="kab-browser-intro planet-text">Browse the 11 sefiroth, including Daath, in a dedicated detail browser. Use Paths for the Rosicrucian Cross and Tree for the diagram.</div>
|
||||||
|
<div id="kab-browser-list" class="planet-card-list" role="listbox" aria-label="Kabbalah sefirot"></div>
|
||||||
|
</aside>
|
||||||
|
<section class="planet-detail-panel" aria-live="polite">
|
||||||
|
<div class="planet-detail-heading">
|
||||||
|
<h2 id="kab-browser-detail-name">Kabbalah Sefirot</h2>
|
||||||
|
<div id="kab-browser-detail-sub" class="planet-detail-type">Select a sephira or path to explore</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse Kabbalah sefirot">
|
||||||
|
<button id="kab-browser-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous Kabbalah sephirah">Back</button>
|
||||||
|
<div id="kab-browser-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="kab-browser-detail-next" class="detail-sequence-btn" type="button" aria-label="Next Kabbalah sephirah">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="kab-browser-detail-body" class="planet-meta-grid"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="kabbalah-worlds-section" hidden>
|
||||||
|
<div class="planet-layout">
|
||||||
|
<aside class="planet-list-panel">
|
||||||
|
<div class="planet-list-header">
|
||||||
|
<strong>Qabalistic Worlds</strong>
|
||||||
|
<span id="kab-worlds-count" class="planet-list-count">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="kab-browser-intro planet-text">Browse the four Qabalistic Worlds and their corresponding soul layers as a dedicated page.</div>
|
||||||
|
<div id="kab-worlds-list" class="planet-card-list" role="listbox" aria-label="Qabalistic worlds"></div>
|
||||||
|
</aside>
|
||||||
|
<section class="planet-detail-panel" aria-live="polite">
|
||||||
|
<div class="planet-detail-heading">
|
||||||
|
<h2 id="kab-worlds-detail-name">Qabalistic Worlds</h2>
|
||||||
|
<div id="kab-worlds-detail-sub" class="planet-detail-type">Select a world layer to explore</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse Qabalistic Worlds">
|
||||||
|
<button id="kab-worlds-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous Qabalistic world">Back</button>
|
||||||
|
<div id="kab-worlds-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="kab-worlds-detail-next" class="detail-sequence-btn" type="button" aria-label="Next Qabalistic world">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="kab-worlds-detail-body" class="planet-meta-grid"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="kabbalah-paths-section" hidden>
|
||||||
|
<div class="planet-layout">
|
||||||
|
<aside class="planet-list-panel">
|
||||||
|
<div class="planet-list-header">
|
||||||
|
<strong>Paths</strong>
|
||||||
|
<span id="kab-paths-count" class="planet-list-count">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="kab-browser-intro planet-text">Browse the 22 Hebrew letter paths as their own entry page. Use Rosicrucian Cross for the petal view.</div>
|
||||||
|
<div id="kab-paths-list" class="planet-card-list" role="listbox" aria-label="Kabbalah paths"></div>
|
||||||
|
</aside>
|
||||||
|
<section class="planet-detail-panel" aria-live="polite">
|
||||||
|
<div class="planet-detail-heading">
|
||||||
|
<h2 id="kab-paths-detail-name">Kabbalah Paths</h2>
|
||||||
|
<div id="kab-paths-detail-sub" class="planet-detail-type">Select a path to explore</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse Kabbalah paths">
|
||||||
|
<button id="kab-paths-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous Kabbalah path">Back</button>
|
||||||
|
<div id="kab-paths-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="kab-paths-detail-next" class="detail-sequence-btn" type="button" aria-label="Next Kabbalah path">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="kab-paths-detail-body" class="planet-meta-grid"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="kabbalah-cross-section" hidden>
|
||||||
<div class="kab-rose-layout">
|
<div class="kab-rose-layout">
|
||||||
<aside class="kab-rose-panel">
|
<aside class="kab-rose-panel">
|
||||||
<div class="planet-list-header">
|
<div class="planet-list-header">
|
||||||
<strong>Rosicrucian Cross</strong>
|
<strong>Rosicrucian Cross</strong>
|
||||||
<span class="planet-list-count">22 Hebrew Letter Paths</span>
|
<span class="planet-list-count">22 Hebrew Letter Petals</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="kab-rose-intro planet-text">Click a Hebrew letter petal to open path correspondences.</div>
|
<div class="kab-rose-intro planet-text">Browse the Rosicrucian Cross as its own page. Click a Hebrew letter petal to explore the linked path from the cross view.</div>
|
||||||
<div id="kab-rose-cross-container" class="kab-rose-cross-container"></div>
|
<div id="kab-rose-cross-container" class="kab-rose-cross-container"></div>
|
||||||
</aside>
|
</aside>
|
||||||
<section class="kab-detail-panel" aria-live="polite">
|
<section class="kab-detail-panel" aria-live="polite">
|
||||||
<div class="planet-detail-heading">
|
<div class="planet-detail-heading">
|
||||||
<h2 id="kab-rose-detail-name">Rosicrucian Cross</h2>
|
<h2 id="kab-rose-detail-name">Rosicrucian Cross</h2>
|
||||||
<div id="kab-rose-detail-sub" class="planet-detail-type">Select a Hebrew letter petal to explore the path</div>
|
<div id="kab-rose-detail-sub" class="planet-detail-type">Select a Hebrew letter petal to explore the path</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse Rosicrucian Cross paths">
|
||||||
|
<button id="kab-rose-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous Rosicrucian Cross path">Back</button>
|
||||||
|
<div id="kab-rose-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="kab-rose-detail-next" class="detail-sequence-btn" type="button" aria-label="Next Rosicrucian Cross path">Next</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="kab-rose-detail-body" class="planet-meta-grid"></div>
|
<div id="kab-rose-detail-body" class="planet-meta-grid"></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -821,6 +928,11 @@
|
|||||||
<div class="planet-detail-heading">
|
<div class="planet-detail-heading">
|
||||||
<h2 id="kab-detail-name">Kabbalah Tree of Life</h2>
|
<h2 id="kab-detail-name">Kabbalah Tree of Life</h2>
|
||||||
<div id="kab-detail-sub" class="planet-detail-type">Select a sephira or path to explore</div>
|
<div id="kab-detail-sub" class="planet-detail-type">Select a sephira or path to explore</div>
|
||||||
|
<div class="detail-sequence-nav" aria-label="Browse Kabbalah nodes">
|
||||||
|
<button id="kab-detail-prev" class="detail-sequence-btn" type="button" aria-label="Previous Kabbalah node">Back</button>
|
||||||
|
<div id="kab-detail-position" class="detail-sequence-position" aria-live="polite">--</div>
|
||||||
|
<button id="kab-detail-next" class="detail-sequence-btn" type="button" aria-label="Next Kabbalah node">Next</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="kab-detail-body" class="planet-meta-grid"></div>
|
<div id="kab-detail-body" class="planet-meta-grid"></div>
|
||||||
</section>
|
</section>
|
||||||
@@ -1182,10 +1294,10 @@
|
|||||||
<script src="node_modules/astronomy-engine/astronomy.browser.min.js"></script>
|
<script src="node_modules/astronomy-engine/astronomy.browser.min.js"></script>
|
||||||
<script src="app/astro-calcs.js"></script>
|
<script src="app/astro-calcs.js"></script>
|
||||||
<script src="app/app-config.js?v=20260309-gate"></script>
|
<script src="app/app-config.js?v=20260309-gate"></script>
|
||||||
<script src="app/data-service.js?v=20260319-word-dictionary-01"></script>
|
<script src="app/data-service.js?v=20260527-api-connection-01"></script>
|
||||||
<script src="app/calendar-events.js"></script>
|
<script src="app/calendar-events.js"></script>
|
||||||
<script src="app/card-images.js?v=20260309-gate"></script>
|
<script src="app/card-images.js?v=20260527-tarot-deck-gallery-01"></script>
|
||||||
<script src="app/ui-tarot-lightbox.js?v=20260404-lightbox-pinch-01"></script>
|
<script src="app/ui-tarot-lightbox.js?v=20260528-tarot-variant-sequence-02"></script>
|
||||||
<script src="app/ui-tarot-house.js?v=20260401-house-top-date-01"></script>
|
<script src="app/ui-tarot-house.js?v=20260401-house-top-date-01"></script>
|
||||||
<script src="app/ui-tarot-relations.js"></script>
|
<script src="app/ui-tarot-relations.js"></script>
|
||||||
<script src="app/ui-now-helpers.js?v=20260314-now-planets-grid-01"></script>
|
<script src="app/ui-now-helpers.js?v=20260314-now-planets-grid-01"></script>
|
||||||
@@ -1198,16 +1310,17 @@
|
|||||||
<script src="app/ui-calendar-detail-panels.js?v=20260424-association-web-02"></script>
|
<script src="app/ui-calendar-detail-panels.js?v=20260424-association-web-02"></script>
|
||||||
<script src="app/ui-calendar-detail.js?v=20260424-detail-inline-links-02"></script>
|
<script src="app/ui-calendar-detail.js?v=20260424-detail-inline-links-02"></script>
|
||||||
<script src="app/ui-calendar-data.js?v=20260424-decan-ranges-01"></script>
|
<script src="app/ui-calendar-data.js?v=20260424-decan-ranges-01"></script>
|
||||||
<script src="app/ui-calendar.js"></script>
|
<script src="app/ui-sequence-nav.js?v=20260528-sequence-nav-01"></script>
|
||||||
|
<script src="app/ui-calendar.js?v=20260528-sequence-nav-01"></script>
|
||||||
<script src="app/ui-holidays-data.js"></script>
|
<script src="app/ui-holidays-data.js"></script>
|
||||||
<script src="app/ui-holidays-render.js?v=20260424-association-web-01"></script>
|
<script src="app/ui-holidays-render.js?v=20260424-association-web-01"></script>
|
||||||
<script src="app/ui-holidays.js"></script>
|
<script src="app/ui-holidays.js?v=20260528-sequence-nav-01"></script>
|
||||||
<script src="app/ui-tarot-card-derivations.js?v=20260307b"></script>
|
<script src="app/ui-tarot-card-derivations.js?v=20260307b"></script>
|
||||||
<script src="app/ui-tarot-detail.js?v=20260424-association-web-04"></script>
|
<script src="app/ui-tarot-detail.js?v=20260527-tarot-deck-gallery-02"></script>
|
||||||
<script src="app/ui-tarot-relation-display.js?v=20260307b"></script>
|
<script src="app/ui-tarot-relation-display.js?v=20260307b"></script>
|
||||||
<script src="app/ui-tarot.js?v=20260402-frame-lightbox-01"></script>
|
<script src="app/ui-tarot.js?v=20260528-tarot-variant-sequence-02"></script>
|
||||||
<script src="app/ui-planets-references.js"></script>
|
<script src="app/ui-planets-references.js"></script>
|
||||||
<script src="app/ui-planets.js?v=20260424-detail-inline-links-01"></script>
|
<script src="app/ui-planets.js?v=20260528-sequence-nav-01"></script>
|
||||||
<script src="app/ui-cycles.js?v=20260424-detail-inline-links-02"></script>
|
<script src="app/ui-cycles.js?v=20260424-detail-inline-links-02"></script>
|
||||||
<script src="app/ui-elements.js?v=20260424-association-web-01"></script>
|
<script src="app/ui-elements.js?v=20260424-association-web-01"></script>
|
||||||
<script src="app/ui-audio-notes.js?v=20260314-audio-notes-02"></script>
|
<script src="app/ui-audio-notes.js?v=20260314-audio-notes-02"></script>
|
||||||
@@ -1215,9 +1328,9 @@
|
|||||||
<script src="app/ui-iching-references.js"></script>
|
<script src="app/ui-iching-references.js"></script>
|
||||||
<script src="app/ui-iching.js?v=20260424-association-web-01"></script>
|
<script src="app/ui-iching.js?v=20260424-association-web-01"></script>
|
||||||
<script src="app/ui-rosicrucian-cross.js"></script>
|
<script src="app/ui-rosicrucian-cross.js"></script>
|
||||||
<script src="app/ui-kabbalah-detail.js?v=20260424-detail-inline-links-01"></script>
|
<script src="app/ui-kabbalah-detail.js?v=20260528-kabbalah-worlds-03"></script>
|
||||||
<script src="app/ui-kabbalah-views.js"></script>
|
<script src="app/ui-kabbalah-views.js"></script>
|
||||||
<script src="app/ui-kabbalah.js?v=20260312-tree-export-01"></script>
|
<script src="app/ui-kabbalah.js?v=20260528-kabbalah-cross-split-06"></script>
|
||||||
<script src="app/ui-cube-detail.js?v=20260424-association-web-02"></script>
|
<script src="app/ui-cube-detail.js?v=20260424-association-web-02"></script>
|
||||||
<script src="app/ui-cube-chassis.js?v=20260424-cube-fixes-01"></script>
|
<script src="app/ui-cube-chassis.js?v=20260424-cube-fixes-01"></script>
|
||||||
<script src="app/ui-cube-math.js"></script>
|
<script src="app/ui-cube-math.js"></script>
|
||||||
@@ -1246,15 +1359,15 @@
|
|||||||
<script src="app/ui-numbers.js"></script>
|
<script src="app/ui-numbers.js"></script>
|
||||||
<script src="app/ui-tarot-spread.js"></script>
|
<script src="app/ui-tarot-spread.js"></script>
|
||||||
<script src="app/ui-tarot-frame.js?v=20260424-frame-export-01"></script>
|
<script src="app/ui-tarot-frame.js?v=20260424-frame-export-01"></script>
|
||||||
<script src="app/ui-settings.js?v=20260415-stellarium-toggle-01"></script>
|
<script src="app/ui-settings.js?v=20260527-tarot-deck-gallery-01"></script>
|
||||||
<script src="app/ui-chrome.js?v=20260328-topbar-settings-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-navigation.js?v=20260528-kabbalah-cross-split-06"></script>
|
||||||
<script src="app/ui-calendar-formatting.js?v=20260307b"></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-calendar-visuals.js?v=20260307b"></script>
|
||||||
<script src="app/ui-home-calendar.js?v=20260415-stellarium-toggle-01"></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/ui-section-state.js?v=20260528-kabbalah-cross-split-06"></script>
|
||||||
<script src="app/app-runtime.js?v=20260309-gate"></script>
|
<script src="app/app-runtime.js?v=20260309-gate"></script>
|
||||||
<script src="app.js?v=20260415-stellarium-toggle-01"></script>
|
<script src="app.js?v=20260528-kabbalah-cross-split-06"></script>
|
||||||
<script src="app/navigation-detail-test-harness.js?v=20260401-universal-detail-02"></script>
|
<script src="app/navigation-detail-test-harness.js?v=20260401-universal-detail-02"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user