diff --git a/app/styles.css b/app/styles.css index 57f3941..0f5ef0f 100644 --- a/app/styles.css +++ b/app/styles.css @@ -873,9 +873,20 @@ justify-items: start; } .iching-tarot-text { - white-space: pre-line; + white-space: normal; line-height: 1.4; font-size: 12px; + display: grid; + gap: 10px; + } + .iching-tarot-group { + display: grid; + gap: 6px; + } + .iching-tarot-group-title { + color: #a1a1aa; + font-size: 12px; + font-weight: 600; } /* ── Kabbalah sections ──────────────────────────────────────────────── */ diff --git a/app/ui-iching.js b/app/ui-iching.js index 77b95e1..8fbf5bc 100644 --- a/app/ui-iching.js +++ b/app/ui-iching.js @@ -315,6 +315,8 @@ return; } + clearChildren(elements.ichingDetailTarotEl); + const upperKey = normalizeSearchValue(entry?.upperTrigram); const lowerKey = normalizeSearchValue(entry?.lowerTrigram); const upperCards = upperKey ? state.tarotByTrigramName[upperKey] || [] : []; @@ -325,15 +327,79 @@ const upperLabel = upperTrigram?.element || entry?.upperTrigram || "--"; const lowerLabel = lowerTrigram?.element || entry?.lowerTrigram || "--"; - const lines = []; - if (upperKey) { - lines.push(`Upper (${upperLabel}): ${upperCards.length ? upperCards.join(", ") : "--"}`); - } - if (lowerKey) { - lines.push(`Lower (${lowerLabel}): ${lowerCards.length ? lowerCards.join(", ") : "--"}`); + function buildTarotTarget(cardName) { + const label = String(cardName || "").trim(); + if (!label) { + return null; + } + + const trumpMatch = label.match(/^key\s*(\d{1,2})\s*:/i); + if (trumpMatch) { + return { + label, + detail: { trumpNumber: Number(trumpMatch[1]) } + }; + } + + return { + label, + detail: { cardName: label } + }; } - elements.ichingDetailTarotEl.textContent = lines.length ? lines.join("\n\n") : "--"; + function appendTarotGroup(groupLabel, trigramLabel, cards) { + const group = document.createElement("div"); + group.className = "iching-tarot-group"; + + const title = document.createElement("div"); + title.className = "iching-tarot-group-title"; + title.textContent = `${groupLabel} (${trigramLabel}):`; + group.appendChild(title); + + if (!cards.length) { + const empty = document.createElement("div"); + empty.className = "planet-text"; + empty.textContent = "--"; + group.appendChild(empty); + elements.ichingDetailTarotEl.appendChild(group); + return; + } + + const buttonRow = document.createElement("div"); + buttonRow.className = "alpha-nav-btns"; + + cards.forEach((cardName) => { + const target = buildTarotTarget(cardName); + if (!target) { + return; + } + + const button = document.createElement("button"); + button.type = "button"; + button.className = "alpha-nav-btn"; + button.textContent = `${target.label} ↗`; + button.addEventListener("click", () => { + document.dispatchEvent(new CustomEvent("nav:tarot-trump", { + detail: target.detail + })); + }); + buttonRow.appendChild(button); + }); + + group.appendChild(buttonRow); + elements.ichingDetailTarotEl.appendChild(group); + } + + if (upperKey) { + appendTarotGroup("Upper", upperLabel, upperCards); + } + if (lowerKey) { + appendTarotGroup("Lower", lowerLabel, lowerCards); + } + + if (!elements.ichingDetailTarotEl.childElementCount) { + elements.ichingDetailTarotEl.textContent = "--"; + } } function renderCalendarMonths(entry, elements) { diff --git a/app/ui-tarot-detail.js b/app/ui-tarot-detail.js index cc27e03..e3c7690 100644 --- a/app/ui-tarot-detail.js +++ b/app/ui-tarot-detail.js @@ -13,6 +13,7 @@ buildSmallCardRulershipRelation, buildSmallCardCourtLinkRelations, buildCubeRelationsForCard, + buildIChingRelationsForCard, parseMonthDayToken, createRelationListItem, findSephirahForMinorCard @@ -131,6 +132,7 @@ const smallCardCourtLinkRelations = buildSmallCardCourtLinkRelations(card, dedupedRelations); const mergedCourtDateRelations = [...courtDateRelations, ...smallCardCourtLinkRelations]; const cubeRelations = buildCubeRelationsForCard(card); + const iChingRelations = buildIChingRelationsForCard(card); const monthRelations = (getMonthRefsByCardId().get(card.id) || []).map((month, index) => { const dateRange = String(month?.dateRange || "").trim(); const context = String(month?.context || "").trim(); @@ -229,6 +231,7 @@ renderStaticRelationGroup(elements.tarotDetailCourtDateEl, elements.tarotMetaCourtDateCardEl, mergedCourtDateRelations); renderStaticRelationGroup(elements.tarotDetailHebrewEl, elements.tarotMetaHebrewCardEl, hebrewRelations); renderStaticRelationGroup(elements.tarotDetailCubeEl, elements.tarotMetaCubeCardEl, cubeRelations); + renderStaticRelationGroup(elements.tarotDetailIChingEl, elements.tarotMetaIChingCardEl, iChingRelations); renderStaticRelationGroup(elements.tarotDetailCalendarEl, elements.tarotMetaCalendarCardEl, mergedMonthRelations); const kabPathEl = elements.tarotKabPathEl; diff --git a/app/ui-tarot-relation-display.js b/app/ui-tarot-relation-display.js index 934b895..7f582ce 100644 --- a/app/ui-tarot-relation-display.js +++ b/app/ui-tarot-relation-display.js @@ -190,6 +190,17 @@ label: `Open ${d.name || monthId} in Calendar` }; } + if (t === "ichingHexagram") { + const hexagramNumber = Number(d.hexagramNumber || relation?.id); + if (!Number.isFinite(hexagramNumber)) { + return null; + } + return { + event: "nav:iching", + detail: { hexagramNumber }, + label: `Open Hexagram ${hexagramNumber} in I Ching` + }; + } if (t === "cubeFace") { const wallId = d.wallId || relation?.id; if (!wallId) { diff --git a/app/ui-tarot-relations.js b/app/ui-tarot-relations.js index 62a4b09..3367733 100644 --- a/app/ui-tarot-relations.js +++ b/app/ui-tarot-relations.js @@ -102,6 +102,8 @@ return String(value || "") .trim() .toLowerCase() + .replace(/^key\s+\d+\s*:\s*/g, "") + .replace(/\b(pentacles?|coins?)\b/g, "disks") .replace(/\s+/g, " "); } @@ -114,6 +116,14 @@ } function resolveTarotTrumpNumber(cardName) { + const rawKey = String(cardName || "") + .trim() + .toLowerCase(); + const keyMatch = rawKey.match(/\bkey\s*(\d{1,2})\b/); + if (keyMatch) { + return Number(keyMatch[1]); + } + const key = normalizeTarotName(cardName); if (!key) { return null; @@ -724,11 +734,86 @@ ]; } + function buildIChingRelationsForCard(card, referenceData) { + const iChing = referenceData?.iChing; + const hexagrams = Array.isArray(iChing?.hexagrams) ? iChing.hexagrams : []; + const trigrams = Array.isArray(iChing?.trigrams) ? iChing.trigrams : []; + const correspondences = Array.isArray(iChing?.correspondences?.tarotToTrigram) + ? iChing.correspondences.tarotToTrigram + : []; + + if (!card || !hexagrams.length || !correspondences.length) { + return []; + } + + const trigramByKey = new Map( + trigrams + .map((trigram) => [normalizeRelationId(trigram?.name), trigram]) + .filter(([key]) => Boolean(key)) + ); + + const matchedTrigramKeys = [...new Set( + correspondences + .filter((row) => cardMatchesTarotAssociation(card, row?.tarot)) + .map((row) => normalizeRelationId(row?.trigram)) + .filter(Boolean) + )]; + + if (!matchedTrigramKeys.length) { + return []; + } + + const relations = []; + + hexagrams.forEach((hexagram) => { + const positionsByTrigram = new Map(); + const upperKey = normalizeRelationId(hexagram?.upperTrigram); + const lowerKey = normalizeRelationId(hexagram?.lowerTrigram); + + if (matchedTrigramKeys.includes(upperKey)) { + positionsByTrigram.set(upperKey, ["upper"]); + } + if (matchedTrigramKeys.includes(lowerKey)) { + const existing = positionsByTrigram.get(lowerKey) || []; + positionsByTrigram.set(lowerKey, [...existing, "lower"]); + } + + positionsByTrigram.forEach((positions, trigramKey) => { + const trigram = trigramByKey.get(trigramKey) || null; + const sideLabel = positions.length === 2 + ? "Upper + Lower" + : positions[0] === "upper" + ? "Upper" + : "Lower"; + const trigramLabel = trigram?.element || trigram?.name || hexagram?.upperTrigram || hexagram?.lowerTrigram || "Trigram"; + + relations.push({ + type: "ichingHexagram", + id: String(hexagram?.number || ""), + label: `Hexagram ${hexagram.number}: ${hexagram.name || "--"} · ${sideLabel} ${trigramLabel}`, + data: { + hexagramNumber: Number(hexagram?.number), + name: hexagram?.name || "", + upperTrigram: hexagram?.upperTrigram || "", + lowerTrigram: hexagram?.lowerTrigram || "", + trigramName: trigram?.name || "", + trigramElement: trigram?.element || "", + positions: positions.join(",") + }, + __key: `ichingHexagram|${hexagram?.number}|${trigramKey}|${positions.join(",")}` + }); + }); + }); + + return relations.sort((left, right) => Number(left?.data?.hexagramNumber || 999) - Number(right?.data?.hexagramNumber || 999)); + } + window.TarotRelationsUi = { buildCourtCardByDecanId, buildSmallCardCourtLinkRelations, buildMonthReferencesByCard, buildCubeRelationsForCard, + buildIChingRelationsForCard, parseMonthDayToken }; })(); \ No newline at end of file diff --git a/app/ui-tarot.js b/app/ui-tarot.js index cf754e5..0e35411 100644 --- a/app/ui-tarot.js +++ b/app/ui-tarot.js @@ -236,6 +236,7 @@ tarotMetaCourtDateCardEl: document.getElementById("tarot-meta-courtdate-card"), tarotMetaHebrewCardEl: document.getElementById("tarot-meta-hebrew-card"), tarotMetaCubeCardEl: document.getElementById("tarot-meta-cube-card"), + tarotMetaIChingCardEl: document.getElementById("tarot-meta-iching-card"), tarotMetaCalendarCardEl: document.getElementById("tarot-meta-calendar-card"), tarotDetailPlanetEl: document.getElementById("tarot-detail-planet"), tarotDetailElementEl: document.getElementById("tarot-detail-element"), @@ -244,6 +245,7 @@ tarotDetailCourtDateEl: document.getElementById("tarot-detail-courtdate"), tarotDetailHebrewEl: document.getElementById("tarot-detail-hebrew"), tarotDetailCubeEl: document.getElementById("tarot-detail-cube"), + tarotDetailIChingEl: document.getElementById("tarot-detail-iching"), tarotDetailCalendarEl: document.getElementById("tarot-detail-calendar"), tarotKabPathEl: document.getElementById("tarot-kab-path"), tarotHouseOfCardsEl: document.getElementById("tarot-house-of-cards") @@ -303,6 +305,7 @@ buildSmallCardRulershipRelation, buildSmallCardCourtLinkRelations, buildCubeRelationsForCard, + buildIChingRelationsForCard, parseMonthDayToken, createRelationListItem, findSephirahForMinorCard @@ -513,6 +516,13 @@ return tarotRelationsUi.buildCubeRelationsForCard(card, state.magickDataset); } + function buildIChingRelationsForCard(card) { + if (typeof tarotRelationsUi.buildIChingRelationsForCard !== "function") { + return []; + } + return tarotRelationsUi.buildIChingRelationsForCard(card, state.referenceData); + } + // Returns nav dispatch config for relations that have a corresponding section, // null for informational-only relations. function getRelationNavTarget(relation) { diff --git a/index.html b/index.html index 5283302..4b0c624 100644 --- a/index.html +++ b/index.html @@ -244,6 +244,10 @@ Cube of Space +
Tarot Correspondences -

--

+
--
Calendar Months