fix(live): pin window.scrollY instead of element viewport top

Element-based scroll tracking broke every time: Bun's HMR destroys the
target element, the browser's scroll anchoring picks a different nearby
element (e.g. the #downloads CTA) as its new anchor, and the page jumps
to wherever that surviving element is. My element-based correction then
computes against a replaced DOM node with stale / wrong geometry.

The primitive the user actually cares about is window.scrollY — they
want the page to stay where it is, regardless of which element survives
the patch. Pin scrollY directly: capture it at session start, restore it
on every mutation inside the wrapper, re-anchor on user scroll, store it
in saveSession for reload-resume.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-22 10:25:40 -07:00
co-authored by Claude Opus 4.7
parent 1e533e535a
commit 565381a3e7
13 changed files with 437 additions and 1256 deletions
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
+36 -85
View File
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
+36 -85
View File
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
+36 -85
View File
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so
+5 -236
View File
@@ -720,245 +720,14 @@
<!-- impeccable-variants-start c744e33e -->
<div data-impeccable-variants="c744e33e" data-impeccable-variant-count="3" style="display: contents">
<!-- Original -->
<div data-impeccable-variant="original">
<div class="consulting-text">
<h2 class="consulting-title">Work with me</h2>
<p class="consulting-desc">Impeccable is built by <a href="https://renaissance-geek.ai" target="_blank" rel="noopener">Renaissance Geek</a>. I work with enterprise teams on large-scale rollouts, custom integrations, and training for designers and developers. If you're a frontier lab, design tool company, or enterprise looking to raise the bar on AI-generated design, let's talk.</p>
</div>
</div>
<!-- Variants: insert below this line -->
<style data-impeccable-css="c744e33e">
@scope ([data-impeccable-variant="1"]) {
.consulting-text {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
max-width: 64ch;
}
.v1-dispatch {
display: flex;
justify-content: space-between;
align-items: baseline;
font-family: var(--font-mono);
font-size: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.28em;
color: var(--color-ash);
padding-bottom: 10px;
border-bottom: 1px solid var(--color-ink);
}
.v1-title {
font-family: var(--font-display);
font-weight: 400;
font-style: italic;
font-size: clamp(2rem, 4vw, 2.75rem);
line-height: 1.05;
color: var(--color-ink);
margin: 0;
}
.v1-log {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
}
.v1-log li {
display: grid;
grid-template-columns: 44px 1fr;
gap: 20px;
align-items: baseline;
padding: 14px 0;
border-bottom: 1px dashed var(--color-mist);
font-family: var(--font-mono);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--color-charcoal);
}
.v1-log .v1-num {
color: var(--color-accent);
font-weight: 500;
}
.v1-desc {
font-family: var(--font-body);
font-size: 0.9375rem;
line-height: 1.6;
color: var(--color-charcoal);
margin: 8px 0 0;
}
.v1-desc a {
color: var(--color-ink);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
text-decoration-color: var(--color-accent);
}
.v1-desc a:hover { color: var(--color-accent); }
}
@scope ([data-impeccable-variant="2"]) {
.consulting-text {
display: grid;
grid-template-columns: minmax(220px, 300px) 1fr;
gap: 48px;
align-items: center;
}
.v2-mark {
font-family: var(--font-display);
font-style: italic;
font-weight: 300;
font-size: clamp(10rem, 18vw, 14rem);
line-height: 0.82;
color: var(--color-ink);
letter-spacing: -0.04em;
text-align: center;
position: relative;
padding: 24px 0;
}
.v2-mark::before,
.v2-mark::after {
content: "";
position: absolute;
left: 10%;
right: 10%;
height: 1px;
background: var(--color-accent);
}
.v2-mark::before { top: 0; }
.v2-mark::after { bottom: 0; }
.v2-mark em {
color: var(--color-accent);
}
.v2-body {
display: flex;
flex-direction: column;
gap: 16px;
}
.v2-tag {
font-family: var(--font-mono);
font-size: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.3em;
color: var(--color-ash);
}
.v2-title {
font-family: var(--font-display);
font-weight: 400;
font-size: clamp(1.75rem, 3.5vw, 2.25rem);
line-height: 1.1;
color: var(--color-ink);
margin: 0;
}
.v2-desc {
font-family: var(--font-body);
font-size: 1rem;
line-height: 1.65;
color: var(--color-charcoal);
margin: 0;
max-width: 58ch;
}
.v2-desc a {
color: var(--color-ink);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
}
.v2-desc a:hover { color: var(--color-accent); }
}
@scope ([data-impeccable-variant="3"]) {
.consulting-text {
position: relative;
padding: 48px 48px 40px;
border: 1px solid var(--color-ink);
max-width: 68ch;
}
.v3-title {
position: absolute;
top: -0.55em;
left: 32px;
background: var(--color-bg);
padding: 0 16px;
font-family: var(--font-display);
font-style: italic;
font-weight: 400;
font-size: clamp(1.75rem, 3.2vw, 2.25rem);
line-height: 1;
color: var(--color-ink);
margin: 0;
}
.v3-desc {
font-family: var(--font-body);
font-size: 1rem;
line-height: 1.65;
color: var(--color-charcoal);
margin: 0;
}
.v3-desc a {
color: var(--color-ink);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
text-decoration-color: var(--color-accent);
}
.v3-desc a:hover { color: var(--color-accent); }
.v3-foot {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-top: 28px;
padding-top: 16px;
border-top: 1px dashed var(--color-mist);
font-family: var(--font-mono);
font-size: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.28em;
color: var(--color-ash);
}
.v3-foot-label { color: var(--color-ink); }
}
</style>
<div data-impeccable-variant="1">
<div class="consulting-text">
<div class="v1-dispatch">
<span>Transmission · 001</span>
<span>Consulting</span>
</div>
<h2 class="v1-title">Work with me.</h2>
<ul class="v1-log">
<li><span class="v1-num">01</span><span>Enterprise rollouts</span></li>
<li><span class="v1-num">02</span><span>Custom integrations</span></li>
<li><span class="v1-num">03</span><span>Team training for designers and developers</span></li>
</ul>
<p class="v1-desc">Impeccable is built by <a href="https://renaissance-geek.ai" target="_blank" rel="noopener">Renaissance Geek</a>. If you're a frontier lab, design tool company, or enterprise team, let's talk.</p>
</div>
</div>
<div data-impeccable-variant="2" style="display: none">
<div class="consulting-text">
<div class="v2-mark"><em>R</em>G</div>
<div class="v2-body">
<span class="v2-tag">Consulting · Renaissance Geek</span>
<h2 class="v2-title">Work with me on the design side of AI.</h2>
<p class="v2-desc">I work with enterprise teams on large-scale rollouts, custom integrations, and training for designers and developers. If you're a frontier lab, design tool company, or enterprise looking to raise the bar on AI-generated design, let's talk.</p>
</div>
</div>
</div>
<div data-impeccable-variant="3" style="display: none">
<div class="consulting-text">
<h2 class="v3-title">Work with me</h2>
<p class="v3-desc">Impeccable is built by <a href="https://renaissance-geek.ai" target="_blank" rel="noopener">Renaissance Geek</a>. I work with enterprise teams on large-scale rollouts, custom integrations, and training for designers and developers. If you're a frontier lab, design tool company, or enterprise looking to raise the bar on AI-generated design, let's talk.</p>
<div class="v3-foot">
<span class="v3-foot-label">Consulting</span>
<span>Renaissance Geek · v3.0</span>
</div>
</div>
</div>
<div class="consulting-text">
<h2 class="consulting-title">Work with me</h2>
<p class="consulting-desc">Impeccable is built by <a href="https://renaissance-geek.ai" target="_blank" rel="noopener">Renaissance Geek</a>. I work with enterprise teams on large-scale rollouts, custom integrations, and training for designers and developers. If you're a frontier lab, design tool company, or enterprise looking to raise the bar on AI-generated design, let's talk.</p>
</div>
<!-- impeccable-variants-end c744e33e -->
@@ -91,11 +91,11 @@
let selectedAction = 'impeccable';
let selectedCount = 3;
// Scroll lock — holds the selected element at a fixed viewport-top while
// the session is active, so HMR DOM patches and variant swaps don't drift
// the page. See startScrollLock / stopScrollLock below.
// Scroll lock — holds window.scrollY at a fixed value while the session is
// active, so HMR DOM patches and variant swaps can't drift the page. See
// startScrollLock / stopScrollLock below.
let scrollLockObserver = null;
let scrollLockTargetTop = null;
let scrollLockTargetY = null;
let scrollLockRaf = null;
let scrollLockAbort = null;
@@ -1320,84 +1320,44 @@
return variantDiv;
}
// Resolve the element whose top we want to lock: the currently-visible
// variant's content (falling back to the original), identified by
// sessionId so we survive DOM swaps that invalidate `selectedElement`.
function resolveScrollLockTarget(sessionId) {
const wrapper = sessionId
? document.querySelector('[data-impeccable-variants="' + sessionId + '"]')
: null;
if (wrapper) {
const idx = visibleVariant > 0 ? visibleVariant : 'original';
const el = pickVariantContent(wrapper, idx);
if (el) return el;
}
return selectedElement?.isConnected ? selectedElement : null;
}
// Hold the resolved target at a fixed viewport-top across DOM mutations
// (HMR patches, variant inserts, variant cycle swaps). If the caller
// passes `initialTargetTop`, use it (e.g. on resume after full reload);
// otherwise capture the current target's top.
function startScrollLock(sessionId, initialTargetTop) {
// Hold window.scrollY at a fixed value across DOM mutations inside the
// session's wrapper (HMR patches, variant inserts, cycle swaps). The key
// insight: we don't care where the selected element ends up, we just
// don't want the page to jump. scrollY is a primitive that survives any
// DOM destruction; element-viewport-top is fragile when the element
// itself gets replaced.
function startScrollLock(sessionId, initialTargetY) {
stopScrollLock();
const initial = resolveScrollLockTarget(sessionId);
if (!initial) return;
scrollLockTargetTop = typeof initialTargetTop === 'number' && isFinite(initialTargetTop)
? initialTargetTop
: initial.getBoundingClientRect().top;
scrollLockTargetY = typeof initialTargetY === 'number' && isFinite(initialTargetY)
? initialTargetY
: window.scrollY;
try { history.scrollRestoration = 'manual'; } catch {}
// Disable browser scroll anchoring on root elements during the session.
// When Bun's HMR destroys our target element and re-inserts it, the
// browser picks a different anchor nearby (often the wrong one — Get
// Started, say) and scrolls the page to keep THAT stable. We want to
// own scroll ourselves, so turn it off while we're active.
// Disable the browser's own scroll anchoring during the session. Bun's
// HMR destroys and re-inserts our target element, at which point the
// browser picks a different anchor elsewhere on the page (e.g. the
// nearest #downloads CTA) and scrolls to keep THAT stable. We own
// scroll ourselves while active.
const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
const prevBodyAnchor = document.body.style.overflowAnchor;
document.documentElement.style.overflowAnchor = 'none';
document.body.style.overflowAnchor = 'none';
// Grace window after any user-scroll intent: suppress corrections so
// momentum scrolls can't be yanked back by a mutation firing mid-scroll.
let lastUserScrollAt = 0;
const USER_SCROLL_GRACE_MS = 400;
const correct = () => {
scrollLockRaf = null;
if (scrollLockTargetTop == null) return;
const el = resolveScrollLockTarget(sessionId);
if (!el) return;
if (performance.now() - lastUserScrollAt < USER_SCROLL_GRACE_MS) {
// User just scrolled — just re-anchor and let them be.
scrollLockTargetTop = el.getBoundingClientRect().top;
return;
}
const currentTop = el.getBoundingClientRect().top;
const delta = currentTop - scrollLockTargetTop;
if (Math.abs(delta) < 0.5) return;
// Always correct, even for huge deltas — a huge delta typically
// means the browser's anchor drifted (common with Bun's HMR
// wholesale-replace) and is exactly when we most need to restore.
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
};
// Restore overflow-anchor on stop. Stash the restorer on the abort
// controller so stopScrollLock picks it up.
const restoreAnchor = () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
if (scrollLockTargetY == null) return;
if (Math.abs(window.scrollY - scrollLockTargetY) < 0.5) return;
window.scrollTo({ top: scrollLockTargetY, left: window.scrollX, behavior: 'instant' });
};
const schedule = () => {
if (scrollLockRaf != null) return;
scrollLockRaf = requestAnimationFrame(correct);
};
// Filter to mutations that touch our session's wrapper. Watching the
// whole body means shader animations, HMR indicators, tooltips, and
// every other DOM change elsewhere on the page fires corrections —
// which fight the user on scroll.
// Filter to mutations that touch our session's wrapper. Unrelated
// mutations (shader animations, HMR indicators, tooltips) shouldn't
// trigger corrections and fight the user.
scrollLockObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.target?.closest?.('[data-impeccable-variants="' + sessionId + '"]')) {
@@ -1414,17 +1374,16 @@
});
scrollLockObserver.observe(document.body, { childList: true, subtree: true });
// Treat explicit user scroll intent as a re-anchor: cancel any pending
// correction, then update the target top to the element's new position
// so we don't drag them back on the next mutation.
// User scroll intent updates the target — we never fight the user.
scrollLockAbort = new AbortController();
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { once: true });
scrollLockAbort.signal.addEventListener('abort', () => {
document.documentElement.style.overflowAnchor = prevHtmlAnchor;
document.body.style.overflowAnchor = prevBodyAnchor;
}, { once: true });
const sig = { signal: scrollLockAbort.signal };
const reanchor = () => {
lastUserScrollAt = performance.now();
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
const el = resolveScrollLockTarget(sessionId);
if (el) scrollLockTargetTop = el.getBoundingClientRect().top;
scrollLockTargetY = window.scrollY;
};
window.addEventListener('wheel', reanchor, { passive: true, ...sig });
window.addEventListener('touchstart', reanchor, { passive: true, ...sig });
@@ -1433,15 +1392,16 @@
if (['PageDown', 'PageUp', ' ', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(e.key)) reanchor();
}, sig);
// Initial apply — primarily useful on resume after a true reload,
// where the browser may have landed us somewhere wrong.
schedule();
if (document.fonts?.ready) document.fonts.ready.then(schedule).catch(() => {});
}
function stopScrollLock() {
if (scrollLockObserver) { scrollLockObserver.disconnect(); scrollLockObserver = null; }
if (scrollLockRaf != null) { cancelAnimationFrame(scrollLockRaf); scrollLockRaf = null; }
if (scrollLockAbort) { scrollLockAbort.abort(); scrollLockAbort = null; }
scrollLockTargetTop = null;
scrollLockTargetY = null;
}
// ---------------------------------------------------------------------------
@@ -2270,15 +2230,6 @@ void main() {
function saveSession() {
if (!currentSessionId) return;
// Capture the selected element's current viewport-relative top so we
// can restore the same framing after a reload, even if layout shifts.
let scrollAnchor = null;
try {
if (selectedElement && selectedElement.isConnected) {
const r = selectedElement.getBoundingClientRect();
if (r.width > 0 && r.height > 0) scrollAnchor = { viewportTop: r.top };
}
} catch {}
try {
localStorage.setItem(LS_KEY, JSON.stringify({
id: currentSessionId,
@@ -2288,7 +2239,7 @@ void main() {
expected: expectedVariants,
arrived: arrivedVariants,
visible: visibleVariant,
scrollAnchor,
scrollY: window.scrollY,
}));
} catch { /* quota exceeded or private mode */ }
}
@@ -2445,7 +2396,7 @@ void main() {
// Hold the target at its saved viewport top through any subsequent
// HMR patches, variant inserts, or cycle swaps.
startScrollLock(currentSessionId, saved?.scrollAnchor?.viewportTop);
startScrollLock(currentSessionId, saved?.scrollY);
// If we reloaded mid-generation (Bun's HTML HMR destroys the shader
// canvas), re-capture the original's content and restart the shader so