#!/usr/bin/env bun // Real desktop-app frames for the teaser's desktop scenes: the Vite dev server of apps/desktop in fixture mode // (VITE_FIXTURES=1: example.com-style fixture data, no Tauri, no store accounts), captured headlessly, dark theme, // with the two dev-build markers hidden (they are not in a release build). // Writes media/teaser/out/desktop/*.jpg plus boxes.json (callout targets, as fractions of the frame). // portfolio.jpg Portfolio: every app of every studio, grouped by studio // portfolio-apps.jpg the same, scrolled to the Apps table: both stores per app, rollout %, crash-rate sparklines // growth.jpg Prism Puddle's Growth tab: the store analytics chart (visx), pointer outside // growth-NN.jpg the same while the pointer (real mouse moves) sweeps across the chart: one frame per hovered // day, the crosshair and tooltip showing that day's impressions, downloads and conversion // health.jpg the Health tab: the crash and ANR rate charts, pointer outside // health-NN.jpg the pointer sweeping across the crash-rate chart, one frame per hovered day // prices.jpg the Prices tab: products x key territories, both stores' prices per country, gaps marked // releases.jpg Prism Puddle's release timeline, both stores rolling out at 20% // releases-empty.jpg the same frame with the rollout bars' fill and "20%" hidden (the video fills the bar) // checks.jpg the Checks tab: the "Rejection risks" card with its findings // checks-clean.jpg the same tab after the fixes: the fixture's scan answer with no findings (the app's own // "No rejection risks found" state; the fixture module is served with an empty findings list) // ready.jpg the same tab scrolled to "Store readiness" (validate, both stores) after the fixes: the // fixture's validate answer with no findings (the app's own "No blockers" state) // console-plan.jpg App info > Console steps > App Privacy > Plan: the console plan panel with the REAL plan // (`storebound console plan apple-app-privacy --json` run in a fresh `storebound init` folder, // served in place of the fixture's shortened plan), scrolled to the answer sheet's end and the // final button that stays the owner's (Publish) with the never-press list // console-plan-top.jpg the same panel at its top: the start page and the first steps // resubmit.jpg Tidy Lists' Releases > Send for review: back from App Review (UNRESOLVED_ISSUES), the rejected // version item, build 52 picked, Resubmit // submit-ready.jpg Prism Puddle's Releases > Send for review: build 52 picked, before "Submit for review" is pressed // submit-plan.jpg Prism Puddle's Send for review: build 52 picked, Submit for review pressed: the plan panel with // the dry run (nothing sent) and "Continue", the in-app step before the system dialog (the // native macOS dialog is not part of the web view, so it is never drawn here) // submit-waiting.jpg the same card after the submit: the fixture's apple status with the new submission waiting // for review (the app's own "Waiting for review" state) // render.ts runs it when out/desktop/ is missing; run it by hand after a UI change: bun media/teaser/desktop.ts import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { launch } from '../../cli/shots/browser'; const DIR = import.meta.dir; const ROOT = join(DIR, '../..'); const DEFAULT_OUT = join(DIR, 'out/desktop'); export const VIEW = { width: 1280, height: 800 }; const log = (m: string): void => console.log(`${new Date().toISOString().slice(11, 19)} [desktop] ${m}`); type Box = { x: number; y: number; w: number; h: number }; /** One frame of a hover sweep: the pointer position (fraction of the frame) at which this day starts to show. */ type SweepFrame = { file: string; px: number; py: number; day: string }; /** Hover sweeps: the chart, the part of its width the pointer crosses, the height it moves at. */ const SWEEPS = { growth: { start: 'app:growth', svg: '.lchart svg', x: [0.2, 0.86], y: 0.42 }, health: { start: 'app:health', svg: '.card:has-text("User-perceived crash rate") .spark svg', x: [0.12, 0.9], y: 0.45 }, } as const; /** The real `storebound console plan