mirror of
https://github.com/luxfi/financial.git
synced 2026-10-30 16:55:56 +00:00
The header carries the LUX wordmark; the triangle stays for the chat launcher, larger, borderless, glowing only on hover. In-page demos brand as Triangle with the filled down triangle. Six product screens animate as 8-second looping mp4s (~150KB each) recorded from screens/*.html by screens/record.mjs and shown in laptop frames on the home page. Co-authored-by: Hanzo Dev <[email protected]>
64 lines
2.4 KiB
JavaScript
64 lines
2.4 KiB
JavaScript
// Record each animated screen to a small looping mp4 + jpg poster.
|
|
// Usage: node screens/record.mjs [name ...] (default: all)
|
|
// Requires: npx playwright (chromium), ffmpeg on PATH.
|
|
import { execFileSync } from "node:child_process";
|
|
import { mkdirSync, readdirSync, statSync, rmSync } from "node:fs";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const outDir = join(here, "..", "public", "screens");
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
const names = process.argv.slice(2).length
|
|
? process.argv.slice(2)
|
|
: readdirSync(here).filter((f) => f.endsWith(".html")).map((f) => f.replace(".html", ""));
|
|
|
|
const LOOP_MS = 8000;
|
|
const W = 1440, H = 900;
|
|
|
|
const { chromium } = await import("playwright");
|
|
const browser = await chromium.launch();
|
|
|
|
for (const name of names) {
|
|
const page = await browser.newPage({ viewport: { width: W, height: H }, deviceScaleFactor: 1 });
|
|
await page.goto(pathToFileURL(join(here, `${name}.html`)).href);
|
|
await page.evaluate(() => document.fonts.ready);
|
|
await page.waitForTimeout(300);
|
|
|
|
// capture the second full cycle so the loop point is seamless
|
|
const fps = 30;
|
|
const frames = (LOOP_MS / 1000) * fps;
|
|
const frameDir = join(outDir, `.${name}-frames`);
|
|
rmSync(frameDir, { recursive: true, force: true });
|
|
mkdirSync(frameDir, { recursive: true });
|
|
|
|
// wait until the start of the next cycle boundary, then step frames
|
|
await page.waitForTimeout(LOOP_MS - 300);
|
|
const t0 = Date.now();
|
|
for (let i = 0; i < frames; i++) {
|
|
const target = t0 + (i * 1000) / fps;
|
|
const wait = target - Date.now();
|
|
if (wait > 0) await page.waitForTimeout(wait);
|
|
await page.screenshot({ path: join(frameDir, `f${String(i).padStart(4, "0")}.png`) });
|
|
}
|
|
await page.close();
|
|
|
|
execFileSync("ffmpeg", [
|
|
"-y", "-framerate", String(fps), "-i", join(frameDir, "f%04d.png"),
|
|
"-vf", "scale=1280:-2", "-c:v", "libx264", "-preset", "slow", "-crf", "30",
|
|
"-pix_fmt", "yuv420p", "-movflags", "+faststart", "-an",
|
|
join(outDir, `${name}.mp4`),
|
|
], { stdio: "pipe" });
|
|
execFileSync("ffmpeg", [
|
|
"-y", "-i", join(frameDir, "f0059.png"), "-vf", "scale=1280:-2", "-q:v", "6",
|
|
join(outDir, `${name}.jpg`),
|
|
], { stdio: "pipe" });
|
|
rmSync(frameDir, { recursive: true, force: true });
|
|
|
|
const kb = Math.round(statSync(join(outDir, `${name}.mp4`)).size / 1024);
|
|
console.log(`${name}.mp4 ${kb} KB`);
|
|
}
|
|
|
|
await browser.close();
|