Skip to content

Commit 2bbf35f

Browse files
akuligowski9claude
andcommitted
Update demo GIF with comparison feature showcase
Re-recorded with Playwright: grid browse, card selection, and side-by-side compare drawer. Optimized to 1.9MB. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b8b32cb commit 2bbf35f

2 files changed

Lines changed: 112 additions & 0 deletions

File tree

public/demo.gif

393 KB
Loading

scripts/capture-demo.mjs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { chromium } from "playwright";
2+
import { execSync } from "child_process";
3+
import path from "path";
4+
import { fileURLToPath } from "url";
5+
6+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
7+
const ROOT = path.resolve(__dirname, "..");
8+
const VIDEO_DIR = path.join(ROOT, ".demo-video");
9+
const OUTPUT_GIF = path.join(ROOT, "public", "demo.gif");
10+
11+
const WIDTH = 1280;
12+
const HEIGHT = 800;
13+
const BASE_URL = process.argv[2] || "http://localhost:3000";
14+
15+
async function sleep(ms) {
16+
return new Promise((r) => setTimeout(r, ms));
17+
}
18+
19+
async function main() {
20+
const browser = await chromium.launch({ headless: true });
21+
const context = await browser.newContext({
22+
viewport: { width: WIDTH, height: HEIGHT },
23+
colorScheme: "dark",
24+
recordVideo: { dir: VIDEO_DIR, size: { width: WIDTH, height: HEIGHT } },
25+
});
26+
const page = await context.newPage();
27+
28+
// Load the page
29+
await page.goto(BASE_URL, { waitUntil: "networkidle" });
30+
await sleep(1500);
31+
32+
// Switch to 3-column layout
33+
const colButtons = page.locator('button[aria-label="3 column layout"]');
34+
await colButtons.click();
35+
await sleep(1000);
36+
37+
// Scroll down slowly to show cards
38+
await page.evaluate(() => window.scrollBy({ top: 400, behavior: "smooth" }));
39+
await sleep(1500);
40+
41+
// Scroll back up
42+
await page.evaluate(() => window.scrollTo({ top: 0, behavior: "smooth" }));
43+
await sleep(1000);
44+
45+
// Click compare checkbox on first 3 featured cards
46+
const cards = page.locator('button[aria-label*="Add"][aria-label*="compare"]');
47+
const count = await cards.count();
48+
const toClick = Math.min(count, 3);
49+
50+
for (let i = 0; i < toClick; i++) {
51+
// Hover the card to reveal the checkbox, then click
52+
const card = cards.nth(i);
53+
await card.scrollIntoViewIfNeeded();
54+
await sleep(300);
55+
// Hover parent card to trigger group-hover
56+
const parentCard = card.locator("xpath=ancestor::a").first();
57+
await parentCard.hover();
58+
await sleep(400);
59+
await card.click();
60+
await sleep(600);
61+
}
62+
63+
await sleep(800);
64+
65+
// Click "Compare" button in the tray
66+
const compareBtn = page.locator('button:has-text("Compare")');
67+
await compareBtn.click();
68+
await sleep(2500);
69+
70+
// Scroll inside the drawer to show content
71+
const drawerBody = page.locator(".overflow-y-auto").first();
72+
await drawerBody.evaluate((el) =>
73+
el.scrollBy({ top: 300, behavior: "smooth" })
74+
);
75+
await sleep(1500);
76+
77+
// Close drawer
78+
await page.keyboard.press("Escape");
79+
await sleep(1000);
80+
81+
// Close context to finalize video
82+
await context.close();
83+
await browser.close();
84+
85+
// Find the recorded webm
86+
const { readdirSync } = await import("fs");
87+
const videos = readdirSync(VIDEO_DIR).filter((f) => f.endsWith(".webm"));
88+
if (videos.length === 0) {
89+
console.error("No video recorded");
90+
process.exit(1);
91+
}
92+
const videoPath = path.join(VIDEO_DIR, videos[0]);
93+
94+
// Convert to GIF with ffmpeg
95+
console.log("Converting to GIF...");
96+
execSync(
97+
`ffmpeg -y -i "${videoPath}" -vf "fps=12,scale=${WIDTH}:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3" -loop 0 "${OUTPUT_GIF}"`,
98+
{ stdio: "inherit" }
99+
);
100+
101+
// Cleanup
102+
execSync(`rm -rf "${VIDEO_DIR}"`);
103+
104+
const { statSync } = await import("fs");
105+
const size = (statSync(OUTPUT_GIF).size / 1024 / 1024).toFixed(1);
106+
console.log(`Done! ${OUTPUT_GIF} (${size}MB)`);
107+
}
108+
109+
main().catch((err) => {
110+
console.error(err);
111+
process.exit(1);
112+
});

0 commit comments

Comments
 (0)