bugfix and phone view optimizations

This commit is contained in:
2026-03-12 02:35:02 -07:00
parent d3d96912c1
commit c6b21095d9
5 changed files with 561 additions and 21 deletions

View File

@@ -42,6 +42,22 @@
.topbar-home-button[aria-pressed="true"] { .topbar-home-button[aria-pressed="true"] {
color: #fbbf24; color: #fbbf24;
} }
.topbar-menu-toggle {
display: none;
padding: 7px 12px;
border-radius: 999px;
border: 1px solid #3f3f46;
background: #27272a;
color: #f4f4f5;
cursor: pointer;
font-size: 13px;
font-weight: 600;
line-height: 1;
flex: 0 0 auto;
}
.topbar-menu-toggle:hover {
background: #3f3f46;
}
.topbar-actions { .topbar-actions {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -113,6 +129,104 @@
.settings-trigger[aria-pressed="true"] { .settings-trigger[aria-pressed="true"] {
background: #3f3f46; background: #3f3f46;
} }
@media (max-width: 900px) {
.topbar {
position: sticky;
top: 0;
padding: 10px 12px;
gap: 8px;
align-items: center;
flex-wrap: wrap;
background: rgba(24, 24, 27, 0.96);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px);
}
.topbar-home-button {
font-size: 17px;
min-height: 38px;
}
.topbar-menu-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 38px;
margin-left: auto;
}
.topbar-actions {
display: none;
flex: 1 0 100%;
width: 100%;
padding: 12px;
margin: 4px 0 0;
border: 1px solid #2f2f39;
border-radius: 16px;
background: linear-gradient(180deg, rgba(24, 24, 34, 0.98), rgba(12, 12, 18, 0.98));
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.34);
overflow: visible;
pointer-events: auto;
gap: 10px;
max-height: calc(100svh - 88px);
overflow-y: auto;
}
.topbar.is-menu-open .topbar-actions {
display: grid;
grid-template-columns: 1fr;
}
.topbar-actions > * {
width: 100%;
}
.topbar-dropdown {
display: grid;
width: 100%;
}
.topbar-dropdown-menu {
position: static;
min-width: 0;
width: 100%;
margin-top: 6px;
padding: 6px;
border-radius: 12px;
border-color: #31313d;
background: rgba(10, 10, 16, 0.92);
box-shadow: none;
}
.topbar-dropdown:hover .topbar-dropdown-menu,
.topbar-dropdown:focus-within .topbar-dropdown-menu {
display: none;
}
.topbar-dropdown.is-open .topbar-dropdown-menu {
display: grid;
}
.settings-trigger {
width: 100%;
min-height: 42px;
display: flex;
align-items: center;
justify-content: flex-start;
text-align: left;
padding: 10px 12px;
}
.topbar-sub-trigger {
min-height: 40px;
font-size: 13px;
}
}
@media (max-width: 640px) {
.topbar {
padding: 9px 10px;
}
.topbar-home-button {
font-size: 16px;
}
.topbar-actions {
padding: 10px;
border-radius: 14px;
max-height: calc(100svh - 78px);
}
.settings-trigger {
font-size: 13px;
}
}
body.connection-gated { body.connection-gated {
overflow: hidden; overflow: hidden;
} }
@@ -475,6 +589,16 @@
border: 1px solid #3f3f46; border: 1px solid #3f3f46;
background: #09090b; background: #09090b;
} }
.tarot-detail-heading {
position: relative;
padding-right: 108px;
}
.tarot-detail-heading .detail-toggle-inline {
position: absolute;
top: 0;
right: 0;
margin-left: 0;
}
.tarot-detail-heading h2 { .tarot-detail-heading h2 {
margin: 0; margin: 0;
font-size: 24px; font-size: 24px;
@@ -1160,6 +1284,13 @@
.planet-detail-heading { .planet-detail-heading {
position: relative; position: relative;
z-index: 1; z-index: 1;
padding-right: 108px;
}
.planet-detail-heading .detail-toggle-inline {
position: absolute;
top: 0;
right: 0;
margin-left: 0;
} }
.planet-detail-heading h2 { .planet-detail-heading h2 {
margin: 0; margin: 0;
@@ -3368,10 +3499,18 @@
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.planet-detail-heading,
.tarot-detail-heading,
.alpha-text-detail-heading { .alpha-text-detail-heading {
padding-right: 0; padding-right: 0;
} }
.planet-detail-heading,
.tarot-detail-heading {
display: grid;
gap: 10px;
}
.alpha-text-controls--heading { .alpha-text-controls--heading {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -3380,10 +3519,29 @@
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.planet-detail-heading .detail-toggle-inline,
.tarot-detail-heading .detail-toggle-inline,
.alpha-text-detail-heading .detail-toggle-inline { .alpha-text-detail-heading .detail-toggle-inline {
position: static; position: static;
justify-self: start; justify-self: start;
} }
.tarot-detail-top {
grid-template-columns: minmax(0, 1fr);
}
.tarot-detail-image {
width: min(180px, 60vw);
height: auto;
aspect-ratio: 2 / 3;
justify-self: center;
}
.tarot-meanings,
.tarot-meta-grid,
#kab-detail-body {
grid-template-columns: 1fr;
}
} }
.alpha-text-meta-grid { .alpha-text-meta-grid {
@@ -5244,9 +5402,61 @@
.now-stats-planets { .now-stats-planets {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
.planet-layout { .planet-layout,
.tarot-layout {
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
.kab-layout {
grid-template-rows: minmax(220px, 42svh) minmax(0, 1fr);
}
.planet-list-panel,
.tarot-list-panel {
border-right: none;
border-bottom: 1px solid #27272a;
max-height: min(42svh, 420px);
}
.planet-list-item,
.tarot-list-item {
min-height: 48px;
padding: 10px 12px;
}
.planet-card-list,
.tarot-card-list {
padding-bottom: 18px;
}
.planet-detail-panel,
.tarot-detail-panel,
.kab-detail-panel {
padding: 14px 14px calc(84px + env(safe-area-inset-bottom, 0px));
gap: 14px;
}
.sidebar-toggle-inline,
.detail-toggle-inline {
min-height: 38px;
padding: 8px 10px;
}
.sidebar-popout-open,
.detail-popout-open {
position: fixed;
top: auto;
bottom: calc(14px + env(safe-area-inset-bottom, 0px));
z-index: 35;
min-height: 42px;
padding: 10px 14px;
border-radius: 999px;
font-size: 13px;
line-height: 1.2;
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.36);
background: rgba(24, 24, 27, 0.96);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
.sidebar-popout-open {
right: 14px;
}
.detail-popout-open {
left: 14px;
}
.planet-meta-grid { .planet-meta-grid {
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }

View File

@@ -40,6 +40,7 @@
let detailNameEl; let detailNameEl;
let detailSubEl; let detailSubEl;
let detailBodyEl; let detailBodyEl;
let textLayoutEl;
let lexiconPopupEl; let lexiconPopupEl;
let lexiconPopupTitleEl; let lexiconPopupTitleEl;
let lexiconPopupSubtitleEl; let lexiconPopupSubtitleEl;
@@ -65,9 +66,21 @@
detailNameEl = document.getElementById("alpha-text-detail-name"); detailNameEl = document.getElementById("alpha-text-detail-name");
detailSubEl = document.getElementById("alpha-text-detail-sub"); detailSubEl = document.getElementById("alpha-text-detail-sub");
detailBodyEl = document.getElementById("alpha-text-detail-body"); detailBodyEl = document.getElementById("alpha-text-detail-body");
textLayoutEl = sourceListEl?.closest?.(".planet-layout") || detailBodyEl?.closest?.(".planet-layout") || null;
ensureLexiconPopup(); ensureLexiconPopup();
} }
function showDetailOnlyMode() {
if (!(textLayoutEl instanceof HTMLElement)) {
return;
}
window.TarotChromeUi?.initializeSidebarPopouts?.();
window.TarotChromeUi?.initializeDetailPopouts?.();
window.TarotChromeUi?.initializeSidebarAutoCollapse?.();
window.TarotChromeUi?.showDetailOnly?.(textLayoutEl);
}
function ensureLexiconPopup() { function ensureLexiconPopup() {
if (lexiconPopupEl instanceof HTMLElement) { if (lexiconPopupEl instanceof HTMLElement) {
return; return;
@@ -597,6 +610,7 @@
button.append(name, meta); button.append(name, meta);
button.addEventListener("click", () => { button.addEventListener("click", () => {
if (normalizeId(source.id) === normalizeId(state.selectedSourceId)) { if (normalizeId(source.id) === normalizeId(state.selectedSourceId)) {
showDetailOnlyMode();
return; return;
} }
@@ -607,6 +621,7 @@
syncSelectionForSource(getSelectedSource()); syncSelectionForSource(getSelectedSource());
renderSourceList(); renderSourceList();
renderSelectors(); renderSelectors();
showDetailOnlyMode();
if (state.searchQuery && state.activeSearchScope === "source") { if (state.searchQuery && state.activeSearchScope === "source") {
void Promise.all([loadSelectedPassage(), runSearch("source")]); void Promise.all([loadSelectedPassage(), runSearch("source")]);
@@ -1468,6 +1483,7 @@
syncSelectionForSource(getSelectedSource()); syncSelectionForSource(getSelectedSource());
renderSourceList(); renderSourceList();
renderSelectors(); renderSelectors();
showDetailOnlyMode();
await loadSelectedPassage(); await loadSelectedPassage();
clearActiveSearchUi({ preserveHighlight: true }); clearActiveSearchUi({ preserveHighlight: true });
renderDetail(); renderDetail();
@@ -1549,6 +1565,8 @@
async function ensureAlphabetTextSection() { async function ensureAlphabetTextSection() {
getElements(); getElements();
bindControls(); bindControls();
window.TarotChromeUi?.initializeSidebarPopouts?.();
window.TarotChromeUi?.initializeDetailPopouts?.();
if (!sourceListEl || !detailBodyEl) { if (!sourceListEl || !detailBodyEl) {
return; return;

View File

@@ -5,6 +5,40 @@
const DETAIL_COLLAPSE_STORAGE_PREFIX = "tarot-detail-collapsed:"; const DETAIL_COLLAPSE_STORAGE_PREFIX = "tarot-detail-collapsed:";
const DEFAULT_DATASET_ENTRY_COLLAPSED = true; const DEFAULT_DATASET_ENTRY_COLLAPSED = true;
const DEFAULT_DATASET_DETAIL_COLLAPSED = false; const DEFAULT_DATASET_DETAIL_COLLAPSED = false;
const MOBILE_TOPBAR_MEDIA_QUERY = "(max-width: 900px)";
const sidebarControllers = new WeakMap();
const detailControllers = new WeakMap();
const AUTO_COLLAPSE_ENTRY_SELECTOR = [
".planet-list-item",
".tarot-list-item",
"[role='option']",
".kab-node[data-sephira]",
".kab-path-hit[data-path]",
".kab-path-tarot[data-path]",
".kab-rose-petal[data-path]",
".cube-face[role='button']",
".cube-edge-line[role='button']",
".cube-direction[role='button']",
".cube-connector[role='button']",
".cube-center[role='button']",
".kab-chip[data-path]"
].join(", ");
const AUTO_COLLAPSE_IGNORE_SELECTOR = [
".sidebar-toggle-inline",
".sidebar-popout-open",
".detail-toggle-inline",
".detail-popout-open",
"input",
"select",
"textarea",
"label",
"form",
".dataset-search-wrap",
".alpha-text-search-controls",
".cube-rotation-controls",
".cube-rotation-btn",
".tarot-house-action-btn"
].join(", ");
function loadSidebarCollapsedState(storageKey) { function loadSidebarCollapsedState(storageKey) {
try { try {
@@ -29,6 +63,128 @@
} }
} }
function resolveLayoutTarget(target) {
if (target instanceof HTMLElement) {
if (target.matches(".planet-layout, .tarot-layout, .kab-layout")) {
return target;
}
return target.closest(".planet-layout, .tarot-layout, .kab-layout");
}
if (typeof target === "string" && target) {
const element = document.getElementById(target);
if (element instanceof HTMLElement) {
if (element.matches(".planet-layout, .tarot-layout, .kab-layout")) {
return element;
}
return element.querySelector(".planet-layout, .tarot-layout, .kab-layout")
|| element.closest(".planet-layout, .tarot-layout, .kab-layout");
}
}
return null;
}
function setSidebarCollapsed(target, collapsed, persist = true) {
const layout = resolveLayoutTarget(target);
const controller = layout ? sidebarControllers.get(layout) : null;
if (!controller) {
return false;
}
controller.applyCollapsedState(Boolean(collapsed), persist);
return true;
}
function setDetailCollapsed(target, collapsed, persist = true) {
const layout = resolveLayoutTarget(target);
const controller = layout ? detailControllers.get(layout) : null;
if (!controller) {
return false;
}
controller.applyCollapsedState(Boolean(collapsed), persist);
return true;
}
function showDetailOnly(target, persist = true) {
const layout = resolveLayoutTarget(target);
if (!layout) {
return false;
}
const detailChanged = setDetailCollapsed(layout, false, persist);
const sidebarChanged = setSidebarCollapsed(layout, true, persist);
return detailChanged || sidebarChanged;
}
function shouldAutoCollapseFromEvent(panel, target) {
if (!(panel instanceof HTMLElement) || !(target instanceof Element) || !panel.contains(target)) {
return false;
}
if (target.closest(AUTO_COLLAPSE_IGNORE_SELECTOR)) {
return false;
}
return Boolean(target.closest(AUTO_COLLAPSE_ENTRY_SELECTOR));
}
function scheduleAutoCollapse(layout) {
if (!(layout instanceof HTMLElement)) {
return;
}
window.requestAnimationFrame(() => {
showDetailOnly(layout);
});
}
function initializeSidebarAutoCollapse() {
const layouts = document.querySelectorAll(".planet-layout, .tarot-layout, .kab-layout");
layouts.forEach((layout) => {
if (!(layout instanceof HTMLElement)) {
return;
}
const panel = Array.from(layout.children).find((child) => (
child instanceof HTMLElement
&& child.matches("aside.planet-list-panel, aside.tarot-list-panel, aside.kab-tree-panel")
));
if (!(panel instanceof HTMLElement) || panel.dataset.sidebarAutoCollapseReady === "1") {
return;
}
panel.dataset.sidebarAutoCollapseReady = "1";
panel.addEventListener("click", (event) => {
const target = event.target instanceof Element ? event.target : null;
if (!shouldAutoCollapseFromEvent(panel, target)) {
return;
}
scheduleAutoCollapse(layout);
});
panel.addEventListener("keydown", (event) => {
if (event.key !== "Enter" && event.key !== " ") {
return;
}
const target = event.target instanceof Element ? event.target : null;
if (!shouldAutoCollapseFromEvent(panel, target)) {
return;
}
scheduleAutoCollapse(layout);
});
});
}
function initializeSidebarPopouts() { function initializeSidebarPopouts() {
const layouts = document.querySelectorAll(".planet-layout, .tarot-layout, .kab-layout"); const layouts = document.querySelectorAll(".planet-layout, .tarot-layout, .kab-layout");
@@ -87,6 +243,14 @@
} }
}; };
sidebarControllers.set(layout, {
applyCollapsedState,
panel,
collapseBtn,
openBtn,
storageKey
});
collapseBtn.addEventListener("click", () => { collapseBtn.addEventListener("click", () => {
applyCollapsedState(true); applyCollapsedState(true);
}); });
@@ -173,6 +337,14 @@
} }
}; };
detailControllers.set(layout, {
applyCollapsedState,
detailPanel,
collapseBtn,
openBtn,
detailStorageKey
});
collapseBtn.addEventListener("click", () => { collapseBtn.addEventListener("click", () => {
applyCollapsedState(true); applyCollapsedState(true);
}); });
@@ -202,6 +374,103 @@
} }
} }
function getTopbarElements() {
const topbarEl = document.querySelector(".topbar");
const actionsEl = document.getElementById("topbar-actions");
const menuToggleEl = document.getElementById("topbar-menu-toggle");
return {
topbarEl: topbarEl instanceof HTMLElement ? topbarEl : null,
actionsEl: actionsEl instanceof HTMLElement ? actionsEl : null,
menuToggleEl: menuToggleEl instanceof HTMLButtonElement ? menuToggleEl : null
};
}
function isMobileTopbarViewport() {
return window.matchMedia(MOBILE_TOPBAR_MEDIA_QUERY).matches;
}
function setTopbarMenuOpen(isOpen) {
const { topbarEl, menuToggleEl } = getTopbarElements();
if (!(topbarEl instanceof HTMLElement) || !(menuToggleEl instanceof HTMLButtonElement)) {
return;
}
const nextOpen = Boolean(isOpen);
topbarEl.classList.toggle("is-menu-open", nextOpen);
menuToggleEl.setAttribute("aria-expanded", nextOpen ? "true" : "false");
menuToggleEl.textContent = nextOpen ? "Close" : "Menu";
menuToggleEl.setAttribute("aria-label", nextOpen ? "Close navigation menu" : "Open navigation menu");
if (!nextOpen) {
closeTopbarDropdowns();
}
}
function bindTopbarMobileMenu() {
const { topbarEl, actionsEl, menuToggleEl } = getTopbarElements();
if (!(topbarEl instanceof HTMLElement) || !(actionsEl instanceof HTMLElement) || !(menuToggleEl instanceof HTMLButtonElement)) {
return;
}
if (menuToggleEl.dataset.mobileMenuReady === "1") {
return;
}
menuToggleEl.dataset.mobileMenuReady = "1";
setTopbarMenuOpen(false);
menuToggleEl.addEventListener("click", (event) => {
event.stopPropagation();
const nextOpen = !topbarEl.classList.contains("is-menu-open");
setTopbarMenuOpen(nextOpen);
});
actionsEl.addEventListener("click", (event) => {
const button = event.target instanceof Element
? event.target.closest("button")
: null;
if (!(button instanceof HTMLButtonElement)) {
return;
}
const isDropdownTrigger = button.getAttribute("aria-haspopup") === "menu";
const isMenuItem = button.getAttribute("role") === "menuitem";
if (!isDropdownTrigger || isMenuItem) {
window.requestAnimationFrame(() => {
if (isMobileTopbarViewport()) {
setTopbarMenuOpen(false);
}
});
}
});
document.addEventListener("click", (event) => {
const clickTarget = event.target;
if (!isMobileTopbarViewport()) {
return;
}
if (clickTarget instanceof Node && topbarEl.contains(clickTarget)) {
return;
}
setTopbarMenuOpen(false);
});
window.addEventListener("resize", () => {
if (!isMobileTopbarViewport()) {
setTopbarMenuOpen(false);
}
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
setTopbarMenuOpen(false);
}
});
}
function closeTopbarDropdowns(exceptEl = null) { function closeTopbarDropdowns(exceptEl = null) {
const topbarDropdownEls = Array.from(document.querySelectorAll(".topbar-dropdown")); const topbarDropdownEls = Array.from(document.querySelectorAll(".topbar-dropdown"));
topbarDropdownEls.forEach((dropdownEl) => { topbarDropdownEls.forEach((dropdownEl) => {
@@ -227,10 +496,16 @@
setTopbarDropdownOpen(dropdownEl, false); setTopbarDropdownOpen(dropdownEl, false);
dropdownEl.addEventListener("mouseenter", () => { dropdownEl.addEventListener("mouseenter", () => {
if (isMobileTopbarViewport()) {
return;
}
setTopbarDropdownOpen(dropdownEl, true); setTopbarDropdownOpen(dropdownEl, true);
}); });
dropdownEl.addEventListener("mouseleave", () => { dropdownEl.addEventListener("mouseleave", () => {
if (isMobileTopbarViewport()) {
return;
}
setTopbarDropdownOpen(dropdownEl, false); setTopbarDropdownOpen(dropdownEl, false);
}); });
@@ -275,6 +550,8 @@
function init() { function init() {
initializeSidebarPopouts(); initializeSidebarPopouts();
initializeDetailPopouts(); initializeDetailPopouts();
initializeSidebarAutoCollapse();
bindTopbarMobileMenu();
bindTopbarDropdownInteractions(); bindTopbarDropdownInteractions();
} }
@@ -283,6 +560,12 @@
init, init,
initializeSidebarPopouts, initializeSidebarPopouts,
initializeDetailPopouts, initializeDetailPopouts,
initializeSidebarAutoCollapse,
bindTopbarMobileMenu,
setSidebarCollapsed,
setDetailCollapsed,
showDetailOnly,
setTopbarMenuOpen,
setTopbarDropdownOpen, setTopbarDropdownOpen,
closeTopbarDropdowns, closeTopbarDropdowns,
bindTopbarDropdownInteractions bindTopbarDropdownInteractions

View File

@@ -369,8 +369,8 @@
if (lightboxState.deckCompareMode) { if (lightboxState.deckCompareMode) {
compareGridSlots.forEach((slot) => { compareGridSlots.forEach((slot) => {
if (slot?.imageEl) { if (slot?.zoomLayerEl) {
slot.imageEl.style.transformOrigin = nextOrigin; slot.zoomLayerEl.style.transformOrigin = nextOrigin;
} }
}); });
return; return;
@@ -392,11 +392,12 @@
if (lightboxState.deckCompareMode) { if (lightboxState.deckCompareMode) {
compareGridSlots.forEach((slot) => { compareGridSlots.forEach((slot) => {
if (!slot?.imageEl) { if (!slot?.imageEl || !slot?.zoomLayerEl) {
return; return;
} }
slot.imageEl.style.transform = `scale(${activeZoomScale}) ${buildRotationTransform(lightboxState.primaryRotated)}`; slot.zoomLayerEl.style.transform = `scale(${activeZoomScale})`;
slot.imageEl.style.transform = buildRotationTransform(lightboxState.primaryRotated);
}); });
applyTransformOrigins(); applyTransformOrigins();
@@ -734,6 +735,9 @@
slot.slotEl.style.display = "none"; slot.slotEl.style.display = "none";
slot.imageEl.removeAttribute("src"); slot.imageEl.removeAttribute("src");
slot.imageEl.alt = "Tarot compare image"; slot.imageEl.alt = "Tarot compare image";
if (slot.zoomLayerEl) {
slot.zoomLayerEl.style.display = "none";
}
slot.imageEl.style.display = "none"; slot.imageEl.style.display = "none";
slot.fallbackEl.style.display = "none"; slot.fallbackEl.style.display = "none";
}); });
@@ -749,6 +753,9 @@
if (!cardRequest) { if (!cardRequest) {
slot.slotEl.style.display = "none"; slot.slotEl.style.display = "none";
slot.imageEl.removeAttribute("src"); slot.imageEl.removeAttribute("src");
if (slot.zoomLayerEl) {
slot.zoomLayerEl.style.display = "none";
}
slot.imageEl.style.display = "none"; slot.imageEl.style.display = "none";
slot.fallbackEl.style.display = "none"; slot.fallbackEl.style.display = "none";
return; return;
@@ -761,11 +768,17 @@
if (cardRequest.src) { if (cardRequest.src) {
slot.imageEl.src = cardRequest.src; slot.imageEl.src = cardRequest.src;
slot.imageEl.alt = cardRequest.altText || cardRequest.label || "Tarot compare image"; slot.imageEl.alt = cardRequest.altText || cardRequest.label || "Tarot compare image";
if (slot.zoomLayerEl) {
slot.zoomLayerEl.style.display = "flex";
}
slot.imageEl.style.display = "block"; slot.imageEl.style.display = "block";
slot.fallbackEl.style.display = "none"; slot.fallbackEl.style.display = "none";
} else { } else {
slot.imageEl.removeAttribute("src"); slot.imageEl.removeAttribute("src");
slot.imageEl.alt = ""; slot.imageEl.alt = "";
if (slot.zoomLayerEl) {
slot.zoomLayerEl.style.display = "none";
}
slot.imageEl.style.display = "none"; slot.imageEl.style.display = "none";
slot.fallbackEl.textContent = cardRequest.missingReason || "Card image unavailable for this deck."; slot.fallbackEl.textContent = cardRequest.missingReason || "Card image unavailable for this deck.";
slot.fallbackEl.style.display = "block"; slot.fallbackEl.style.display = "block";
@@ -983,12 +996,13 @@
applyZoomTransform(); applyZoomTransform();
} }
function updateZoomOrigin(clientX, clientY, targetImage = imageEl) { function updateZoomOrigin(clientX, clientY, targetImage = imageEl, targetFrame = null) {
if (!zoomed || !targetImage) { const referenceEl = targetFrame || targetImage;
if (!zoomed || !referenceEl) {
return; return;
} }
const rect = targetImage.getBoundingClientRect(); const rect = referenceEl.getBoundingClientRect();
if (!rect.width || !rect.height) { if (!rect.width || !rect.height) {
return; return;
} }
@@ -1000,12 +1014,13 @@
applyTransformOrigins(); applyTransformOrigins();
} }
function isPointOnCard(clientX, clientY, targetImage = imageEl) { function isPointOnCard(clientX, clientY, targetImage = imageEl, targetFrame = null) {
if (!targetImage) { const frameElForHitTest = targetFrame || targetImage;
if (!targetImage || !frameElForHitTest) {
return false; return false;
} }
const rect = targetImage.getBoundingClientRect(); const rect = frameElForHitTest.getBoundingClientRect();
const naturalWidth = targetImage.naturalWidth; const naturalWidth = targetImage.naturalWidth;
const naturalHeight = targetImage.naturalHeight; const naturalHeight = targetImage.naturalHeight;
@@ -1350,13 +1365,23 @@
mediaEl.style.background = "rgba(2, 6, 23, 0.4)"; mediaEl.style.background = "rgba(2, 6, 23, 0.4)";
mediaEl.style.overflow = "hidden"; mediaEl.style.overflow = "hidden";
const zoomLayerEl = document.createElement("div");
zoomLayerEl.style.position = "absolute";
zoomLayerEl.style.inset = "16px";
zoomLayerEl.style.display = "flex";
zoomLayerEl.style.alignItems = "center";
zoomLayerEl.style.justifyContent = "center";
zoomLayerEl.style.transform = "scale(1)";
zoomLayerEl.style.transformOrigin = "50% 50%";
zoomLayerEl.style.transition = "transform 120ms ease-out";
const compareImageEl = document.createElement("img"); const compareImageEl = document.createElement("img");
compareImageEl.alt = "Tarot compare image"; compareImageEl.alt = "Tarot compare image";
compareImageEl.style.width = "100%"; compareImageEl.style.width = "100%";
compareImageEl.style.height = "100%"; compareImageEl.style.height = "100%";
compareImageEl.style.objectFit = "contain"; compareImageEl.style.objectFit = "contain";
compareImageEl.style.cursor = "zoom-in"; compareImageEl.style.cursor = "zoom-in";
compareImageEl.style.transform = "scale(1) rotate(0deg)"; compareImageEl.style.transform = "rotate(0deg)";
compareImageEl.style.transformOrigin = "center center"; compareImageEl.style.transformOrigin = "center center";
compareImageEl.style.transition = "transform 120ms ease-out"; compareImageEl.style.transition = "transform 120ms ease-out";
compareImageEl.style.userSelect = "none"; compareImageEl.style.userSelect = "none";
@@ -1369,13 +1394,16 @@
fallbackEl.style.font = "600 13px/1.45 sans-serif"; fallbackEl.style.font = "600 13px/1.45 sans-serif";
fallbackEl.style.color = "rgba(226, 232, 240, 0.88)"; fallbackEl.style.color = "rgba(226, 232, 240, 0.88)";
mediaEl.append(compareImageEl, fallbackEl); zoomLayerEl.appendChild(compareImageEl);
mediaEl.append(zoomLayerEl, fallbackEl);
slotEl.append(headerEl, mediaEl); slotEl.append(headerEl, mediaEl);
return { return {
slotEl, slotEl,
badgeEl, badgeEl,
cardLabelEl, cardLabelEl,
mediaEl,
zoomLayerEl,
imageEl: compareImageEl, imageEl: compareImageEl,
fallbackEl fallbackEl
}; };
@@ -1716,7 +1744,7 @@
compareGridSlots.forEach((slot) => { compareGridSlots.forEach((slot) => {
slot.imageEl.addEventListener("click", (event) => { slot.imageEl.addEventListener("click", (event) => {
event.stopPropagation(); event.stopPropagation();
if (!isPointOnCard(event.clientX, event.clientY, slot.imageEl)) { if (!isPointOnCard(event.clientX, event.clientY, slot.imageEl, slot.mediaEl)) {
close(); close();
return; return;
} }
@@ -1724,7 +1752,7 @@
if (!zoomed) { if (!zoomed) {
zoomed = true; zoomed = true;
applyZoomTransform(); applyZoomTransform();
updateZoomOrigin(event.clientX, event.clientY, slot.imageEl); updateZoomOrigin(event.clientX, event.clientY, slot.imageEl, slot.mediaEl);
applyComparePresentation(); applyComparePresentation();
return; return;
} }
@@ -1734,7 +1762,7 @@
}); });
slot.imageEl.addEventListener("mousemove", (event) => { slot.imageEl.addEventListener("mousemove", (event) => {
updateZoomOrigin(event.clientX, event.clientY, slot.imageEl); updateZoomOrigin(event.clientX, event.clientY, slot.imageEl, slot.mediaEl);
}); });
slot.imageEl.addEventListener("mouseleave", () => { slot.imageEl.addEventListener("mouseleave", () => {

View File

@@ -16,12 +16,13 @@
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css"> <link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-400.css">
<link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-700.css"> <link rel="stylesheet" href="node_modules/@fontsource/amiri/arabic-700.css">
<link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css"> <link rel="stylesheet" href="node_modules/@fontsource/noto-naskh-arabic/arabic-400.css">
<link rel="stylesheet" href="app/styles.css?v=20260310-cube-focus-01"> <link rel="stylesheet" href="app/styles.css?v=20260312-mobile-nav-01">
</head> </head>
<body> <body>
<div class="topbar"> <div class="topbar">
<button id="open-home" class="topbar-home-button" type="button" aria-pressed="true">Tarot Time!</button> <button id="open-home" class="topbar-home-button" type="button" aria-pressed="true">Tarot Time!</button>
<div class="topbar-actions"> <button id="topbar-menu-toggle" class="topbar-menu-toggle" type="button" aria-expanded="false" aria-controls="topbar-actions" aria-label="Open navigation menu">Menu</button>
<div id="topbar-actions" class="topbar-actions">
<div class="topbar-dropdown" aria-label="Calendar menu"> <div class="topbar-dropdown" aria-label="Calendar menu">
<button id="open-calendar" class="settings-trigger" type="button" aria-pressed="false" aria-haspopup="menu" aria-controls="calendar-subpages" aria-expanded="false">Calendar ▾</button> <button id="open-calendar" class="settings-trigger" type="button" aria-pressed="false" aria-haspopup="menu" aria-controls="calendar-subpages" aria-expanded="false">Calendar ▾</button>
<div id="calendar-subpages" class="topbar-dropdown-menu" role="menu" aria-label="Calendar subpages"> <div id="calendar-subpages" class="topbar-dropdown-menu" role="menu" aria-label="Calendar subpages">
@@ -954,7 +955,7 @@
<script src="app/data-service.js?v=20260310-text-search-split-04"></script> <script src="app/data-service.js?v=20260310-text-search-split-04"></script>
<script src="app/calendar-events.js"></script> <script src="app/calendar-events.js"></script>
<script src="app/card-images.js?v=20260309-gate"></script> <script src="app/card-images.js?v=20260309-gate"></script>
<script src="app/ui-tarot-lightbox.js?v=20260307b"></script> <script src="app/ui-tarot-lightbox.js?v=20260312-compare-zoom-01"></script>
<script src="app/ui-tarot-house.js?v=20260307b"></script> <script src="app/ui-tarot-house.js?v=20260307b"></script>
<script src="app/ui-tarot-relations.js"></script> <script src="app/ui-tarot-relations.js"></script>
<script src="app/ui-now-helpers.js"></script> <script src="app/ui-now-helpers.js"></script>
@@ -996,7 +997,7 @@
<script src="app/ui-alphabet-detail.js?v=20260309-enochian-api"></script> <script src="app/ui-alphabet-detail.js?v=20260309-enochian-api"></script>
<script src="app/ui-alphabet-kabbalah.js"></script> <script src="app/ui-alphabet-kabbalah.js"></script>
<script src="app/ui-alphabet.js?v=20260308b"></script> <script src="app/ui-alphabet.js?v=20260308b"></script>
<script src="app/ui-alphabet-text.js?v=20260310-text-search-split-10"></script> <script src="app/ui-alphabet-text.js?v=20260312-text-panel-collapse-01"></script>
<script src="app/ui-zodiac-references.js"></script> <script src="app/ui-zodiac-references.js"></script>
<script src="app/ui-zodiac.js"></script> <script src="app/ui-zodiac.js"></script>
<script src="app/ui-quiz-bank-builtins-domains.js"></script> <script src="app/ui-quiz-bank-builtins-domains.js"></script>
@@ -1013,7 +1014,7 @@
<script src="app/ui-numbers.js"></script> <script src="app/ui-numbers.js"></script>
<script src="app/ui-tarot-spread.js"></script> <script src="app/ui-tarot-spread.js"></script>
<script src="app/ui-settings.js?v=20260309-gate"></script> <script src="app/ui-settings.js?v=20260309-gate"></script>
<script src="app/ui-chrome.js"></script> <script src="app/ui-chrome.js?v=20260312-mobile-nav-01"></script>
<script src="app/ui-navigation.js?v=20260309-alphabet-text-01"></script> <script src="app/ui-navigation.js?v=20260309-alphabet-text-01"></script>
<script src="app/ui-calendar-formatting.js?v=20260307b"></script> <script src="app/ui-calendar-formatting.js?v=20260307b"></script>
<script src="app/ui-calendar-visuals.js?v=20260307b"></script> <script src="app/ui-calendar-visuals.js?v=20260307b"></script>