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"] {
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 {
display: flex;
align-items: center;
@@ -113,6 +129,104 @@
.settings-trigger[aria-pressed="true"] {
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 {
overflow: hidden;
}
@@ -475,6 +589,16 @@
border: 1px solid #3f3f46;
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 {
margin: 0;
font-size: 24px;
@@ -1160,6 +1284,13 @@
.planet-detail-heading {
position: relative;
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 {
margin: 0;
@@ -3368,10 +3499,18 @@
}
@media (max-width: 720px) {
.planet-detail-heading,
.tarot-detail-heading,
.alpha-text-detail-heading {
padding-right: 0;
}
.planet-detail-heading,
.tarot-detail-heading {
display: grid;
gap: 10px;
}
.alpha-text-controls--heading {
grid-template-columns: 1fr;
}
@@ -3380,10 +3519,29 @@
grid-template-columns: 1fr;
}
.planet-detail-heading .detail-toggle-inline,
.tarot-detail-heading .detail-toggle-inline,
.alpha-text-detail-heading .detail-toggle-inline {
position: static;
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 {
@@ -5244,9 +5402,61 @@
.now-stats-planets {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.planet-layout {
.planet-layout,
.tarot-layout {
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 {
grid-template-columns: minmax(0, 1fr);
}

View File

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

View File

@@ -5,6 +5,40 @@
const DETAIL_COLLAPSE_STORAGE_PREFIX = "tarot-detail-collapsed:";
const DEFAULT_DATASET_ENTRY_COLLAPSED = true;
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) {
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() {
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", () => {
applyCollapsedState(true);
});
@@ -173,6 +337,14 @@
}
};
detailControllers.set(layout, {
applyCollapsedState,
detailPanel,
collapseBtn,
openBtn,
detailStorageKey
});
collapseBtn.addEventListener("click", () => {
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) {
const topbarDropdownEls = Array.from(document.querySelectorAll(".topbar-dropdown"));
topbarDropdownEls.forEach((dropdownEl) => {
@@ -227,10 +496,16 @@
setTopbarDropdownOpen(dropdownEl, false);
dropdownEl.addEventListener("mouseenter", () => {
if (isMobileTopbarViewport()) {
return;
}
setTopbarDropdownOpen(dropdownEl, true);
});
dropdownEl.addEventListener("mouseleave", () => {
if (isMobileTopbarViewport()) {
return;
}
setTopbarDropdownOpen(dropdownEl, false);
});
@@ -275,6 +550,8 @@
function init() {
initializeSidebarPopouts();
initializeDetailPopouts();
initializeSidebarAutoCollapse();
bindTopbarMobileMenu();
bindTopbarDropdownInteractions();
}
@@ -283,6 +560,12 @@
init,
initializeSidebarPopouts,
initializeDetailPopouts,
initializeSidebarAutoCollapse,
bindTopbarMobileMenu,
setSidebarCollapsed,
setDetailCollapsed,
showDetailOnly,
setTopbarMenuOpen,
setTopbarDropdownOpen,
closeTopbarDropdowns,
bindTopbarDropdownInteractions

View File

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