● Zion Boggan zionboggan.com ↗

Rebuild walkthrough from live screen-recordings (smooth scroll + cursor tours)

ec2d6ef   Zion Boggan committed on Jun 25, 2026 (1 month ago)
apps/web/src/components/smooth-scroll.tsx +1 -0
@@ -7,6 +7,7 @@ export function SmoothScroll({ children }: { children: ReactNode }) {
useEffect(() => {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const lenis = new Lenis({ duration: 1.1, smoothWheel: true });
+ (window as unknown as { __lenis?: Lenis }).__lenis = lenis;
let raf = 0;
const loop = (t: number) => {
lenis.raf(t);
promo/.gitignore +1 -0
@@ -10,3 +10,4 @@ __pycache__/
node_modules
recap*.mjs
+rec/
promo/build_video2_win.py +88 -0
@@ -0,0 +1,88 @@
+import json, os, shutil, subprocess
+
+FF = r"C:\Users\homelab\ffbuild71\ffmpeg-n7.1-latest-win64-gpl-7.1\bin\ffmpeg.exe"
+ROOT = r"C:\Users\homelab\stepping-promo"
+REC = ROOT + r"\rec"
+CLIPS = ROOT + r"\build\clips2"
+os.makedirs(CLIPS, exist_ok=True)
+os.makedirs(ROOT + r"\fonts", exist_ok=True)
+os.chdir(ROOT)
+
+def cpfont(cands, dst):
+ for s in cands:
+ if os.path.exists(s):
+ shutil.copy(s, dst); return
+ raise SystemExit("no font " + dst)
+cpfont([r"C:\Windows\Fonts\consolab.ttf", r"C:\Windows\Fonts\consola.ttf"], ROOT + r"\fonts\mono.ttf")
+cpfont([r"C:\Windows\Fonts\arialbd.ttf", r"C:\Windows\Fonts\arial.ttf"], ROOT + r"\fonts\sans.ttf")
+
+FPS, W, H = 30, 1920, 1080
+MONO, SANS = "fonts/mono.ttf", "fonts/sans.ttf"
+GOLD, BONE, STAGE = "0xC8A24C", "0xF2ECE0", "0x0F0E0C"
+VC = "-c:v h264_nvenc -pix_fmt yuv420p -preset p5 -rc vbr -cq 20 -b:v 0"
+
+meta = json.load(open(ROOT + r"\voiceover\scene_times.json"))
+ss, total, TAIL = meta["scene_start"], meta["total"], 1.6
+order = ["s1","s2","s3","s4","s5","s6","s7","s8","s9","s10"]
+cfg = {
+ "s1": ("desk", "STEPPING / DIVINE NINE STEP CULTURE"),
+ "s2": ("desk", "CHOOSE YOUR CITY"),
+ "s3": ("desk", "THE LINE-UP / UPCOMING SHOWS"),
+ "s4": ("desk", "EVERY SHOW, IN DETAIL"),
+ "s5": ("desk", "THE YARD / THE COMMUNITY"),
+ "s6": ("desk", "PROFILES / WHO'S WHO"),
+ "s7": ("desk", "THE ROUTE / PLAN THE TRIP"),
+ "s8": ("phone", "RIGHT IN YOUR PHONE"),
+ "s9": ("desk", "MORE ON THE WAY"),
+ "s10": ("end", ""),
+}
+
+def dur(i):
+ s = order[i]
+ return round((ss[order[i+1]] - ss[s]) if i < len(order)-1 else (total - ss[s] + TAIL), 3)
+
+def esc(t): return t.replace(":", "\\:").replace("'", "’").replace(",", "\\,")
+def cap(t):
+ if not t: return ""
+ return (f",drawtext=fontfile={MONO}:text='{esc(t)}':fontcolor={GOLD}:fontsize=30"
+ f":x=88:y=h-96:box=1:boxcolor={STAGE}@0.6:boxborderw=22")
+def q(p): return '"' + p + '"'
+def run(cmd):
+ r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
+ if r.returncode != 0:
+ print("ERR:", r.stderr[-1400:]); raise SystemExit(1)
+
+clips = []
+for i, s in enumerate(order):
+ kind, c = cfg[s]; d = dur(i); out = f"{CLIPS}\\{s}.mp4"
+ fin = "fade=t=in:st=0:d=0.4,fade=t=out:st=%.3f:d=0.4" % max(d-0.4, 0)
+ src = q(f"{REC}\\{s}.webm")
+ if kind == "desk":
+ vf = f"scale={W}:{H}:force_original_aspect_ratio=increase,crop={W}:{H},fps={FPS},{fin}{cap(c)}"
+ run(f'{q(FF)} -y -sseof -{d} -t {d} -i {src} -vf "{vf}" -r {FPS} {VC} -an {q(out)}')
+ elif kind == "phone":
+ fc = (f"color={STAGE}:s={W}x{H}:d={d}:r={FPS}[bg];[0:v]scale=-1:1000,fps={FPS}[ph];"
+ f"[bg][ph]overlay=(W-w)/2:(H-h)/2,{fin}{cap(c)}")
+ run(f'{q(FF)} -y -sseof -{d} -t {d} -i {src} -filter_complex "{fc}" -r {FPS} {VC} -an {q(out)}')
+ else:
+ vf = (f"drawtext=fontfile={SANS}:text='STEPPING':fontcolor={BONE}:fontsize=120:x=(w-text_w)/2:y=(h-text_h)/2-40,"
+ f"drawtext=fontfile={MONO}:text='BUILT FOR THE CULTURE':fontcolor={GOLD}:fontsize=30:x=(w-text_w)/2:y=(h/2)+70,"
+ f"fade=t=in:st=0:d=0.5")
+ run(f'{q(FF)} -y -f lavfi -i color={STAGE}:s={W}x{H}:d={d}:r={FPS} -vf "{vf}" {VC} -an {q(out)}')
+ clips.append(out); print(f" {s} {d}s ok")
+
+with open(f"{CLIPS}\\list.txt", "w") as f:
+ for c in clips: f.write("file '" + c.replace("\\", "/") + "'\n")
+run(f'{q(FF)} -y -f concat -safe 0 -i {q(CLIPS + chr(92) + "list.txt")} -c copy {q(ROOT + chr(92) + "build" + chr(92) + "video2_silent.mp4")}')
+
+vlen = round(total + TAIL, 3); fadest = round(total - 0.3, 3)
+af = (f"[0:a]aresample=44100,aformat=channel_layouts=stereo,loudnorm=I=-16:TP=-1.5:LRA=11,apad=whole_dur={vlen},asplit=2[vo][key];"
+ f"[1:a]atrim=0:{vlen},asetpts=N/SR/TB,aresample=44100,aformat=channel_layouts=stereo,volume=0.22,afade=t=in:st=0:d=2,afade=t=out:st={fadest}:d=2.0[m];"
+ f"[m][key]sidechaincompress=threshold=0.035:ratio=9:attack=15:release=350:makeup=1[md];"
+ f"[vo][md]amix=inputs=2:duration=longest:normalize=0,alimiter=limit=0.97[out]")
+run(f'{q(FF)} -y -i {q(ROOT + chr(92) + "voiceover" + chr(92) + "stepping_vo.wav")} -i {q(ROOT + chr(92) + "assets" + chr(92) + "music_inspired.mp3")} '
+ f'-filter_complex "{af}" -map "[out]" -ar 44100 -ac 2 -c:a aac -b:a 192k {q(ROOT + chr(92) + "build" + chr(92) + "audio2.m4a")}')
+
+run(f'{q(FF)} -y -i {q(ROOT + chr(92) + "build" + chr(92) + "video2_silent.mp4")} -i {q(ROOT + chr(92) + "build" + chr(92) + "audio2.m4a")} '
+ f'-map 0:v -map 1:a -c:v copy -c:a copy -shortest {q(ROOT + chr(92) + "build" + chr(92) + "stepping_walkthrough_v2.mp4")}')
+print("DONE", vlen)
promo/record.mjs +71 -0
@@ -0,0 +1,71 @@
+import { chromium } from "playwright-core";
+import { readFileSync, readdirSync, renameSync, mkdirSync } from "fs";
+
+const EXE = "/root/.npm/_npx/361ceb562f3b3235/node_modules/playwright-core/.local-browsers/chromium-1228/chrome-linux64/chrome";
+const SCR = "/tmp/claude-0/-shared-projects/26d7af11-61c4-4183-85c3-70d9aac72ef6/scratchpad";
+const REC = "/shared/projects/SteppingApp/promo/rec";
+const BASE = "http://REDACTED-IP:3000";
+const DJ = readFileSync(`${SCR}/.djid`, "utf8").trim();
+const EV = readFileSync(`${SCR}/.evid`, "utf8").trim();
+
+const scenes = [
+ { id: "s1", url: "/", vp: [1920, 1080], dur: 11.3, act: "hero" },
+ { id: "s2", url: "/", vp: [1920, 1080], dur: 7.45, act: "toRoute" },
+ { id: "s3", url: "/city/atlanta", vp: [1920, 1080], dur: 12.15, act: "scroll" },
+ { id: "s4", url: `/city/atlanta/event/${EV}`, vp: [1920, 1080], dur: 8.18, act: "scroll" },
+ { id: "s5", url: "/city/atlanta/yard", vp: [1920, 1080], dur: 11.3, act: "scroll" },
+ { id: "s6", url: `/city/atlanta/profile/dj/${DJ}`, vp: [1920, 1080], dur: 5.55, act: "hold" },
+ { id: "s7", url: "/city/atlanta/route", vp: [1920, 1080], dur: 11.5, act: "scroll" },
+ { id: "s8", url: "/", vp: [430, 932], dur: 10.25, act: "scroll", mobile: true },
+ { id: "s9", url: "/", vp: [1920, 1080], dur: 9.2, act: "hero" },
+];
+
+const args = ["--no-sandbox", "--disable-dev-shm-usage", "--use-gl=angle", "--use-angle=swiftshader", "--enable-unsafe-swiftshader", "--ignore-gpu-blocklist"];
+const browser = await chromium.launch({ executablePath: EXE, args });
+
+for (const s of scenes) {
+ const dir = `${REC}/${s.id}`;
+ mkdirSync(dir, { recursive: true });
+ const ctx = await browser.newContext({
+ viewport: { width: s.vp[0], height: s.vp[1] },
+ deviceScaleFactor: 1,
+ recordVideo: { dir, size: { width: s.vp[0], height: s.vp[1] } },
+ ...(s.mobile ? { isMobile: true, hasTouch: true } : {}),
+ });
+ const page = await ctx.newPage();
+ await page.goto(`${BASE}${s.url}`, { waitUntil: "networkidle" });
+ await page.waitForTimeout(s.act === "hero" ? 1800 : 900);
+ const ms = s.dur * 1000;
+
+ if (s.act === "hero") {
+ const [W, H] = s.vp;
+ const pts = [[W * 0.6, H * 0.42], [W * 0.72, H * 0.55], [W * 0.54, H * 0.5], [W * 0.68, H * 0.46]];
+ const per = ms / pts.length;
+ for (const [x, y] of pts) {
+ await page.mouse.move(x, y, { steps: 24 });
+ await page.mouse.click(x, y);
+ await page.waitForTimeout(Math.max(per - 250, 200));
+ }
+ } else if (s.act === "toRoute") {
+ await page.evaluate(() => {
+ const l = window.__lenis; const el = document.querySelector("#route");
+ if (el && l) l.scrollTo(el.offsetTop - 90, { duration: 4.5 });
+ });
+ await page.waitForTimeout(ms);
+ } else if (s.act === "scroll") {
+ await page.evaluate((d) => {
+ const l = window.__lenis; const t = document.body.scrollHeight - window.innerHeight;
+ if (l) l.scrollTo(t, { duration: d }); else window.scrollTo({ top: t, behavior: "smooth" });
+ }, s.dur - 1.5);
+ await page.waitForTimeout(ms);
+ } else {
+ await page.waitForTimeout(ms);
+ }
+
+ await ctx.close();
+ const f = readdirSync(dir).find((x) => x.endsWith(".webm"));
+ renameSync(`${dir}/${f}`, `${REC}/${s.id}.webm`);
+ console.log(s.id, "recorded");
+}
+await browser.close();
+console.log("ALL RECORDED");
promo/rerecord.mjs +46 -0
@@ -0,0 +1,46 @@
+import { chromium } from "playwright-core";
+import { readFileSync, readdirSync, renameSync, mkdirSync } from "fs";
+
+const EXE = "/root/.npm/_npx/361ceb562f3b3235/node_modules/playwright-core/.local-browsers/chromium-1228/chrome-linux64/chrome";
+const SCR = "/tmp/claude-0/-shared-projects/26d7af11-61c4-4183-85c3-70d9aac72ef6/scratchpad";
+const REC = "/shared/projects/SteppingApp/promo/rec";
+const BASE = "http://REDACTED-IP:3000";
+const DJ = readFileSync(`${SCR}/.djid`, "utf8").trim();
+const EV = readFileSync(`${SCR}/.evid`, "utf8").trim();
+
+const tours = {
+ s3: { url: "/city/atlanta", dur: 12.15, pts: [[600,275],[455,745],[1245,755],[455,845],[1245,852],[1165,527],[345,272],[700,668]] },
+ s4: { url: `/city/atlanta/event/${EV}`, dur: 8.18, pts: [[600,200],[600,463],[690,533],[820,625],[895,864],[660,403]] },
+ s6: { url: `/city/atlanta/profile/dj/${DJ}`, dur: 5.55, pts: [[210,130],[520,300],[820,430],[430,500],[300,200]] },
+};
+
+const args = ["--no-sandbox","--disable-dev-shm-usage","--use-gl=angle","--use-angle=swiftshader","--enable-unsafe-swiftshader","--ignore-gpu-blocklist"];
+const browser = await chromium.launch({ executablePath: EXE, args });
+
+for (const [id, t] of Object.entries(tours)) {
+ const dir = `${REC}/${id}`;
+ mkdirSync(dir, { recursive: true });
+ const ctx = await browser.newContext({
+ viewport: { width: 1920, height: 1080 }, deviceScaleFactor: 1,
+ recordVideo: { dir, size: { width: 1920, height: 1080 } },
+ });
+ const page = await ctx.newPage();
+ await page.goto(`${BASE}${t.url}`, { waitUntil: "networkidle" });
+ await page.mouse.move(960, 540);
+ await page.waitForTimeout(900);
+ const end = Date.now() + t.dur * 1000;
+ let i = 0;
+ while (Date.now() < end) {
+ const [x, y] = t.pts[i % t.pts.length];
+ await page.mouse.move(x, y, { steps: 28 });
+ if (i % 3 === 2) await page.mouse.click(x, y);
+ await page.waitForTimeout(420);
+ i++;
+ }
+ await ctx.close();
+ const f = readdirSync(dir).find((x) => x.endsWith(".webm"));
+ renameSync(`${dir}/${f}`, `${REC}/${id}.webm`);
+ console.log(id, "re-recorded (cursor tour)");
+}
+await browser.close();
+console.log("DONE");