update tarot frame settings UI

This commit is contained in:
2026-04-01 16:08:52 -07:00
parent a7d956cee8
commit efe5017740
7 changed files with 1216 additions and 67 deletions

10
app.js
View File

@@ -397,7 +397,15 @@ window.TarotSpreadUi?.init?.({
window.TarotFrameUi?.init?.({
ensureTarotSection,
getCards: () => window.TarotSectionUi?.getCards?.() || []
getCards: () => window.TarotSectionUi?.getCards?.() || [],
getHouseTopCardsVisible: () => window.TarotSectionUi?.getHouseTopCardsVisible?.() !== false,
getHouseTopInfoModes: () => window.TarotSectionUi?.getHouseTopInfoModes?.() || {},
getHouseBottomCardsVisible: () => window.TarotSectionUi?.getHouseBottomCardsVisible?.() !== false,
getHouseBottomInfoModes: () => window.TarotSectionUi?.getHouseBottomInfoModes?.() || {},
setHouseTopCardsVisible: (value) => window.TarotSectionUi?.setHouseTopCardsVisible?.(value),
setHouseTopInfoMode: (mode, value) => window.TarotSectionUi?.setHouseTopInfoMode?.(mode, value),
setHouseBottomCardsVisible: (value) => window.TarotSectionUi?.setHouseBottomCardsVisible?.(value),
setHouseBottomInfoMode: (mode, value) => window.TarotSectionUi?.setHouseBottomInfoMode?.(mode, value)
});
sectionStateUi.init?.({

View File

@@ -903,6 +903,57 @@
flex-wrap: wrap;
}
.tarot-frame-layout-panel {
position: absolute;
top: calc(100% + 10px);
right: 0;
z-index: 26;
min-width: 270px;
display: grid;
gap: 8px;
padding: 12px;
border: 1px solid #312e81;
border-radius: 16px;
background:
radial-gradient(circle at top, rgba(99, 102, 241, 0.16), transparent 42%),
linear-gradient(180deg, rgba(22, 22, 34, 0.98), rgba(10, 10, 18, 0.98));
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.3);
}
.tarot-frame-layout-panel[hidden] {
display: none !important;
}
.tarot-frame-layout-option {
display: grid;
gap: 4px;
padding: 10px 12px;
border: 1px solid rgba(99, 102, 241, 0.28);
border-radius: 12px;
background: rgba(15, 23, 42, 0.5);
color: #cbd5e1;
text-align: left;
cursor: pointer;
}
.tarot-frame-layout-option:hover,
.tarot-frame-layout-option.is-active {
border-color: rgba(129, 140, 248, 0.85);
background: rgba(49, 46, 129, 0.44);
color: #f8fafc;
}
.tarot-frame-layout-option strong {
font-size: 13px;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.tarot-frame-layout-option span {
font-size: 12px;
line-height: 1.35;
}
.tarot-frame-action-btn {
padding: 10px 14px;
border: 1px solid #4c1d95;
@@ -937,6 +988,67 @@
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.3);
}
.tarot-frame-settings-group {
display: grid;
gap: 10px;
padding-top: 2px;
border-top: 1px solid rgba(99, 102, 241, 0.18);
}
.tarot-frame-settings-group[hidden] {
display: none !important;
}
.tarot-frame-settings-heading {
color: #f8fafc;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.tarot-frame-settings-subheading {
color: #cbd5e1;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.tarot-frame-settings-note {
color: #94a3b8;
font-size: 11px;
line-height: 1.45;
}
.tarot-frame-checkbox-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.tarot-frame-check {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
padding: 8px 9px;
border: 1px solid rgba(99, 102, 241, 0.24);
border-radius: 10px;
background: rgba(15, 23, 42, 0.4);
color: #dbe4f0;
font-size: 12px;
line-height: 1.2;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.tarot-frame-check input {
margin: 0;
accent-color: #818cf8;
}
.tarot-frame-settings-panel[hidden] {
display: none !important;
}
@@ -963,6 +1075,8 @@
}
.tarot-frame-toggle input:disabled,
.tarot-frame-check input:disabled,
.tarot-frame-layout-option:disabled,
.tarot-frame-export-btn:disabled,
.tarot-frame-settings-toggle:disabled {
cursor: wait;
@@ -1171,6 +1285,54 @@
linear-gradient(180deg, #1e1b4b 0%, #0f172a 100%);
}
.tarot-frame-card-text-face {
width: 100%;
height: 100%;
display: grid;
align-content: center;
justify-items: center;
gap: 4px;
padding: 6px 5px;
box-sizing: border-box;
color: #fafafa;
text-align: center;
background:
radial-gradient(circle at top, rgba(99, 102, 241, 0.16), transparent 55%),
linear-gradient(180deg, rgba(30, 41, 59, 0.94), rgba(15, 23, 42, 0.94));
pointer-events: none;
-webkit-user-select: none;
user-select: none;
}
.tarot-frame-card-text-face.is-dense {
gap: 3px;
padding: 5px 4px;
}
.tarot-frame-card-text-face.is-top-hebrew .tarot-frame-card-text-primary {
font-size: clamp(11px, 1vw, 16px);
line-height: 1;
font-family: "Noto Sans Hebrew", "Segoe UI Symbol", sans-serif;
}
.tarot-frame-card-text-primary {
display: block;
font-size: clamp(7px, 0.78vw, 10px);
line-height: 1.15;
font-weight: 700;
letter-spacing: 0.02em;
overflow-wrap: anywhere;
}
.tarot-frame-card-text-secondary {
display: block;
color: rgba(250, 250, 250, 0.76);
font-size: clamp(6px, 0.7vw, 8px);
line-height: 1.2;
letter-spacing: 0.02em;
overflow-wrap: anywhere;
}
.tarot-frame-card-badge {
position: absolute;
left: 4px;
@@ -1262,6 +1424,11 @@
right: auto;
}
.tarot-frame-layout-panel {
left: 0;
right: auto;
}
.tarot-frame-panel {
padding: 14px;
}
@@ -1274,6 +1441,23 @@
font-size: 7px;
padding: 3px 4px;
}
.tarot-frame-card-text-face {
gap: 2px;
padding: 4px 3px;
}
.tarot-frame-card-text-face.is-top-hebrew .tarot-frame-card-text-primary {
font-size: 10px;
}
.tarot-frame-card-text-primary {
font-size: 6px;
}
.tarot-frame-card-text-secondary {
font-size: 5px;
}
}
.tarot-house-card-head {

View File

@@ -43,6 +43,40 @@
throw new Error("Tarot database assembly dependencies are incomplete");
}
function buildTokenDateRange(startToken, endToken) {
const parseToken = (value) => {
const match = String(value || "").trim().match(/^(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
const month = Number(match[1]);
const day = Number(match[2]);
if (!Number.isFinite(month) || !Number.isFinite(day)) {
return null;
}
return new Date(2025, month - 1, day);
};
const start = parseToken(startToken);
const endBase = parseToken(endToken);
if (!start || !endBase) {
return null;
}
const wraps = endBase.getTime() < start.getTime();
const end = wraps ? new Date(2026, endBase.getMonth(), endBase.getDate()) : endBase;
return {
start,
end,
startToken: startToken || null,
endToken: endToken || null,
label: `${formatMonthDayLabel(start)}${formatMonthDayLabel(end)}`
};
}
function buildMajorCards(referenceData, magickDataset) {
const tarotDb = getTarotDbConfig(referenceData);
const dynamicRelations = buildMajorDynamicRelations(referenceData);
@@ -178,6 +212,10 @@
const windowDecans = windowDecanIds
.map((decanId) => decanById.get(decanId) || null)
.filter(Boolean);
const explicitWindowRange = buildTokenDateRange(
tarotDb.courtDateRanges?.[cardName]?.start,
tarotDb.courtDateRanges?.[cardName]?.end
);
const dynamicRelations = [];
const monthKeys = new Set();
@@ -255,9 +293,17 @@
if (windowDecans.length) {
const firstRange = windowDecans[0].dateRange;
const lastRange = windowDecans[windowDecans.length - 1].dateRange;
const windowLabel = firstRange && lastRange
? `${formatMonthDayLabel(firstRange.start)}${formatMonthDayLabel(lastRange.end)}`
: "--";
const fallbackWindowRange = firstRange && lastRange
? {
start: firstRange.start,
end: lastRange.end,
startToken: firstRange.startToken,
endToken: lastRange.endToken,
label: `${formatMonthDayLabel(firstRange.start)}${formatMonthDayLabel(lastRange.end)}`
}
: null;
const windowRange = explicitWindowRange || fallbackWindowRange;
const windowLabel = windowRange?.label || "--";
dynamicRelations.unshift(
createRelation(
@@ -265,8 +311,8 @@
`${rankKey}-${suitKey}`,
`Court date window: ${windowLabel}`,
{
dateStart: firstRange?.startToken || null,
dateEnd: lastRange?.endToken || null,
dateStart: windowRange?.startToken || null,
dateEnd: windowRange?.endToken || null,
dateRange: windowLabel,
decanIds: windowDecanIds
}

View File

@@ -29,7 +29,8 @@
suitInfo: hasDb && db.suitInfo && typeof db.suitInfo === "object" ? db.suitInfo : suitInfo,
rankInfo: hasDb && db.rankInfo && typeof db.rankInfo === "object" ? db.rankInfo : rankInfo,
courtInfo: hasDb && db.courtInfo && typeof db.courtInfo === "object" ? db.courtInfo : courtInfo,
courtDecanWindows: hasDb && db.courtDecanWindows && typeof db.courtDecanWindows === "object" ? db.courtDecanWindows : courtDecanWindows
courtDecanWindows: hasDb && db.courtDecanWindows && typeof db.courtDecanWindows === "object" ? db.courtDecanWindows : courtDecanWindows,
courtDateRanges: hasDb && db.courtDateRanges && typeof db.courtDateRanges === "object" ? db.courtDateRanges : {}
};
}
@@ -178,7 +179,36 @@
return { start, end };
}
function buildDecanDateRange(sign, decanIndex) {
function buildTokenDateRange(startToken, endToken) {
const start = monthDayToDate(startToken, 2025);
const endBase = monthDayToDate(endToken, 2025);
if (!start || !endBase) {
return null;
}
const wraps = endBase.getTime() < start.getTime();
const end = wraps ? monthDayToDate(endToken, 2026) : endBase;
if (!end) {
return null;
}
return {
start,
end,
startMonth: start.getMonth() + 1,
endMonth: end.getMonth() + 1,
startToken: formatMonthDayToken(start),
endToken: formatMonthDayToken(end),
label: `${formatMonthDayLabel(start)}${formatMonthDayLabel(end)}`
};
}
function buildDecanDateRange(sign, decanIndex, decan = null) {
const explicitRange = buildTokenDateRange(decan?.dateStart, decan?.dateEnd);
if (explicitRange) {
return explicitRange;
}
const bounds = getSignDateBounds(sign);
if (!bounds || !Number.isFinite(Number(decanIndex))) {
return null;
@@ -238,7 +268,7 @@
const startDegree = (index - 1) * 10;
const endDegree = startDegree + 10;
const dateRange = buildDecanDateRange(sign, index);
const dateRange = buildDecanDateRange(sign, index, decan);
return {
decan,

File diff suppressed because it is too large Load Diff

View File

@@ -594,6 +594,46 @@
}
}
function refreshHouseUi() {
if (!state.initialized) {
return;
}
const elements = getElements();
renderHouseOfCards(elements);
syncHouseControls(elements);
}
function setHouseTopCardsVisible(value) {
state.houseTopCardsVisible = Boolean(value);
refreshHouseUi();
}
function setHouseTopInfoMode(mode, value) {
const key = String(mode || "").trim();
if (!key || !Object.prototype.hasOwnProperty.call(state.houseTopInfoModes, key)) {
return;
}
state.houseTopInfoModes[key] = Boolean(value);
refreshHouseUi();
}
function setHouseBottomCardsVisible(value) {
state.houseBottomCardsVisible = Boolean(value);
refreshHouseUi();
}
function setHouseBottomInfoMode(mode, value) {
const key = String(mode || "").trim();
if (!key || !Object.prototype.hasOwnProperty.call(state.houseBottomInfoModes, key)) {
return;
}
state.houseBottomInfoModes[key] = Boolean(value);
refreshHouseUi();
}
async function exportHouseOfCards(elements, format = "png") {
if (state.houseExportInProgress) {
return;
@@ -1076,6 +1116,14 @@
ensureTarotSection,
selectCardByTrump,
selectCardByName,
getCards: () => state.cards
getCards: () => state.cards,
getHouseTopCardsVisible: () => state.houseTopCardsVisible,
getHouseTopInfoModes: () => ({ ...state.houseTopInfoModes }),
getHouseBottomCardsVisible: () => state.houseBottomCardsVisible,
getHouseBottomInfoModes: () => ({ ...state.houseBottomInfoModes }),
setHouseTopCardsVisible,
setHouseTopInfoMode,
setHouseBottomCardsVisible,
setHouseBottomInfoMode
};
})();

View File

@@ -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=20260401-tarot-frame-09">
<link rel="stylesheet" href="app/styles.css?v=20260401-tarot-frame-12">
</head>
<body>
<div class="topbar">
@@ -75,7 +75,6 @@
<button id="open-tarot-cards" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Cards</button>
<button id="open-tarot-spread" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Draw Spread</button>
<button id="open-tarot-frame" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">Frame</button>
<button id="open-tarot-house" class="settings-trigger topbar-sub-trigger" type="button" role="menuitem">House</button>
</div>
</div>
</div>
@@ -314,16 +313,55 @@
<div class="tarot-frame-header">
<div>
<h2 class="tarot-frame-title">Tarot Frame</h2>
<p class="tarot-frame-copy">Arrange all 78 tarot cards inside one master 18x18 grid. The extra cards sit across the top row, and every square stays available for custom layouts.</p>
<p class="tarot-frame-copy">Arrange all 78 tarot cards inside one master 18x18 grid, then switch between the Frames and House of Cards presets without leaving the page.</p>
</div>
<div class="tarot-frame-actions">
<button id="tarot-frame-settings-toggle" class="tarot-frame-action-btn tarot-frame-settings-toggle" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="tarot-frame-settings-panel">Settings</button>
<button id="tarot-frame-reset" class="tarot-frame-action-btn" type="button">Reset Layout</button>
<button id="tarot-frame-layout-toggle" class="tarot-frame-action-btn" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="tarot-frame-layout-panel">Layout: Frames</button>
<div id="tarot-frame-layout-panel" class="tarot-frame-layout-panel" role="menu" aria-label="Tarot Frame layouts" hidden>
<button class="tarot-frame-layout-option" type="button" data-layout-preset-id="frames" role="menuitemradio" aria-checked="true">
<strong>Frames</strong>
<span>The current master frame with top-row extras and nested chronological rings.</span>
</button>
<button class="tarot-frame-layout-option" type="button" data-layout-preset-id="house" role="menuitemradio" aria-checked="false">
<strong>House of Cards</strong>
<span>The legacy house composition rebuilt inside the 18x18 snap grid.</span>
</button>
</div>
<div id="tarot-frame-settings-panel" class="tarot-frame-settings-panel" role="dialog" aria-label="Tarot Frame settings" hidden>
<label class="tarot-frame-toggle" for="tarot-frame-show-info">
<input id="tarot-frame-show-info" type="checkbox" checked>
<span>Display Info</span>
</label>
<div id="tarot-frame-house-settings" class="tarot-frame-settings-group" hidden>
<div class="tarot-frame-settings-heading">House Layout Settings</div>
<div class="tarot-frame-settings-note">These controls mirror the old House of Cards view and only apply while that layout is active.</div>
<label class="tarot-frame-toggle" for="tarot-frame-house-top-cards-visible">
<input id="tarot-frame-house-top-cards-visible" type="checkbox" checked>
<span>Show top card images</span>
</label>
<div class="tarot-frame-settings-subheading">Top Info</div>
<div class="tarot-frame-checkbox-grid">
<label class="tarot-frame-check" for="tarot-frame-house-top-info-hebrew"><input id="tarot-frame-house-top-info-hebrew" type="checkbox" checked><span>Hebrew</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-top-info-planet"><input id="tarot-frame-house-top-info-planet" type="checkbox" checked><span>Planet</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-top-info-zodiac"><input id="tarot-frame-house-top-info-zodiac" type="checkbox" checked><span>Zodiac</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-top-info-trump"><input id="tarot-frame-house-top-info-trump" type="checkbox" checked><span>Trump</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-top-info-path"><input id="tarot-frame-house-top-info-path" type="checkbox" checked><span>Path</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-top-info-date"><input id="tarot-frame-house-top-info-date" type="checkbox"><span>Date</span></label>
</div>
<label class="tarot-frame-toggle" for="tarot-frame-house-bottom-cards-visible">
<input id="tarot-frame-house-bottom-cards-visible" type="checkbox" checked>
<span>Show lower card images</span>
</label>
<div class="tarot-frame-settings-subheading">Lower Info</div>
<div class="tarot-frame-checkbox-grid">
<label class="tarot-frame-check" for="tarot-frame-house-bottom-info-zodiac"><input id="tarot-frame-house-bottom-info-zodiac" type="checkbox" checked><span>Zodiac</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-bottom-info-decan"><input id="tarot-frame-house-bottom-info-decan" type="checkbox" checked><span>Decan</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-bottom-info-month"><input id="tarot-frame-house-bottom-info-month" type="checkbox" checked><span>Month</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-bottom-info-ruler"><input id="tarot-frame-house-bottom-info-ruler" type="checkbox" checked><span>Ruler</span></label>
<label class="tarot-frame-check" for="tarot-frame-house-bottom-info-date"><input id="tarot-frame-house-bottom-info-date" type="checkbox"><span>Date</span></label>
</div>
</div>
<button id="tarot-frame-export-webp" class="tarot-frame-action-btn tarot-frame-export-btn" type="button">Export WebP</button>
</div>
</div>
@@ -1076,9 +1114,9 @@
<script src="app/ui-now-helpers.js?v=20260314-now-planets-grid-01"></script>
<script src="app/ui-now.js?v=20260314-now-planets-grid-01"></script>
<script src="app/ui-natal.js"></script>
<script src="app/tarot-database-builders.js"></script>
<script src="app/tarot-database-assembly.js"></script>
<script src="app/tarot-database.js"></script>
<script src="app/tarot-database-builders.js?v=20260401-tarot-dates-01"></script>
<script src="app/tarot-database-assembly.js?v=20260401-tarot-dates-01"></script>
<script src="app/tarot-database.js?v=20260401-tarot-dates-01"></script>
<script src="app/ui-calendar-dates.js"></script>
<script src="app/ui-calendar-detail-panels.js"></script>
<script src="app/ui-calendar-detail.js"></script>
@@ -1090,7 +1128,7 @@
<script src="app/ui-tarot-card-derivations.js?v=20260307b"></script>
<script src="app/ui-tarot-detail.js?v=20260307b"></script>
<script src="app/ui-tarot-relation-display.js?v=20260307b"></script>
<script src="app/ui-tarot.js?v=20260401-house-top-date-01"></script>
<script src="app/ui-tarot.js?v=20260401-house-top-date-02"></script>
<script src="app/ui-planets-references.js"></script>
<script src="app/ui-planets.js"></script>
<script src="app/ui-cycles.js"></script>
@@ -1130,7 +1168,7 @@
<script src="app/ui-numbers-detail.js"></script>
<script src="app/ui-numbers.js"></script>
<script src="app/ui-tarot-spread.js"></script>
<script src="app/ui-tarot-frame.js?v=20260401-tarot-frame-09"></script>
<script src="app/ui-tarot-frame.js?v=20260401-tarot-frame-11"></script>
<script src="app/ui-settings.js?v=20260309-gate"></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>
@@ -1139,7 +1177,7 @@
<script src="app/ui-home-calendar.js"></script>
<script src="app/ui-section-state.js?v=20260401-tarot-frame-01"></script>
<script src="app/app-runtime.js?v=20260309-gate"></script>
<script src="app.js?v=20260401-tarot-frame-01"></script>
<script src="app.js?v=20260401-tarot-frame-02"></script>
<script src="app/navigation-detail-test-harness.js?v=20260401-universal-detail-02"></script>
</body>
</html>