mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
fix(live): restore scroll to element's viewport-relative top after reload
When HMR misses and we fall back to window.location.reload(), the native scroll restoration landed the page somewhere near the right region but not on the selected element, because layout had shifted between the save and the reload. Capture the element's getBoundingClientRect().top into the session snapshot, disable native scroll restoration on resume, and manually scroll the element back to that exact viewport-relative position. Run a second correction pass after fonts and images settle to absorb late layout shifts without animating the fix. 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
101dc50362
commit
4f4df85250
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
+8
-2
@@ -104,6 +104,8 @@
|
||||
|
||||
<div class="hero-cta-group">
|
||||
<a href="#downloads" class="hero-cta-combined">Get Started</a>
|
||||
|
||||
|
||||
<div class="hero-logos-inline">
|
||||
<span class="hero-logos-label">Works with</span>
|
||||
<div class="hero-logos-row">
|
||||
@@ -118,6 +120,8 @@
|
||||
<span class="hero-logo-icon has-tooltip" data-tooltip="Pi"><img src="assets/pi-logo.svg" alt="Pi" width="20" height="20" loading="lazy" style="background:#1a1a1a;border-radius:50%;padding:3px"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<p class="hero-version-link"><a href="#changelog">v3.0: 1 skill, 23 commands</a></p>
|
||||
</div>
|
||||
@@ -713,10 +717,12 @@
|
||||
<!-- PARTNERSHIPS -->
|
||||
<section class="consulting-section" id="consulting">
|
||||
<div class="consulting-content" data-reveal>
|
||||
|
||||
<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>
|
||||
<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 class="consulting-actions">
|
||||
<a href="mailto:paul@renaissance-geek.ai" class="btn btn-primary">
|
||||
<span>Get in touch</span>
|
||||
|
||||
@@ -2135,6 +2135,15 @@ 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,
|
||||
@@ -2144,6 +2153,7 @@ void main() {
|
||||
expected: expectedVariants,
|
||||
arrived: arrivedVariants,
|
||||
visible: visibleVariant,
|
||||
scrollAnchor,
|
||||
}));
|
||||
} catch { /* quota exceeded or private mode */ }
|
||||
}
|
||||
@@ -2288,6 +2298,25 @@ void main() {
|
||||
// Set display state BEFORE starting observer (avoid triggering it)
|
||||
if (visibleVariant > 0) showVariantInDOM(currentSessionId, visibleVariant);
|
||||
|
||||
// Restore scroll: land the selected element back at the same
|
||||
// viewport-relative top it had before the reload. Two passes — once
|
||||
// synchronously, once after fonts/images settle — catch late layout
|
||||
// shifts without animating the correction.
|
||||
if (saved?.scrollAnchor && selectedElement) {
|
||||
try { history.scrollRestoration = 'manual'; } catch {}
|
||||
const targetTop = saved.scrollAnchor.viewportTop;
|
||||
const correct = () => {
|
||||
if (!selectedElement?.isConnected) return;
|
||||
const delta = selectedElement.getBoundingClientRect().top - targetTop;
|
||||
if (Math.abs(delta) > 0.5) window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
|
||||
};
|
||||
correct();
|
||||
requestAnimationFrame(() => requestAnimationFrame(correct));
|
||||
if (document.fonts?.ready) {
|
||||
document.fonts.ready.then(() => requestAnimationFrame(correct)).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
state = arrivedVariants >= expectedVariants ? 'CYCLING' : 'GENERATING';
|
||||
showBar(state === 'CYCLING' ? 'cycling' : 'generating');
|
||||
startScrollTracking();
|
||||
|
||||
Reference in New Issue
Block a user