Center the installer screens and move the effects to ttfx

The progress screen and the finish screen now center vertically the way the
greeter does, instead of stacking from row one. Both derive their block height
from the logo, so the frame the installer sits on for several minutes is
composed rather than top-aligned. The progress screen redraws itself if the
console resizes, which the framebuffer still does late in VMs.

Centering the finish screen turned up a spacing bug: gum confirm paints
prompt/blank/button from the cursor and clears all three rows, so anchoring one
row below the title left three blank rows under it, not one. Anchoring a row
higher closes that to the two rows gum allows, one more than the greeter's frame.

Swap python-terminaltexteffects for ttfx. The flags carry over, but two things
do not. ttfx resolves even indexed stops to truecolor, so the greeter needs
--xterm-colors to keep its palette indexed: the console reduces 256-colour codes
well and 24-bit ones badly, and the settle colour has to match the logo drawn
underneath with gum style --foreground 2. And ttfx centres its text two columns
right of plain centering, so both screens hand it a canvas two columns narrower
than the console or the logo jumps sideways when the effect starts.

The finish screen uses smoke rather than laseretch, at 60fps rather than 260 —
smoke finishes in under a second at the rate laseretch was tuned for, which
reads as a flicker instead of a send-off. Smoke also survives the console's
16-colour collapse better, carrying its motion in glyphs rather than gradients.

ttfx is in the ISO's package list and timeout is coreutils, so neither needs an
availability check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Heinemeier HanssonandClaude Opus 5 committed 2026-08-10 09:19:44 -07:00
1 parent a12bfea7a8
commit e5f2b466ef
3 files changed
+96 -43

No files matched your search

+33 -27
View File
@@ -121,36 +121,42 @@ greeter() {
# ColorShift the logo: a green base (indexed color 2) with a cyan accent (6)
# drifting through, settling on green. Indexed ANSI colors, not hex — the
# framebuffer console can't render tte's truecolor faithfully (it crushes the
# framebuffer console can't render truecolor faithfully (it crushes the
# palette to a muddy lavender), but the 16 indexed colors map to the Tokyo
# Night palette and render true. One long-running invocation (many cycles) so
# it never restarts — a restart is what flashed. The effect reads from
# /dev/null so it never swallows the Return; --reuse-canvas paints upward from
# the saved cursor, anchored one row below the logo.
if command -v tte >/dev/null 2>&1; then
printf '\033[%d;1H\0337' "$((logo_row + logo_h))"
# Run tte directly (not inside a `while` subshell) so $anim is tte's own
# PID: killing a wrapping subshell would orphan tte, which then keeps
# painting the logo over the keyboard step. --cycles is high enough that it
# never ends on its own before Return.
tte -i "$LOGO_PATH" \
--canvas-width "$cols" \
--anchor-text c \
--frame-rate 60 \
--reuse-canvas \
colorshift \
--gradient-stops 2 10 6 10 \
--gradient-frames 3 \
--cycles 1000 \
--final-gradient-stops 2 \
</dev/null >/dev/tty 2>/dev/null &
anim=$!
fi
# Night palette and render true. --xterm-colors is what keeps them indexed:
# ttfx resolves even indexed stops to truecolor otherwise, which would put the
# settle color out of step with the `gum style --foreground 2` logo underneath.
# One long-running invocation (many cycles) so it never restarts — a restart is
# what flashed. The effect reads from /dev/null so it never swallows the
# Return; --reuse-canvas paints upward from the saved cursor, anchored one row
# below the logo.
printf '\033[%d;1H\0337' "$((logo_row + logo_h))"
# Run ttfx directly (not inside a `while` subshell) so $anim is ttfx's own
# PID: killing a wrapping subshell would orphan ttfx, which then keeps
# painting the logo over the keyboard step. --cycles is high enough that it
# never ends on its own before Return.
# Two columns narrower than the console: ttfx centers its text two columns
# right of plain centering, so a full-width canvas would sit the animated
# logo a column off the gum-drawn one underneath it.
ttfx -i "$LOGO_PATH" \
--canvas-width "$((cols > 2 ? cols - 2 : cols))" \
--anchor-text c \
--frame-rate 60 \
--reuse-canvas \
--xterm-colors \
colorshift \
--gradient-stops 2 10 6 10 \
--gradient-frames 3 \
--cycles 1000 \
--final-gradient-stops 2 \
</dev/null >/dev/tty 2>/dev/null &
anim=$!
IFS= read -r _ </dev/tty || true
# Tolerate tte's kill signal in `wait` so a future `set -e` here can't abort.
[[ -n ${anim:-} ]] && { kill "$anim" 2>/dev/null; wait "$anim" 2>/dev/null || true; }
# tte leaves the tty in raw/no-echo mode when killed; restore it or the gum
# Tolerate ttfx's kill signal in `wait` so a future `set -e` here can't abort.
kill "$anim" 2>/dev/null
wait "$anim" 2>/dev/null || true
# ttfx leaves the tty in raw/no-echo mode when killed; restore it or the gum
# prompts in the keyboard step that follows silently die.
stty sane </dev/tty 2>/dev/null || true
# Reset the screen so the leftover animation frame doesn't linger under the