mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-16 08:06:24 +03:00
fix(live): disable browser overflow-anchor during session, always correct
Two things were wrong. First, I capped large corrections — which was backwards: a huge delta is exactly when we most need to restore (it means the browser's own scroll anchoring drifted, which is what makes the page 'jump to Get Started' when Bun's HMR destroys and re-inserts our target). Remove the cap so any delta is corrected. Second, the browser's built-in scroll anchoring was competing with us: when Bun destroys our target element, the browser picks the nearest surviving element (like a CTA anchor in another section) as its new scroll anchor and scrolls to keep THAT stable. Disable overflow-anchor on html and body for the duration of the session so we own scroll entirely; restore the original values on stopScrollLock. Kept the user-scroll grace window (400ms): wheel / touch / arrow keys re-anchor and suppress corrections, so momentum scrolls don't get fought. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
b99ab4db2c
commit
1e533e535a
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
+158
-106
@@ -719,8 +719,9 @@
|
||||
<div class="consulting-content" data-reveal>
|
||||
|
||||
|
||||
<!-- impeccable-variants-start ec52f1ff -->
|
||||
<div data-impeccable-variants="ec52f1ff" data-impeccable-variant-count="3" style="display: contents">
|
||||
|
||||
<!-- 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">
|
||||
@@ -729,143 +730,171 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Variants: insert below this line -->
|
||||
<style data-impeccable-css="ec52f1ff">
|
||||
<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;
|
||||
gap: 24px;
|
||||
padding: 12px 0;
|
||||
border-top: 1px solid var(--color-mist);
|
||||
border-bottom: 1px solid var(--color-mist);
|
||||
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-weight: 300;
|
||||
font-size: 1.75rem;
|
||||
line-height: 1;
|
||||
font-size: clamp(2rem, 4vw, 2.75rem);
|
||||
line-height: 1.05;
|
||||
color: var(--color-ink);
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.v1-sep {
|
||||
.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);
|
||||
letter-spacing: 0.2em;
|
||||
font-weight: 500;
|
||||
}
|
||||
.v1-desc {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-charcoal);
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
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: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-width: 62ch;
|
||||
}
|
||||
.v2-title {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: clamp(2.25rem, 4vw, 3rem);
|
||||
line-height: 1.05;
|
||||
color: var(--color-ink);
|
||||
margin: 0;
|
||||
}
|
||||
.v2-deck {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.35;
|
||||
color: var(--color-accent);
|
||||
margin: 0;
|
||||
}
|
||||
.v2-desc {
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-charcoal);
|
||||
margin: 0;
|
||||
}
|
||||
.v2-desc a {
|
||||
color: var(--color-ink);
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 3px;
|
||||
text-decoration-color: var(--color-accent);
|
||||
}
|
||||
.v2-desc a:hover { color: var(--color-accent); }
|
||||
}
|
||||
|
||||
@scope ([data-impeccable-variant="3"]) {
|
||||
.consulting-text {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(auto, 60%) minmax(240px, 36ch);
|
||||
gap: 48px 64px;
|
||||
align-items: end;
|
||||
padding: 40px 0 20px;
|
||||
grid-template-columns: minmax(220px, 300px) 1fr;
|
||||
gap: 48px;
|
||||
align-items: center;
|
||||
}
|
||||
.v3-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
.v3-stack-line {
|
||||
.v2-mark {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-size: clamp(5rem, 13vw, 10rem);
|
||||
line-height: 0.88;
|
||||
letter-spacing: -0.03em;
|
||||
font-size: clamp(10rem, 18vw, 14rem);
|
||||
line-height: 0.82;
|
||||
color: var(--color-ink);
|
||||
margin: 0;
|
||||
letter-spacing: -0.04em;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 24px 0;
|
||||
}
|
||||
.v3-stack-line:nth-child(2) {
|
||||
padding-left: 0.25em;
|
||||
.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);
|
||||
}
|
||||
.v3-stack-line:nth-child(3) {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
.v3-rail {
|
||||
.v2-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-left: 1px solid var(--color-ink);
|
||||
padding-left: 24px;
|
||||
align-self: stretch;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
.v3-tag {
|
||||
.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: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
font-size: 1rem;
|
||||
line-height: 1.65;
|
||||
color: var(--color-charcoal);
|
||||
margin: 0;
|
||||
}
|
||||
@@ -877,37 +906,60 @@
|
||||
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">
|
||||
<h2 class="v1-title"><em>Work with me.</em></h2>
|
||||
<span class="v1-sep">///</span>
|
||||
<p class="v1-desc">Built by <a href="https://renaissance-geek.ai" target="_blank" rel="noopener">Renaissance Geek</a> · Rollouts · Integrations · Training · <a href="mailto:paul@renaissance-geek.ai">Say hello</a></p>
|
||||
<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">
|
||||
<h2 class="v2-title">Work with me.</h2>
|
||||
<p class="v2-deck">Rollouts, integrations, and training for teams that take AI-generated design seriously.</p>
|
||||
<p class="v2-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 looking to raise the bar on AI-generated design, let's talk.</p>
|
||||
<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">
|
||||
<div class="v3-stack">
|
||||
<span class="v3-stack-line">Work</span>
|
||||
<span class="v3-stack-line">with</span>
|
||||
<span class="v3-stack-line">me.</span>
|
||||
</div>
|
||||
<div class="v3-rail">
|
||||
<span class="v3-tag">Consulting · Renaissance Geek</span>
|
||||
<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>
|
||||
<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>
|
||||
<!-- impeccable-variants-end ec52f1ff -->
|
||||
<!-- impeccable-variants-end c744e33e -->
|
||||
|
||||
|
||||
|
||||
<div class="consulting-actions">
|
||||
|
||||
@@ -1349,15 +1349,45 @@
|
||||
|
||||
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.
|
||||
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;
|
||||
const delta = el.getBoundingClientRect().top - scrollLockTargetTop;
|
||||
if (Math.abs(delta) > 0.5) {
|
||||
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
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;
|
||||
};
|
||||
const schedule = () => {
|
||||
if (scrollLockRaf != null) return;
|
||||
@@ -1388,8 +1418,10 @@
|
||||
// correction, then update the target top to the element's new position
|
||||
// so we don't drag them back on the next mutation.
|
||||
scrollLockAbort = new AbortController();
|
||||
scrollLockAbort.signal.addEventListener('abort', restoreAnchor, { 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;
|
||||
|
||||
Reference in New Issue
Block a user