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 ichingSectionEl = document.getElementById("iching-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 cubeSectionEl = document.getElementById("cube-section");
|
||||
const alphabetSectionEl = document.getElementById("alphabet-section");
|
||||
@@ -78,6 +81,10 @@ const openCyclesEl = document.getElementById("open-cycles");
|
||||
const openElementsEl = document.getElementById("open-elements");
|
||||
const openIChingEl = document.getElementById("open-iching");
|
||||
const openKabbalahEl = document.getElementById("open-kabbalah");
|
||||
const 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 openKabbalahCubeEl = document.getElementById("open-kabbalah-cube");
|
||||
const openAlphabetEl = document.getElementById("open-alphabet");
|
||||
@@ -327,15 +334,22 @@ function getConnectionSettings() {
|
||||
};
|
||||
}
|
||||
|
||||
function syncConnectionGateInputs() {
|
||||
const connectionSettings = getConnectionSettings();
|
||||
function normalizeConnectionSettingsInput(connectionSettings = null) {
|
||||
return {
|
||||
apiBaseUrl: String(connectionSettings?.apiBaseUrl || "").trim().replace(/\/+$/, ""),
|
||||
apiKey: String(connectionSettings?.apiKey || "").trim()
|
||||
};
|
||||
}
|
||||
|
||||
function syncConnectionGateInputs(connectionSettings = getConnectionSettings()) {
|
||||
const normalizedConnectionSettings = normalizeConnectionSettingsInput(connectionSettings);
|
||||
|
||||
if (connectionGateBaseUrlEl) {
|
||||
connectionGateBaseUrlEl.value = String(connectionSettings.apiBaseUrl || "");
|
||||
connectionGateBaseUrlEl.value = normalizedConnectionSettings.apiBaseUrl;
|
||||
}
|
||||
|
||||
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") {
|
||||
syncConnectionGateInputs();
|
||||
function showConnectionGate(message, tone = "default", connectionSettings = null) {
|
||||
syncConnectionGateInputs(connectionSettings || getConnectionSettings());
|
||||
if (connectionGateEl) {
|
||||
connectionGateEl.hidden = false;
|
||||
}
|
||||
@@ -369,33 +383,49 @@ function hideConnectionGate() {
|
||||
}
|
||||
|
||||
function getConnectionSettingsFromGate() {
|
||||
return {
|
||||
return normalizeConnectionSettingsInput({
|
||||
apiBaseUrl: String(connectionGateBaseUrlEl?.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) {
|
||||
if (nextConnectionSettings) {
|
||||
window.TarotAppConfig?.updateConnectionSettings?.(nextConnectionSettings);
|
||||
const configuredConnection = nextConnectionSettings
|
||||
? normalizeConnectionSettingsInput(nextConnectionSettings)
|
||||
: getConnectionSettings();
|
||||
|
||||
if (!nextConnectionSettings) {
|
||||
syncConnectionGateInputs(configuredConnection);
|
||||
}
|
||||
|
||||
syncConnectionGateInputs();
|
||||
|
||||
const configuredConnection = getConnectionSettings();
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
showConnectionGate(probeResult?.message || "Unable to reach the API.", "error");
|
||||
showConnectionGate(probeResult?.message || "Unable to reach the API.", "error", configuredConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextConnectionSettings) {
|
||||
window.TarotAppConfig?.updateConnectionSettings?.(configuredConnection);
|
||||
syncConnectionGateInputs(configuredConnection);
|
||||
}
|
||||
|
||||
hideConnectionGate();
|
||||
if (!hasRenderedConnectedShell) {
|
||||
sectionStateUi.setActiveSection?.("home");
|
||||
@@ -405,6 +435,7 @@ async function ensureConnectedApp(nextConnectionSettings = null) {
|
||||
setConnectionGateStatus("Connected.", "success");
|
||||
setStatus(`Connected to ${configuredConnection.apiBaseUrl}.`);
|
||||
await appRuntime.renderWeek?.();
|
||||
warmAllDeckImagesInBackground();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -491,6 +522,9 @@ sectionStateUi.init?.({
|
||||
elementsSectionEl,
|
||||
ichingSectionEl,
|
||||
kabbalahSectionEl,
|
||||
kabbalahWorldsSectionEl,
|
||||
kabbalahPathsSectionEl,
|
||||
kabbalahCrossSectionEl,
|
||||
kabbalahTreeSectionEl,
|
||||
cubeSectionEl,
|
||||
alphabetSectionEl,
|
||||
@@ -519,6 +553,10 @@ sectionStateUi.init?.({
|
||||
openElementsEl,
|
||||
openIChingEl,
|
||||
openKabbalahEl,
|
||||
openKabbalahSephirotEl,
|
||||
openKabbalahWorldsEl,
|
||||
openKabbalahPathsEl,
|
||||
openKabbalahCrossEl,
|
||||
openKabbalahTreeEl,
|
||||
openKabbalahCubeEl,
|
||||
openAlphabetEl,
|
||||
@@ -626,6 +664,10 @@ navigationUi.init?.({
|
||||
openElementsEl,
|
||||
openIChingEl,
|
||||
openKabbalahEl,
|
||||
openKabbalahSephirotEl,
|
||||
openKabbalahWorldsEl,
|
||||
openKabbalahPathsEl,
|
||||
openKabbalahCrossEl,
|
||||
openKabbalahTreeEl,
|
||||
openKabbalahCubeEl,
|
||||
openAlphabetEl,
|
||||
|
||||
+34
-1
@@ -137,7 +137,7 @@
|
||||
const standardMinorSuits = ["Wands", "Cups", "Swords", "Disks"];
|
||||
const standardDeckCardNames = buildStandardDeckCardNames();
|
||||
|
||||
let deckManifestSources = buildDeckManifestSources();
|
||||
let deckManifestSources = null;
|
||||
|
||||
const manifestCache = new Map();
|
||||
const cardBackCache = new Map();
|
||||
@@ -161,6 +161,25 @@
|
||||
.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) {
|
||||
const normalizedBasePath = String(basePath || "").trim();
|
||||
if (!normalizedBasePath) {
|
||||
@@ -530,6 +549,9 @@
|
||||
try {
|
||||
const request = new XMLHttpRequest();
|
||||
request.open("GET", encodeURI(path), false);
|
||||
Object.entries(buildManifestRequestHeaders(path)).forEach(([headerName, headerValue]) => {
|
||||
request.setRequestHeader(headerName, headerValue);
|
||||
});
|
||||
request.send(null);
|
||||
|
||||
const okStatus = (request.status >= 200 && request.status < 300) || request.status === 0;
|
||||
@@ -1164,6 +1186,9 @@
|
||||
})
|
||||
.then((result) => {
|
||||
markDeckAsWarmed(normalizedDeckId);
|
||||
if (options.background) {
|
||||
emitDeckPreloadStatus();
|
||||
}
|
||||
if (!options.background) {
|
||||
setDeckPreloadStatus({
|
||||
activeDeckId: normalizedDeckId,
|
||||
@@ -1211,6 +1236,13 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function scheduleAllDeckImagePreload(options = {}) {
|
||||
return deferPreload(() => preloadAllDeckImages({
|
||||
...defaultDeckWarmupOptions,
|
||||
...options
|
||||
}));
|
||||
}
|
||||
|
||||
function resolveDisplayNameWithDeck(deckId, cardName, trumpNumber) {
|
||||
const manifest = getDeckManifest(deckId);
|
||||
const fallbackName = String(cardName || "").trim();
|
||||
@@ -1333,6 +1365,7 @@
|
||||
resolveTarotCardBackThumbnail,
|
||||
preloadDeckImages,
|
||||
preloadAllDeckImages,
|
||||
scheduleAllDeckImagePreload,
|
||||
ensureImageLoaded,
|
||||
isImageLoaded,
|
||||
getDeckPreloadStatus: () => emitDeckPreloadStatus(),
|
||||
|
||||
+24
-9
@@ -102,8 +102,22 @@
|
||||
pluto: "Pluto"
|
||||
};
|
||||
|
||||
function buildRequestHeaders() {
|
||||
const apiKey = getApiKey();
|
||||
function resolveConnectionSettings(connectionSettings = null) {
|
||||
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
|
||||
? {
|
||||
"x-api-key": apiKey
|
||||
@@ -172,8 +186,8 @@
|
||||
.join("/");
|
||||
}
|
||||
|
||||
function buildApiUrl(path, query = {}) {
|
||||
const apiBaseUrl = getApiBaseUrl();
|
||||
function buildApiUrl(path, query = {}, connectionSettings = null) {
|
||||
const { apiBaseUrl } = resolveConnectionSettings(connectionSettings);
|
||||
if (!apiBaseUrl) {
|
||||
return "";
|
||||
}
|
||||
@@ -569,8 +583,9 @@
|
||||
}));
|
||||
}
|
||||
|
||||
async function probeConnection() {
|
||||
const apiBaseUrl = getApiBaseUrl();
|
||||
async function probeConnection(connectionSettings = null) {
|
||||
const resolvedConnection = resolveConnectionSettings(connectionSettings);
|
||||
const apiBaseUrl = resolvedConnection.apiBaseUrl;
|
||||
if (!apiBaseUrl) {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -580,11 +595,11 @@
|
||||
}
|
||||
|
||||
const requestOptions = {
|
||||
headers: buildRequestHeaders()
|
||||
headers: buildRequestHeaders(resolvedConnection)
|
||||
};
|
||||
|
||||
try {
|
||||
const healthResponse = await fetch(buildApiUrl("/api/v1/health"), requestOptions);
|
||||
const healthResponse = await fetch(buildApiUrl("/api/v1/health", {}, resolvedConnection), requestOptions);
|
||||
if (!healthResponse.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -594,7 +609,7 @@
|
||||
}
|
||||
|
||||
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) {
|
||||
return {
|
||||
|
||||
+161
-1
@@ -792,7 +792,7 @@
|
||||
}
|
||||
.tarot-detail-top {
|
||||
display: grid;
|
||||
grid-template-columns: 150px minmax(0, 1fr);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@@ -866,6 +866,93 @@
|
||||
text-transform: uppercase;
|
||||
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 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -3190,6 +3277,43 @@
|
||||
line-height: 1.45;
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
@@ -3520,6 +3644,34 @@
|
||||
}
|
||||
#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 {
|
||||
height: calc(100vh - 61px);
|
||||
background: #18181b;
|
||||
@@ -5692,6 +5844,14 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.detail-sequence-nav {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detail-sequence-position {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.alpha-text-controls--heading {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
hebrewById: new Map(),
|
||||
dayLinksCache: new Map()
|
||||
};
|
||||
let detailNavigator = null;
|
||||
|
||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||
"the fool": 0,
|
||||
@@ -200,6 +201,7 @@
|
||||
|
||||
function getElements() {
|
||||
return {
|
||||
sectionEl: document.getElementById("calendar-section"),
|
||||
monthListEl: document.getElementById("calendar-month-list"),
|
||||
monthCountEl: document.getElementById("calendar-month-count"),
|
||||
listTitleEl: document.getElementById("calendar-list-title"),
|
||||
@@ -210,6 +212,9 @@
|
||||
searchClearEl: document.getElementById("calendar-search-clear"),
|
||||
detailNameEl: document.getElementById("calendar-detail-name"),
|
||||
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")
|
||||
};
|
||||
}
|
||||
@@ -523,6 +528,66 @@
|
||||
|
||||
function 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) {
|
||||
@@ -613,6 +678,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function bindDetailNavigation(elements) {
|
||||
getDetailNavigator()?.bind(elements);
|
||||
}
|
||||
|
||||
function loadCalendarType(calendarId, elements) {
|
||||
const months = state.calendarData[calendarId];
|
||||
if (!Array.isArray(months)) {
|
||||
@@ -745,6 +814,8 @@
|
||||
bindYearInput(elements);
|
||||
bindSearchInput(elements);
|
||||
bindCalendarTypeSelect(elements);
|
||||
bindDetailNavigation(elements);
|
||||
bindKeyboardNavigation(elements);
|
||||
}
|
||||
|
||||
applySearchFilter(elements);
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
hebrewById: new Map(),
|
||||
calendarData: {}
|
||||
};
|
||||
let detailNavigator = null;
|
||||
|
||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||
"the fool": 0,
|
||||
@@ -93,6 +94,7 @@
|
||||
|
||||
function getElements() {
|
||||
return {
|
||||
sectionEl: document.getElementById("holiday-section"),
|
||||
sourceSelectEl: document.getElementById("holiday-source-select"),
|
||||
yearInputEl: document.getElementById("holiday-year-input"),
|
||||
searchInputEl: document.getElementById("holiday-search-input"),
|
||||
@@ -101,6 +103,9 @@
|
||||
listEl: document.getElementById("holiday-list"),
|
||||
detailNameEl: document.getElementById("holiday-detail-name"),
|
||||
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")
|
||||
};
|
||||
}
|
||||
@@ -225,6 +230,7 @@
|
||||
detailNameEl.textContent = "--";
|
||||
detailSubEl.textContent = "Select a holiday to explore";
|
||||
detailBodyEl.innerHTML = "";
|
||||
syncDetailNavigation(elements);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -232,6 +238,66 @@
|
||||
detailSubEl.textContent = `${holidayDataUi.calendarLabel(holiday?.calendarId)} - ${holidayDataUi.monthLabelForCalendar(state.calendarData, holiday?.calendarId, holiday?.monthId)}`;
|
||||
detailBodyEl.innerHTML = renderHolidayDetail(holiday);
|
||||
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) {
|
||||
@@ -307,6 +373,8 @@
|
||||
elements.searchInputEl.focus();
|
||||
});
|
||||
}
|
||||
|
||||
bindKeyboardNavigation(elements);
|
||||
}
|
||||
|
||||
function attachNavHandlers(detailBodyEl) {
|
||||
|
||||
@@ -309,6 +309,62 @@
|
||||
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) {
|
||||
return String(value || "")
|
||||
.split(/,|;|·|\/|\bor\b|\band\b|\+/i)
|
||||
@@ -419,12 +475,14 @@
|
||||
|
||||
function renderSephiraDetail(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 =
|
||||
[seph.nameHebrew, seph.translation, seph.planet].filter(Boolean).join(" · ");
|
||||
|
||||
elements.detailBodyEl.innerHTML = "";
|
||||
elements.detailBodyEl.appendChild(buildFourWorldsCard(tree, "", context));
|
||||
elements.detailBodyEl.appendChild(buildPlanetLuminaryCard(seph.planet, context));
|
||||
elements.detailBodyEl.appendChild(metaCard("Intelligence", seph.intelligence));
|
||||
elements.detailBodyEl.appendChild(buildTarotAttributionCard(seph.tarot));
|
||||
@@ -484,7 +542,6 @@
|
||||
elements.detailSubEl.textContent = [path.tarot?.card, astro].filter(Boolean).join(" · ");
|
||||
|
||||
elements.detailBodyEl.innerHTML = "";
|
||||
elements.detailBodyEl.appendChild(buildFourWorldsCard(tree, context.activeHebrewToken, context));
|
||||
elements.detailBodyEl.appendChild(buildConnectsCard(path, fromName, toName));
|
||||
elements.detailBodyEl.appendChild(buildHebrewLetterCard(letter, context));
|
||||
elements.detailBodyEl.appendChild(buildAstrologyCard(path.astrology, context));
|
||||
@@ -543,6 +600,7 @@
|
||||
}
|
||||
|
||||
window.KabbalahDetailUi = {
|
||||
renderWorldLayerDetail,
|
||||
renderSephiraDetail,
|
||||
renderPathDetail,
|
||||
renderRoseLandingIntro
|
||||
|
||||
+992
-32
File diff suppressed because it is too large
Load Diff
+45
-7
@@ -22,6 +22,20 @@
|
||||
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 = {
|
||||
tarot: "#tarot-browse-view .tarot-layout",
|
||||
cube: "#cube-layout",
|
||||
@@ -31,6 +45,10 @@
|
||||
iching: "#iching-section .planet-layout",
|
||||
gods: "#gods-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",
|
||||
planets: "#planet-section .planet-layout",
|
||||
elements: "#elements-section .planet-layout"
|
||||
@@ -167,12 +185,28 @@
|
||||
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, () => {
|
||||
setActiveSection(getActiveSection() === "kabbalah-tree" ? "home" : "kabbalah-tree");
|
||||
setActiveSection("kabbalah-tree");
|
||||
});
|
||||
|
||||
bindClick(elements.openKabbalahCubeEl, () => {
|
||||
setActiveSection(getActiveSection() === "cube" ? "home" : "cube");
|
||||
setActiveSection("cube");
|
||||
});
|
||||
|
||||
bindClick(elements.openAlphabetWordEl, () => {
|
||||
@@ -403,17 +437,21 @@
|
||||
|
||||
document.addEventListener("nav:kabbalah-path", (event) => {
|
||||
const magickDataset = getMagickDataset();
|
||||
const pathNo = event?.detail?.pathNo;
|
||||
const pathNo = getKabbalahPathNo(event?.detail);
|
||||
if (typeof ensure.ensureKabbalahSection === "function" && magickDataset) {
|
||||
ensure.ensureKabbalahSection(magickDataset);
|
||||
}
|
||||
setActiveSection("kabbalah-tree");
|
||||
if (pathNo != null) {
|
||||
const targetSection = Number(pathNo) >= 11 ? "kabbalah-paths" : "kabbalah";
|
||||
setActiveSection(targetSection);
|
||||
requestAnimationFrame(() => {
|
||||
window.KabbalahSectionUi?.selectNode?.(pathNo);
|
||||
scheduleSectionDetailOnly("kabbalah-tree");
|
||||
scheduleSectionDetailOnly(targetSection);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveSection("kabbalah-paths");
|
||||
});
|
||||
|
||||
document.addEventListener("nav:planet", (event) => {
|
||||
@@ -473,7 +511,7 @@
|
||||
});
|
||||
|
||||
document.addEventListener("tarot:view-kab-path", (event) => {
|
||||
setActiveSection("kabbalah-tree");
|
||||
setActiveSection("kabbalah-paths");
|
||||
const pathNumber = event?.detail?.pathNumber;
|
||||
if (pathNumber != null) {
|
||||
requestAnimationFrame(() => {
|
||||
@@ -483,7 +521,7 @@
|
||||
} else {
|
||||
kabbalahUi?.selectPathByNumber?.(pathNumber);
|
||||
}
|
||||
scheduleSectionDetailOnly("kabbalah-tree");
|
||||
scheduleSectionDetailOnly("kabbalah-paths");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
+74
-3
@@ -21,6 +21,7 @@
|
||||
monthRefsByPlanetId: new Map(),
|
||||
cubePlacementsByPlanetId: new Map()
|
||||
};
|
||||
let detailNavigator = null;
|
||||
|
||||
function normalizePlanetToken(value) {
|
||||
return String(value || "")
|
||||
@@ -80,12 +81,16 @@
|
||||
|
||||
function getElements() {
|
||||
return {
|
||||
planetSectionEl: document.getElementById("planet-section"),
|
||||
planetCardListEl: document.getElementById("planet-card-list"),
|
||||
planetSearchInputEl: document.getElementById("planet-search-input"),
|
||||
planetSearchClearEl: document.getElementById("planet-search-clear"),
|
||||
planetCountEl: document.getElementById("planet-card-count"),
|
||||
planetDetailNameEl: document.getElementById("planet-detail-name"),
|
||||
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"),
|
||||
planetDetailFactsEl: document.getElementById("planet-detail-facts"),
|
||||
planetDetailAtmosphereEl: document.getElementById("planet-detail-atmosphere"),
|
||||
@@ -156,11 +161,14 @@
|
||||
if (!state.filteredEntries.some((entry) => entry.id === state.selectedId)) {
|
||||
if (state.filteredEntries.length > 0) {
|
||||
selectById(state.filteredEntries[0].id, elements);
|
||||
} else {
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
updateSelection(elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
|
||||
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) {
|
||||
const entry = state.entries.find((planet) => planet.id === id);
|
||||
if (!entry) {
|
||||
@@ -443,6 +513,7 @@
|
||||
state.selectedId = entry.id;
|
||||
updateSelection(elements);
|
||||
renderDetail(entry, elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
|
||||
function renderList(elements) {
|
||||
@@ -580,6 +651,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
bindKeyboardNavigation(elements);
|
||||
|
||||
state.initialized = true;
|
||||
}
|
||||
|
||||
@@ -594,9 +667,7 @@
|
||||
);
|
||||
if (!entry) return;
|
||||
selectById(entry.id, el);
|
||||
el.planetCardListEl
|
||||
?.querySelector(`[data-planet-id="${entry.id}"]`)
|
||||
?.scrollIntoView({ block: "nearest" });
|
||||
scrollEntryIntoView(entry.id, el);
|
||||
}
|
||||
|
||||
window.PlanetSectionUi = {
|
||||
|
||||
+15
-2
@@ -19,6 +19,9 @@
|
||||
"elements",
|
||||
"iching",
|
||||
"kabbalah",
|
||||
"kabbalah-worlds",
|
||||
"kabbalah-paths",
|
||||
"kabbalah-cross",
|
||||
"kabbalah-tree",
|
||||
"cube",
|
||||
"alphabet",
|
||||
@@ -109,9 +112,12 @@
|
||||
const isElementsOpen = activeSection === "elements";
|
||||
const isIChingOpen = activeSection === "iching";
|
||||
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 isCubeOpen = activeSection === "cube";
|
||||
const isKabbalahMenuOpen = isKabbalahOpen || isKabbalahTreeOpen || isCubeOpen;
|
||||
const isKabbalahMenuOpen = isKabbalahOpen || isKabbalahWorldsOpen || isKabbalahPathsOpen || isKabbalahCrossOpen || isKabbalahTreeOpen || isCubeOpen;
|
||||
const isAlphabetOpen = activeSection === "alphabet";
|
||||
const isAlphabetLettersOpen = activeSection === "alphabet-letters";
|
||||
const isAlphabetTextOpen = activeSection === "alphabet-text";
|
||||
@@ -137,6 +143,9 @@
|
||||
setHidden(elements.elementsSectionEl, !isElementsOpen);
|
||||
setHidden(elements.ichingSectionEl, !isIChingOpen);
|
||||
setHidden(elements.kabbalahSectionEl, !isKabbalahOpen);
|
||||
setHidden(elements.kabbalahWorldsSectionEl, !isKabbalahWorldsOpen);
|
||||
setHidden(elements.kabbalahPathsSectionEl, !isKabbalahPathsOpen);
|
||||
setHidden(elements.kabbalahCrossSectionEl, !isKabbalahCrossOpen);
|
||||
setHidden(elements.kabbalahTreeSectionEl, !isKabbalahTreeOpen);
|
||||
setHidden(elements.cubeSectionEl, !isCubeOpen);
|
||||
setHidden(elements.alphabetSectionEl, !isAlphabetOpen);
|
||||
@@ -168,6 +177,10 @@
|
||||
setPressed(elements.openElementsEl, isElementsOpen);
|
||||
setPressed(elements.openIChingEl, isIChingOpen);
|
||||
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.openKabbalahCubeEl, isCubeOpen);
|
||||
setPressed(elements.openAlphabetEl, isAlphabetMenuOpen);
|
||||
@@ -249,7 +262,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (isKabbalahOpen || isKabbalahTreeOpen) {
|
||||
if (isKabbalahOpen || isKabbalahWorldsOpen || isKabbalahPathsOpen || isKabbalahCrossOpen || isKabbalahTreeOpen) {
|
||||
ensure.ensureKabbalahSection?.(magickDataset);
|
||||
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 loadedCount = Math.max(0, Number(status?.selectedDeckLoadedCount) || 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 (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") {
|
||||
@@ -222,9 +227,21 @@
|
||||
}
|
||||
|
||||
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}).`;
|
||||
}
|
||||
|
||||
if (totalDeckCount > 1 && warmedDeckCount > 0) {
|
||||
return `Deck cache idle. Background warmup has ${Math.min(warmedDeckCount, totalDeckCount)}/${totalDeckCount} decks ready.`;
|
||||
}
|
||||
|
||||
return "Deck cache idle.";
|
||||
}
|
||||
|
||||
@@ -570,6 +587,18 @@
|
||||
const previousConnectionSettings = getConnectionSettings();
|
||||
const connectionSettings = getConnectionSettingsFromInputs();
|
||||
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 normalized = applySettingsToInputs(settings);
|
||||
syncSky(
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
getMagickDataset,
|
||||
resolveTarotCardImage,
|
||||
resolveTarotCardThumbnail,
|
||||
getDeckVariantsForCard,
|
||||
openDeckVariantLightbox,
|
||||
getDisplayCardName,
|
||||
buildTypeLabel,
|
||||
clearChildren,
|
||||
@@ -407,6 +409,73 @@
|
||||
.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) {
|
||||
if (!card || !elements) {
|
||||
return;
|
||||
@@ -454,6 +523,8 @@
|
||||
elements.tarotDetailReversedEl.textContent = card.meanings?.reversed || "--";
|
||||
}
|
||||
|
||||
renderDeckVariants(card, elements, cardDisplayName || card.name);
|
||||
|
||||
const meaningText = String(card.meaning || card.meanings?.upright || "").trim();
|
||||
if (elements.tarotMetaMeaningCardEl && elements.tarotDetailMeaningEl) {
|
||||
if (meaningText) {
|
||||
|
||||
+20
-12
@@ -837,12 +837,14 @@
|
||||
function normalizeCardRequest(request) {
|
||||
const normalized = normalizeOpenRequest(request);
|
||||
const label = String(normalized.label || normalized.altText || "Tarot card enlarged image").trim() || "Tarot card enlarged image";
|
||||
const cardId = String(normalized.cardId || "").trim();
|
||||
return {
|
||||
src: String(normalized.src || "").trim(),
|
||||
previewSrc: String(normalized.previewSrc || "").trim(),
|
||||
altText: String(normalized.altText || label).trim() || label,
|
||||
label,
|
||||
cardId: String(normalized.cardId || "").trim(),
|
||||
cardId,
|
||||
sequenceId: String(normalized.sequenceId || cardId).trim(),
|
||||
deckId: String(normalized.deckId || "").trim(),
|
||||
deckLabel: String(normalized.deckLabel || normalized.deckId || "").trim(),
|
||||
missingReason: String(normalized.missingReason || "").trim(),
|
||||
@@ -995,19 +997,20 @@
|
||||
});
|
||||
}
|
||||
|
||||
function resolveCardRequestById(cardId) {
|
||||
if (!cardId || typeof lightboxState.resolveCardById !== "function") {
|
||||
function resolveCardRequestById(sequenceId) {
|
||||
if (!sequenceId || typeof lightboxState.resolveCardById !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resolved = lightboxState.resolveCardById(cardId);
|
||||
const resolved = lightboxState.resolveCardById(sequenceId);
|
||||
if (!resolved) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalizeCardRequest({
|
||||
...resolved,
|
||||
cardId
|
||||
sequenceId: String(resolved.sequenceId || sequenceId).trim() || String(sequenceId),
|
||||
cardId: String(resolved.cardId || sequenceId).trim()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3277,7 +3280,10 @@
|
||||
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);
|
||||
if (startIndex < 0) {
|
||||
return;
|
||||
@@ -3285,12 +3291,13 @@
|
||||
|
||||
for (let offset = 1; offset <= sequence.length; offset += 1) {
|
||||
const nextIndex = (startIndex + direction * offset + sequence.length) % sequence.length;
|
||||
const nextCardId = sequence[nextIndex];
|
||||
if (!nextCardId || nextCardId === lightboxState.primaryCard?.cardId) {
|
||||
const nextSequenceId = sequence[nextIndex];
|
||||
const primarySequenceId = lightboxState.primaryCard?.sequenceId || lightboxState.primaryCard?.cardId;
|
||||
if (!nextSequenceId || nextSequenceId === primarySequenceId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextCard = resolveCardRequestById(nextCardId);
|
||||
const nextCard = resolveCardRequestById(nextSequenceId);
|
||||
if (nextCard && setSecondaryCard(nextCard, true)) {
|
||||
break;
|
||||
}
|
||||
@@ -3303,14 +3310,15 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const startIndex = sequence.indexOf(lightboxState.primaryCard?.cardId);
|
||||
const primarySequenceId = lightboxState.primaryCard?.sequenceId || lightboxState.primaryCard?.cardId;
|
||||
const startIndex = sequence.indexOf(primarySequenceId);
|
||||
if (startIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextIndex = (startIndex + direction + sequence.length) % sequence.length;
|
||||
const nextCardId = sequence[nextIndex];
|
||||
const nextCard = resolveCardRequestById(nextCardId);
|
||||
const nextSequenceId = sequence[nextIndex];
|
||||
const nextCard = resolveCardRequestById(nextSequenceId);
|
||||
if (!nextCard?.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
+227
-12
@@ -47,6 +47,7 @@
|
||||
courtCardByDecanId: new Map(),
|
||||
loadingPromise: null
|
||||
};
|
||||
let detailNavigator = null;
|
||||
|
||||
const TAROT_TRUMP_NUMBER_BY_NAME = {
|
||||
"the fool": 0,
|
||||
@@ -255,9 +256,14 @@
|
||||
tarotDetailImageEl: document.getElementById("tarot-detail-image"),
|
||||
tarotDetailNameEl: document.getElementById("tarot-detail-name"),
|
||||
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"),
|
||||
tarotDetailUprightEl: document.getElementById("tarot-detail-upright"),
|
||||
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"),
|
||||
tarotDetailMeaningEl: document.getElementById("tarot-detail-meaning"),
|
||||
tarotDetailKeywordsEl: document.getElementById("tarot-detail-keywords"),
|
||||
@@ -355,6 +361,8 @@
|
||||
getMagickDataset: () => state.magickDataset,
|
||||
resolveTarotCardImage,
|
||||
resolveTarotCardThumbnail,
|
||||
getDeckVariantsForCard,
|
||||
openDeckVariantLightbox,
|
||||
getDisplayCardName,
|
||||
buildTypeLabel,
|
||||
clearChildren,
|
||||
@@ -523,11 +531,14 @@
|
||||
if (!state.filteredCards.some((card) => card.id === state.selectedCardId)) {
|
||||
if (state.filteredCards.length > 0) {
|
||||
selectCardById(state.filteredCards[0].id, elements);
|
||||
} else {
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
updateListSelection(elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
|
||||
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) {
|
||||
const card = state.cards.find((entry) => entry.id === cardIdToSelect);
|
||||
if (!card) {
|
||||
@@ -733,6 +800,7 @@
|
||||
updateListSelection(elements);
|
||||
updateHouseSelection(elements);
|
||||
renderDetail(card, elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
|
||||
function scrollCardIntoView(cardIdToReveal, elements) {
|
||||
@@ -758,6 +826,47 @@
|
||||
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 = "") {
|
||||
const card = state.cards.find((entry) => entry.id === cardIdToResolve);
|
||||
if (!card) {
|
||||
@@ -790,8 +899,8 @@
|
||||
};
|
||||
}
|
||||
|
||||
function buildLightboxCardRequestById(cardIdToResolve) {
|
||||
const request = buildDeckLightboxCardRequest(cardIdToResolve, getActiveDeck?.() || "");
|
||||
function buildLightboxCardRequestById(cardIdToResolve, deckIdToResolve = "") {
|
||||
const request = buildDeckLightboxCardRequest(cardIdToResolve, deckIdToResolve || getActiveDeck?.() || "");
|
||||
if (!request?.src) {
|
||||
return null;
|
||||
}
|
||||
@@ -799,19 +908,67 @@
|
||||
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 = {}) {
|
||||
const normalizedCardId = String(cardIdToOpen || "").trim();
|
||||
if (!normalizedCardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId);
|
||||
const requestedDeckId = String(options?.deckId || getActiveDeck?.() || "").trim();
|
||||
const primaryCardRequest = buildLightboxCardRequestById(normalizedCardId, requestedDeckId);
|
||||
if (!primaryCardRequest?.src) {
|
||||
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 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"
|
||||
? options.onSelectCardId
|
||||
: (nextCardId) => {
|
||||
@@ -825,6 +982,7 @@
|
||||
altText: primaryCardRequest.altText,
|
||||
label: primaryCardRequest.label,
|
||||
cardId: primaryCardRequest.cardId,
|
||||
sequenceId: String(options?.sequenceId || primaryCardRequest.sequenceId || normalizedCardId).trim(),
|
||||
deckId: primaryCardRequest.deckId || activeDeckId,
|
||||
deckLabel: primaryCardRequest.deckLabel || "",
|
||||
compareDetails: primaryCardRequest.compareDetails || [],
|
||||
@@ -834,13 +992,72 @@
|
||||
activeDeckLabel: primaryCardRequest.deckLabel || "",
|
||||
availableCompareDecks,
|
||||
maxCompareDecks: 2,
|
||||
sequenceIds: state.cards.map((card) => card.id),
|
||||
resolveCardById: buildLightboxCardRequestById,
|
||||
sequenceIds,
|
||||
resolveCardById,
|
||||
resolveDeckCardById: buildDeckLightboxCardRequest,
|
||||
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) {
|
||||
if (!elements?.tarotCardListEl) {
|
||||
return;
|
||||
@@ -940,6 +1157,7 @@
|
||||
const selected = state.cards.find((card) => card.id === state.selectedCardId);
|
||||
if (selected) {
|
||||
renderDetail(selected, elements);
|
||||
syncDetailNavigation(elements);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -1007,6 +1225,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
bindKeyboardNavigation(elements);
|
||||
|
||||
if (elements.tarotHouseTopCardsVisibleEl) {
|
||||
elements.tarotHouseTopCardsVisibleEl.addEventListener("change", () => {
|
||||
state.houseTopCardsVisible = Boolean(elements.tarotHouseTopCardsVisibleEl.checked);
|
||||
@@ -1104,12 +1324,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const request = buildLightboxCardRequestById(state.selectedCardId);
|
||||
if (!request?.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.TarotUiLightbox?.open?.(request);
|
||||
openCardLightboxById(state.selectedCardId);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+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-700.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>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
@@ -63,8 +63,12 @@
|
||||
<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>
|
||||
<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-cube" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Cube</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="open-numbers" class="settings-trigger" type="button" aria-pressed="false">Numbers</button>
|
||||
@@ -216,6 +220,11 @@
|
||||
<div class="planet-detail-heading">
|
||||
<h2 id="calendar-detail-name">--</h2>
|
||||
<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 id="calendar-detail-body"></div>
|
||||
</section>
|
||||
@@ -252,6 +261,11 @@
|
||||
<div class="planet-detail-heading">
|
||||
<h2 id="holiday-detail-name">--</h2>
|
||||
<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 id="holiday-detail-body"></div>
|
||||
</section>
|
||||
@@ -273,10 +287,14 @@
|
||||
</aside>
|
||||
<section class="tarot-detail-panel" aria-live="polite">
|
||||
<div class="tarot-detail-top">
|
||||
<img id="tarot-detail-image" class="tarot-detail-image" alt="Tarot card image" />
|
||||
<div class="tarot-detail-heading">
|
||||
<h2 id="tarot-detail-name">--</h2>
|
||||
<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>
|
||||
</div>
|
||||
@@ -291,6 +309,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<strong>Traditional Meaning</strong>
|
||||
<div id="tarot-detail-meaning" class="planet-text">--</div>
|
||||
@@ -539,6 +561,11 @@
|
||||
<div class="planet-detail-heading">
|
||||
<h2 id="planet-detail-name">--</h2>
|
||||
<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>
|
||||
<div class="planet-meta-grid">
|
||||
@@ -774,19 +801,99 @@
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<aside class="kab-rose-panel">
|
||||
<div class="planet-list-header">
|
||||
<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 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>
|
||||
</aside>
|
||||
<section class="kab-detail-panel" aria-live="polite">
|
||||
<div class="planet-detail-heading">
|
||||
<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 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 id="kab-rose-detail-body" class="planet-meta-grid"></div>
|
||||
</section>
|
||||
@@ -821,6 +928,11 @@
|
||||
<div class="planet-detail-heading">
|
||||
<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 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 id="kab-detail-body" class="planet-meta-grid"></div>
|
||||
</section>
|
||||
@@ -1182,10 +1294,10 @@
|
||||
<script src="node_modules/astronomy-engine/astronomy.browser.min.js"></script>
|
||||
<script src="app/astro-calcs.js"></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/card-images.js?v=20260309-gate"></script>
|
||||
<script src="app/ui-tarot-lightbox.js?v=20260404-lightbox-pinch-01"></script>
|
||||
<script src="app/card-images.js?v=20260527-tarot-deck-gallery-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-relations.js"></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.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.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-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-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.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.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-elements.js?v=20260424-association-web-01"></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.js?v=20260424-association-web-01"></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.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-chassis.js?v=20260424-cube-fixes-01"></script>
|
||||
<script src="app/ui-cube-math.js"></script>
|
||||
@@ -1246,15 +1359,15 @@
|
||||
<script src="app/ui-numbers.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-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-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-visuals.js?v=20260307b"></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.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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user