Skip to content

Commit eb98b31

Browse files
committed
feat: add new feature with new UI/UX
1 parent 73f34ec commit eb98b31

5 files changed

Lines changed: 715 additions & 161 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:18-bullseye
1+
FROM
22

33
WORKDIR /app
44

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { useEffect, useMemo, useState } from "react";
2+
3+
const FRAME_COUNT = 6;
4+
const FRAME_INTERVAL = 1200;
5+
6+
function getStatusLabel(state) {
7+
switch (state) {
8+
case "loading":
9+
return "Loading…";
10+
case "ready":
11+
return "Rendered";
12+
case "error":
13+
return "Blocked";
14+
default:
15+
return "Queued";
16+
}
17+
}
18+
19+
function PreviewFrame({ url, index, delayMs }) {
20+
const [source, setSource] = useState("");
21+
const [state, setState] = useState("queued");
22+
const [startedAt, setStartedAt] = useState(null);
23+
const [loadedAt, setLoadedAt] = useState(null);
24+
25+
useEffect(() => {
26+
if (!url) {
27+
setSource("");
28+
setState("idle");
29+
setStartedAt(null);
30+
setLoadedAt(null);
31+
return;
32+
}
33+
34+
setState("queued");
35+
const timer = setTimeout(() => {
36+
setSource(url);
37+
setStartedAt(performance.now());
38+
setState("loading");
39+
}, delayMs);
40+
41+
return () => {
42+
clearTimeout(timer);
43+
setSource("");
44+
setState("queued");
45+
setStartedAt(null);
46+
setLoadedAt(null);
47+
};
48+
}, [url, delayMs]);
49+
50+
const handleLoad = () => {
51+
setState("ready");
52+
setLoadedAt(performance.now());
53+
};
54+
55+
const handleError = () => {
56+
setState("error");
57+
};
58+
59+
const deltaMs =
60+
startedAt && loadedAt ? Math.max(0, loadedAt - startedAt).toFixed(0) : null;
61+
62+
return (
63+
<div className={`preview-frame ${state}`}>
64+
{source ? (
65+
<iframe
66+
src={source}
67+
title={`preview-${index}`}
68+
onLoad={handleLoad}
69+
onError={handleError}
70+
sandbox="allow-same-origin allow-scripts allow-forms"
71+
loading="lazy"
72+
referrerPolicy="no-referrer"
73+
/>
74+
) : (
75+
<div className="preview-placeholder">Awaiting snapshot…</div>
76+
)}
77+
<footer>
78+
<div>
79+
<span>Frame {index + 1}</span>
80+
<small>{getStatusLabel(state)}</small>
81+
</div>
82+
<div className="latency-badge">
83+
{deltaMs ? `${deltaMs} ms` : "—"}
84+
</div>
85+
</footer>
86+
</div>
87+
);
88+
}
89+
90+
export default function PreviewGrid({ url }) {
91+
const frames = useMemo(
92+
() => Array.from({ length: FRAME_COUNT }, (_, idx) => idx),
93+
[]
94+
);
95+
96+
if (!url) {
97+
return null;
98+
}
99+
100+
return (
101+
<section className="preview-grid">
102+
<header>
103+
<div>
104+
<p className="eyebrow">Live stability preview</p>
105+
<h2>How does the page settle?</h2>
106+
</div>
107+
<p>
108+
We reload the site multiple times in quick succession to surface
109+
layout shifts, blocking assets, and other instability indicators.
110+
</p>
111+
</header>
112+
<div className="preview-grid__frames">
113+
{frames.map((idx) => (
114+
<PreviewFrame
115+
key={`${url}-${idx}`}
116+
url={url}
117+
index={idx}
118+
delayMs={idx * FRAME_INTERVAL}
119+
/>
120+
))}
121+
</div>
122+
</section>
123+
);
124+
}
125+

0 commit comments

Comments
 (0)