rosary: fix mystery image timing and SVG container clipping
All checks were successful
CI / update (push) Successful in 1m51s

Show first mystery image at the Pater Noster instead of the Gloria Patri
by removing the early lbead2 trigger. Fix IntersectionObserver to prefer
the topmost intersecting entry so short _pater sections aren't skipped.
Use full viewport height (100dvh) for the SVG container to prevent
clipping at edges.
This commit is contained in:
2026-03-08 20:46:04 +01:00
parent f76b647918
commit 75401784ba
2 changed files with 31 additions and 24 deletions

View File

@@ -137,7 +137,6 @@ const hasMysteryImages = $derived(showImages && (allMysteryImages[selectedMyster
* @returns {number | 'before' | 'after'} * @returns {number | 'before' | 'after'}
*/ */
function getMysteryScrollTarget(section) { function getMysteryScrollTarget(section) {
if (section === 'lbead2') return 1;
const secretMatch = section.match(/^secret(\d)/); const secretMatch = section.match(/^secret(\d)/);
if (secretMatch) { if (secretMatch) {
const num = parseInt(secretMatch[1]); const num = parseInt(secretMatch[1]);
@@ -446,8 +445,8 @@ onMount(() => {
.rosary-visualization { .rosary-visualization {
padding: 2rem 0; padding: 2rem 0;
position: sticky; position: sticky;
top: 2rem; top: 0;
max-height: calc(100vh - 2rem); max-height: 100dvh;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */

View File

@@ -59,29 +59,37 @@ export function setupScrollSync({
const svgContainer = getSvgContainer(); const svgContainer = getSvgContainer();
const sectionElements = getSectionElements(); const sectionElements = getSectionElements();
entries.forEach((entry) => { if (scrollLock === 'svg' || scrollLock === 'click') return;
if (entry.isIntersecting && scrollLock !== 'svg' && scrollLock !== 'click') { if (window.scrollY < 50) return;
// Skip observer updates when at the top — handleWindowScroll handles this
if (window.scrollY < 50) return;
const section = /** @type {HTMLElement} */ (entry.target).dataset.section; // Pick the topmost intersecting entry so short sections (e.g. _pater) aren't
if (!section) return; // immediately overridden by the taller section below them
setActiveSection(section); let bestEntry = null;
for (const entry of entries) {
// Scroll SVG to keep active section visible at top if (!entry.isIntersecting) continue;
if (svgContainer && sectionPositions[section] !== undefined) { if (!bestEntry || entry.boundingClientRect.top < bestEntry.boundingClientRect.top) {
const svg = /** @type {SVGSVGElement | null} */ (svgContainer.querySelector('svg')); bestEntry = entry;
if (!svg) return;
const pixelPosition = svgSectionToPixel(svg, section);
if (pixelPosition === null) return;
const targetScroll = pixelPosition - 100;
setScrollLock('prayer');
svgContainer.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' });
}
} }
}); }
if (bestEntry) {
const section = /** @type {HTMLElement} */ (bestEntry.target).dataset.section;
if (!section) return;
setActiveSection(section);
// Scroll SVG to keep active section visible at top
if (svgContainer && sectionPositions[section] !== undefined) {
const svg = /** @type {SVGSVGElement | null} */ (svgContainer.querySelector('svg'));
if (!svg) return;
const pixelPosition = svgSectionToPixel(svg, section);
if (pixelPosition === null) return;
const targetScroll = pixelPosition - 100;
setScrollLock('prayer');
svgContainer.scrollTo({ top: Math.max(0, targetScroll), behavior: 'smooth' });
}
}
}, { }, {
root: null, root: null,
rootMargin: "-20% 0px -60% 0px", rootMargin: "-20% 0px -60% 0px",