From 71f46ef20f16538bd92f96259cd31604f06377d2 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 8 Apr 2026 11:09:05 -0700 Subject: [PATCH] Add invisible hover buffer around the before/after demo Ported the homepage's padding-margin trick: .split-comparison now has 20px of padding around the visible box, with a matching negative top/bottom margin so the padding does not affect layout flow. The pointer event listeners move from .split-container to .split-comparison so the hover tracking engages inside the buffer and only resets when the mouse leaves the full padded area. Percentage math still reads .split-container.getBoundingClientRect() so the divider position stays aligned with the visible box. This matches how the landing-page split demo feels: graze the edge and the divider holds; leave the box entirely and it eases back. --- public/css/sub-pages.css | 9 +++++++-- scripts/lib/render-page.js | 36 +++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/public/css/sub-pages.css b/public/css/sub-pages.css index 3feffd4d3..f5494deb0 100644 --- a/public/css/sub-pages.css +++ b/public/css/sub-pages.css @@ -997,8 +997,13 @@ main#main { .split-comparison { position: relative; width: 100%; - max-width: 500px; - margin: 0 0 clamp(2rem, 4vw, 3rem); + max-width: 540px; + /* 20px padding acts as an invisible hover buffer so the divider does + not immediately snap back when the pointer grazes the box edge. + Negative top/bottom margins collapse the padding out of layout flow + so the demo still sits at its intended rhythm in the page. */ + padding: 20px; + margin: -20px 0 calc(clamp(2rem, 4vw, 3rem) - 20px); } .split-container { diff --git a/scripts/lib/render-page.js b/scripts/lib/render-page.js index cdb6cd0e3..7878e27f5 100644 --- a/scripts/lib/render-page.js +++ b/scripts/lib/render-page.js @@ -116,17 +116,20 @@ ${bodyHtml} }); // Before/after split-compare: drag on touch, hover OR drag on mouse. - // On mouse-leave the divider eases back to the default position. + // Pointer events attach to the padded .split-comparison wrapper so + // there is a ~20px invisible buffer around the visible box. The + // divider only snaps back when the pointer leaves that outer buffer. (function initSplitCompare() { - const containers = document.querySelectorAll('.split-container'); - if (containers.length === 0) return; + const wrappers = document.querySelectorAll('.split-comparison'); + if (wrappers.length === 0) return; const hasHover = matchMedia('(hover: hover)').matches; const DEFAULT_POSITION = 50; - for (const container of containers) { - const splitAfter = container.querySelector('.split-after'); - const splitDivider = container.querySelector('.split-divider'); - if (!splitAfter || !splitDivider) continue; + for (const wrapper of wrappers) { + const container = wrapper.querySelector('.split-container'); + const splitAfter = wrapper.querySelector('.split-after'); + const splitDivider = wrapper.querySelector('.split-divider'); + if (!container || !splitAfter || !splitDivider) continue; const tanAngle = Math.tan(10 * Math.PI / 180); let skewOffset = 8; @@ -168,6 +171,9 @@ ${bodyHtml} paint(DEFAULT_POSITION); + // Percentage is always relative to the VISIBLE .split-container, + // not the padded .split-comparison wrapper. The pointer event + // target is the wrapper but the clip-path math uses the inner box. const pctFromClientX = (clientX) => { const rect = container.getBoundingClientRect(); return ((clientX - rect.left) / rect.width) * 100; @@ -176,19 +182,19 @@ ${bodyHtml} let hovering = false; let dragging = false; - container.addEventListener('pointerenter', (e) => { + wrapper.addEventListener('pointerenter', (e) => { if (hasHover && e.pointerType === 'mouse') { hovering = true; } }); - container.addEventListener('pointerdown', (e) => { + wrapper.addEventListener('pointerdown', (e) => { dragging = true; - container.setPointerCapture(e.pointerId); + wrapper.setPointerCapture(e.pointerId); setTarget(pctFromClientX(e.clientX)); }); - container.addEventListener('pointermove', (e) => { + wrapper.addEventListener('pointermove', (e) => { if (dragging || hovering) { setTarget(pctFromClientX(e.clientX)); } @@ -197,14 +203,14 @@ ${bodyHtml} const endDrag = (e) => { if (dragging) { dragging = false; - try { container.releasePointerCapture(e.pointerId); } catch {} + try { wrapper.releasePointerCapture(e.pointerId); } catch {} } }; - container.addEventListener('pointerup', endDrag); - container.addEventListener('pointercancel', endDrag); + wrapper.addEventListener('pointerup', endDrag); + wrapper.addEventListener('pointercancel', endDrag); - container.addEventListener('pointerleave', (e) => { + wrapper.addEventListener('pointerleave', (e) => { endDrag(e); if (hovering) { hovering = false;