apps/desktop/electron-forge/setup-icon/generate/README.md
setup-banner.gif and setup-banner-dev.gif (one directory up) are the splash screens shown by the
Squirrel.Windows installer while it extracts the app (loadingGif in electron-forge/forge.config.ts).
Squirrel only accepts GIF, shows it at native pixel size in a borderless window, and provides no
progress bar — the animation is the user's only feedback that the install is running.
Both files are generated from splash.html in this directory. Do not edit the GIFs by hand;
change the HTML/timeline and regenerate:
pnpm --filter desktop generate-setup-banners
Run it on Windows so the wordmark renders in Segoe UI (the font is rasterized into the GIF).
splash.html is a deterministic frame source: window.renderFrame(t) computes every element's
opacity/transform at absolute time t seconds (no CSS animations), so output is reproducible.
window.SPLASH_TIMELINE is the [{ t, delay }] frame schedule. ?variant=nightly switches to the
purple palette and shows the NIGHTLY badge.generate.mts walks the timeline in headless Chromium (via the repo's @playwright/test),
screenshots each frame, and encodes an animated GIF with gifenc — no external binaries needed.REVEAL_DURATION / LOOP_DURATION /
STEADY_FPS in splash.html.Update.exe renders the GIF with WpfAnimatedGif,
which pre-decodes every frame to a full-size (640×480) bitmap held in memory, in a 32-bit
process (verified: electron-winstaller's vendored Squirrel.exe is x86). Memory scales with
frame count, not file size — ~1.2 MiB per frame. The current 108 frames ≈ 127 MiB; a 100 s / 1000-
frame GIF would be ~1.2 GiB and risks an OutOfMemoryException. The low-fps steady tail exists to
hold the count down.Update.exe is WindowStyle.None +
transparent). The splash is a flat dark surface with a 1px hairline. The surface is dark on
purpose: the installer ships one baked GIF that can't follow the user's Windows theme, and no opaque
surface is theme-neutral — a dark surface degrades more gracefully than a light one (gentle on a
dark desktop, unremarkable on a light one, whereas a light one is a harsh bright flash for dark-mode
users). The colorful trillium also pops on it. The one cost is that the mark's white "starburst"
(the surface showing through the leaf gaps) becomes dark.dispose: 1). This shrinks the mostly-static
steady tail to almost nothing (~140 KiB total). It saves file size only — not Update.exe memory,
which re-expands every frame to full size regardless.applyPalette nearest-color search is approximate, so without pinning a big flat area drifts (the
white card, in an earlier light design, used to go warm). deriveAnchorColors() reads the surface
and hairline from the first rendered frame — so changing the surface tone in splash.html needs no
change here — and pins the wordmark colour explicitly (update it there if the wordmark changes).#26292f) + 1px hairline (#484d57). Transparency is used only for delta
frames — frame 0 is a full opaque frame, so the loop resets cleanly with no reliance on desktop
show-through. 108 frames, ~20 s loop, infinite loop, 255-color global palette, ~140 KiB.Update.exe is not
DPI-aware, so on a HiDPI display the window is bilinear-stretched by the display scale factor
(e.g. 1.5×) and there is no way to signal density through a GIF. Bolder artwork survives that
stretch better — it cannot be made pixel-sharp from our side.icon-color.svg); the nightly variant remaps each leaf to a purple ramp.