#!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")/.." ASSETS="assets/screenshots" LIGHT="$ASSETS/status.png" DARK="$ASSETS/status-dark.png" COLLAGE="$ASSETS/status-collage.png" PORT="${PORT:-17645}" URL="http://127.0.0.1:$PORT" CHROMIUM="$(command -v chromium || command -v chromium-browser || true)" if [ -z "$CHROMIUM" ]; then echo "error: chromium not found (install chromium or chromium-browser)" >&2 exit 1 fi if python3 -c 'import PIL' 2>/dev/null; then HAVE_PIL=1 else HAVE_PIL=0 echo "note: python3-PIL not found, skipping framing and collage" >&2 fi echo "building frontend..." bun run build echo "building server..." CGO_ENABLED=1 go build -o bin/statuspage ./server cleanup() { if [ -n "${SERVER_PID:-}" ]; then kill "$SERVER_PID" 2>/dev/null || true fi } trap cleanup EXIT echo "starting mock server on $URL" ./bin/statuspage --mock --addr "127.0.0.1:$PORT" & SERVER_PID=$! for _ in $(seq 1 50); do if curl -sf "$URL/api/status" >/dev/null 2>&1; then break fi sleep 0.1 done if ! curl -sf "$URL/api/status" >/dev/null 2>&1; then echo "error: mock server did not come up" >&2 exit 1 fi mkdir -p "$ASSETS" # $1=theme $2=out $3=shadow $4=border capture() { local theme="$1" out="$2" shadow="$3" border="$4" if [ -f "$out" ] && ! find src index.html public server go.mod go.sum \ -type f -newer "$out" -print -quit 2>/dev/null | grep -q .; then echo "up to date, skipping ($out)" return fi "$CHROMIUM" --headless=new --disable-gpu --hide-scrollbars \ --window-size=1280,720 \ --force-device-scale-factor=2 \ --screenshot="$out" \ --virtual-time-budget=8000 \ "$URL/?theme=$theme" 2>/dev/null # frame the capture: rounded corners, 2px border, hard offset shadow (2x for device scale) if [ "$HAVE_PIL" = 1 ]; then python3 - "$out" "$shadow" "$border" <<'PY' import sys from PIL import Image, ImageDraw path = sys.argv[1] img = Image.open(path).convert("RGB") W, H = img.size r = 16 # 0.5rem radius b = 4 # 2px border s = 12 # 0.35rem shadow offset m = 24 # margin around the frame BORDER = tuple(int(c) for c in sys.argv[3].split(",")) shadow_parts = [int(c) for c in sys.argv[2].split(",")] if len(shadow_parts) == 3: shadow_parts.append(255) SHADOW = tuple(shadow_parts) mask = Image.new("L", (W, H), 0) ImageDraw.Draw(mask).rounded_rectangle([0, 0, W - 1, H - 1], radius=r, fill=255) img_r = img.copy() img_r.putalpha(mask) bw, bh = W + 2 * b, H + 2 * b canvas = Image.new("RGBA", (bw + 2 * m + s, bh + 2 * m + s), (0, 0, 0, 0)) d = ImageDraw.Draw(canvas) d.rounded_rectangle( [m + s, m + s, m + s + bw - 1, m + s + bh - 1], radius=r, fill=SHADOW) d.rounded_rectangle( [m, m, m + bw - 1, m + bh - 1], radius=r, fill=BORDER + (255,)) canvas.alpha_composite(img_r, (m + b, m + b)) canvas.save(path) print(f"framed {path}") PY fi echo "wrote $out" } capture light "$LIGHT" "0,0,0,136" "17,17,17" capture dark "$DARK" "0,0,0,136" "58,64,58" # collage: rebuild only when a variant is newer than the existing collage if [ "$HAVE_PIL" = 1 ]; then if [ -f "$COLLAGE" ] && [ "$COLLAGE" -nt "$LIGHT" ] && [ "$COLLAGE" -nt "$DARK" ]; then echo "collage up to date, skipping ($COLLAGE)" else python3 - "$LIGHT" "$DARK" "$COLLAGE" <<'PY' import sys from PIL import Image light = Image.open(sys.argv[1]).convert("RGBA") dark = Image.open(sys.argv[2]).convert("RGBA") W, H = light.size ox, oy = 480, 160 canvas = Image.new("RGBA", (W + ox, H + oy), (0, 0, 0, 0)) canvas.alpha_composite(dark, (0, 0)) canvas.alpha_composite(light, (ox, oy)) canvas.thumbnail((1920, 1080), Image.LANCZOS) canvas.save(sys.argv[3]) print(f"wrote {sys.argv[3]}") PY fi fi