diff --git a/.agents/skills/screenshots/.gitignore b/.agents/skills/screenshots/.gitignore new file mode 100644 index 0000000..b744346 --- /dev/null +++ b/.agents/skills/screenshots/.gitignore @@ -0,0 +1 @@ +debug/ diff --git a/.agents/skills/screenshots/SKILL.md b/.agents/skills/screenshots/SKILL.md new file mode 100644 index 0000000..8b5d061 --- /dev/null +++ b/.agents/skills/screenshots/SKILL.md @@ -0,0 +1,67 @@ +--- +name: screenshots +description: Capture and regenerate SwiftShift marketing screenshots for README/website assets. Use when updating light/dark SwiftShift screenshots, especially `www/src/images/screenshot-light.jpg` and `www/src/images/screenshot-dark.jpg`. +--- + +# SwiftShift screenshots + +This skill regenerates the app settings screenshots as clean 843×670 composites: + +- clean background + empty menu bar skeletons live in `assets/bg-light.jpg` and `assets/bg-dark.jpg` +- real desktop wallpapers live in `assets/wallpaper-light.jpg` and `assets/wallpaper-dark.jpg` +- fresh Swift Shift Dev settings window is captured from the local build using macOS window capture plus a full-screen tint crop +- full screenshot outputs default to `www/src/images/screenshot-light.jpg` and `www/src/images/screenshot-dark.jpg` +- mouse feature crops default to `www/src/images/mouse-light.jpg` and `www/src/images/mouse-dark.jpg` +- debug step images are written to `.agents/skills/screenshots/debug/` + +## Requirements + +- macOS +- `peekaboo` with Screen Recording and Accessibility permissions +- ImageMagick (`magick`): `brew install imagemagick` +- `python3` and macOS `screencapture` +- Xcode command line build works for this repo + +## Command + +From the repo root: + +```bash +.agents/skills/screenshots/bin/capture-swiftshift-screenshots.sh +``` + +Optional output directory: + +```bash +.agents/skills/screenshots/bin/capture-swiftshift-screenshots.sh /tmp/swiftshift-screenshots +``` + +## What the script does + +1. Saves current appearance and wallpaper. +2. Builds and runs the local dev app with `make build && make run-app`. +3. Temporarily switches to light mode, applies `assets/wallpaper-light.jpg` as the real desktop wallpaper, hides other apps, and opens the Swift Shift menu bar window. +4. Captures the window-only PNG by CoreGraphics window id for rounded-corner/shadow alpha. +5. Captures a full-screen image and crops the same window rectangle for the true SwiftUI material tint from the real wallpaper. +6. Applies the window-only alpha to the tinted crop and composites that onto `assets/bg-light.jpg`. +7. Repeats for dark mode with `assets/wallpaper-dark.jpg` and `assets/bg-dark.jpg`. +8. Restores the original appearance and wallpaper. + +The script intentionally uses clean skeleton backgrounds so the final menu bar contains only the SwiftShift icon, while the app window keeps the real glass tint from the matching wallpaper. + +## Layout constants + +The app window is placed at `+255+31` in the final 843×670 image. The mouse feature visual is cropped from that final image at `633×310+125+260`. + +Override if the design changes: + +```bash +SWIFTSHIFT_SCREENSHOT_X=250 SWIFTSHIFT_SCREENSHOT_Y=31 \ +SWIFTSHIFT_MOUSE_CROP_X=125 SWIFTSHIFT_MOUSE_CROP_Y=260 \ + .agents/skills/screenshots/bin/capture-swiftshift-screenshots.sh +``` + +## Notes + +- The script will briefly change the Mac appearance/wallpaper and hide visible apps. +- If the app UI gets taller/wider, update the background skeletons and placement constants together. diff --git a/.agents/skills/screenshots/assets/bg-dark.jpg b/.agents/skills/screenshots/assets/bg-dark.jpg new file mode 100644 index 0000000..ba7279b Binary files /dev/null and b/.agents/skills/screenshots/assets/bg-dark.jpg differ diff --git a/.agents/skills/screenshots/assets/bg-light.jpg b/.agents/skills/screenshots/assets/bg-light.jpg new file mode 100644 index 0000000..0692e83 Binary files /dev/null and b/.agents/skills/screenshots/assets/bg-light.jpg differ diff --git a/.agents/skills/screenshots/assets/wallpaper-dark.jpg b/.agents/skills/screenshots/assets/wallpaper-dark.jpg new file mode 100644 index 0000000..d191862 Binary files /dev/null and b/.agents/skills/screenshots/assets/wallpaper-dark.jpg differ diff --git a/.agents/skills/screenshots/assets/wallpaper-light.jpg b/.agents/skills/screenshots/assets/wallpaper-light.jpg new file mode 100644 index 0000000..4d00e64 Binary files /dev/null and b/.agents/skills/screenshots/assets/wallpaper-light.jpg differ diff --git a/.agents/skills/screenshots/bin/capture-swiftshift-screenshots.sh b/.agents/skills/screenshots/bin/capture-swiftshift-screenshots.sh new file mode 100755 index 0000000..88ee2fc --- /dev/null +++ b/.agents/skills/screenshots/bin/capture-swiftshift-screenshots.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +REPO_ROOT="$(cd "$SKILL_DIR/../../.." && pwd)" +ASSET_DIR="$SKILL_DIR/assets" +OUT_DIR="${1:-$REPO_ROOT/www/src/images}" +DEBUG_DIR="${SWIFTSHIFT_SCREENSHOT_DEBUG_DIR:-$REPO_ROOT/.agents/skills/screenshots/debug}" + +APP_NAME="Swift Shift Dev" +BUNDLE_ID="com.pablopunk.Swift-Shift.dev" +# Desired top-left for the actual SwiftShift window content in the 843×670 final. +# Window-only captures may include transparent shadow margins; the script accounts for that. +TARGET_X="${SWIFTSHIFT_SCREENSHOT_X:-255}" +TARGET_Y="${SWIFTSHIFT_SCREENSHOT_Y:-31}" + +# Crop for the website mouse-buttons feature visual, derived from the final 843×670 composite. +MOUSE_CROP_X="${SWIFTSHIFT_MOUSE_CROP_X:-125}" +MOUSE_CROP_Y="${SWIFTSHIFT_MOUSE_CROP_Y:-260}" +MOUSE_CROP_W="${SWIFTSHIFT_MOUSE_CROP_W:-633}" +MOUSE_CROP_H="${SWIFTSHIFT_MOUSE_CROP_H:-310}" + +mkdir -p "$OUT_DIR" "$DEBUG_DIR" +TMP_DIR="$(mktemp -d)" +ORIGINAL_DARK_MODE="" +ORIGINAL_WALLPAPER="" + +log() { printf '› %s\n' "$*"; } +fail() { printf 'error: %s\n' "$*" >&2; exit 1; } + +require_bin() { + command -v "$1" >/dev/null 2>&1 || fail "missing '$1'. Install it and retry." +} + +apple_bool() { + osascript -e "$1" 2>/dev/null | tr '[:upper:]' '[:lower:]' +} + +capture_state() { + ORIGINAL_DARK_MODE="$(apple_bool 'tell application "System Events" to tell appearance preferences to get dark mode' || true)" + ORIGINAL_WALLPAPER="$(osascript -e 'tell application "System Events" to get picture of current desktop' 2>/dev/null || true)" +} + +restore_state() { + set +e + if [[ -n "$ORIGINAL_DARK_MODE" ]]; then + osascript -e "tell application \"System Events\" to tell appearance preferences to set dark mode to $ORIGINAL_DARK_MODE" >/dev/null 2>&1 + fi + if [[ -n "$ORIGINAL_WALLPAPER" ]]; then + osascript -e "tell application \"System Events\" to set picture of every desktop to \"$ORIGINAL_WALLPAPER\"" >/dev/null 2>&1 + fi + rm -rf "$TMP_DIR" +} +trap restore_state EXIT + +set_appearance() { + local mode="$1" + local dark=false + [[ "$mode" == "dark" ]] && dark=true + osascript -e "tell application \"System Events\" to tell appearance preferences to set dark mode to $dark" >/dev/null + sleep 1 +} + +set_wallpaper() { + local image="$1" + osascript -e "tell application \"System Events\" to set picture of every desktop to \"$image\"" >/dev/null || true + sleep 1 +} + +hide_other_apps() { + # Keeps the captured SwiftUI material from picking up random windows behind it. + osascript >/dev/null <<'APPLESCRIPT' || true +tell application "System Events" + repeat with p in application processes + try + if background only of p is false and name of p is not "Swift Shift Dev" then + set visible of p to false + end if + end try + end repeat +end tell +APPLESCRIPT + sleep 0.5 +} + +open_menu() { + osascript <<'APPLESCRIPT' +tell application "System Events" + tell process "Swift Shift Dev" + repeat 40 times + if exists menu bar 2 then exit repeat + delay 0.25 + end repeat + if not (exists menu bar 2) then error "Swift Shift menu bar item was not found" + + click menu bar item 1 of menu bar 2 + delay 0.8 + if (count of windows) is 0 then + click menu bar item 1 of menu bar 2 + delay 0.8 + end if + if (count of windows) is 0 then error "Swift Shift settings window did not open" + end tell +end tell +APPLESCRIPT +} + +swiftshift_window_info() { + peekaboo list windows --app "$APP_NAME" --json | python3 -c 'import json,sys; w=json.load(sys.stdin)["data"]["windows"][0]; b=w["bounds"]; print("{},{},{},{},{}".format(w["window_id"], int(b[0][0]), int(b[0][1]), int(b[1][0]), int(b[1][1])))' +} + +alpha_bbox() { + local image="$1" + magick "$image" -alpha extract -threshold 90% -format '%@' info: +} + +bbox_offset_x() { + printf '%s' "$1" | sed -E 's/^[0-9]+x[0-9]+\+([0-9]+)\+([0-9]+)$/\1/' +} + +bbox_offset_y() { + printf '%s' "$1" | sed -E 's/^[0-9]+x[0-9]+\+([0-9]+)\+([0-9]+)$/\2/' +} + +capture_theme() { + local mode="$1" + local bg="$ASSET_DIR/bg-$mode.jpg" + local wallpaper="$ASSET_DIR/wallpaper-$mode.jpg" + local out="$OUT_DIR/screenshot-$mode.jpg" + local mouse_out="$OUT_DIR/mouse-$mode.jpg" + local debug_bg="$DEBUG_DIR/01-backplate-$mode.jpg" + local full_screen="$TMP_DIR/full-screen-$mode.png" + local window="$DEBUG_DIR/02-window-alpha-$mode.png" + local screen_crop="$DEBUG_DIR/03-screen-tint-crop-$mode.png" + local content_overlay="$DEBUG_DIR/04-content-tint-overlay-$mode.png" + local tinted_window="$DEBUG_DIR/05-tinted-window-$mode.png" + local final="$DEBUG_DIR/06-final-$mode.jpg" + + [[ -f "$bg" ]] || fail "missing background asset: $bg" + [[ -f "$wallpaper" ]] || fail "missing wallpaper asset: $wallpaper" + + log "capturing $mode screenshot" + cp "$bg" "$debug_bg" + set_appearance "$mode" + set_wallpaper "$wallpaper" + hide_other_apps + open_menu >/dev/null + + local info window_id x y w h + info="$(swiftshift_window_info)" + IFS=',' read -r window_id x y w h <<< "$info" + [[ -n "$window_id" && -n "$x" && -n "$y" && -n "$w" && -n "$h" ]] || fail "could not parse Swift Shift window info: $info" + + # Window-only capture gives us the real rounded-corner/shadow alpha. + screencapture -x -l"$window_id" -t png "$window" + + local bbox offset_x offset_y capture_w capture_h crop_x crop_y image_x image_y + bbox="$(alpha_bbox "$window")" + offset_x="$(bbox_offset_x "$bbox")" + offset_y="$(bbox_offset_y "$bbox")" + capture_w="$(magick identify -format '%w' "$window")" + capture_h="$(magick identify -format '%h' "$window")" + crop_x=$((x - offset_x)) + crop_y=$((y - offset_y)) + image_x=$((TARGET_X - offset_x)) + image_y=$((TARGET_Y - offset_y)) + + # Full-screen crop has the correct SwiftUI material tint from the real wallpaper. + peekaboo image --mode screen --screen-index 0 --path "$full_screen" --format png >/dev/null + magick "$full_screen" -crop "${capture_w}x${capture_h}+${crop_x}+${crop_y}" +repage "$screen_crop" + + # Keep the clean window-only shadow/rounded edges, but replace the opaque window interior + # with the full-screen crop so SwiftUI glass keeps the real wallpaper tint. The hard + # threshold prevents real menu-bar pixels from leaking into the top shadow margin. + magick "$screen_crop" \( "$window" -alpha extract -threshold 90% \) -compose CopyOpacity -composite "$content_overlay" + magick "$window" "$content_overlay" -compose Over -composite "$tinted_window" + + magick "$bg" "$tinted_window" -geometry "+${image_x}+${image_y}" -composite -quality 92 "$out" + magick "$out" -crop "${MOUSE_CROP_W}x${MOUSE_CROP_H}+${MOUSE_CROP_X}+${MOUSE_CROP_Y}" +repage -quality 92 "$mouse_out" + cp "$out" "$final" + cp "$mouse_out" "$DEBUG_DIR/07-mouse-crop-$mode.jpg" + log "wrote $out" + log "wrote $mouse_out" + log "debug: $debug_bg" + log "debug: $window" + log "debug: $screen_crop" + log "debug: $content_overlay" + log "debug: $tinted_window" + log "debug: $final" + log "debug: $DEBUG_DIR/07-mouse-crop-$mode.jpg" + # Close the menu extra before switching themes. + osascript -e 'tell application "System Events" to key code 53' >/dev/null 2>&1 || true + sleep 0.3 +} + +main() { + require_bin osascript + require_bin make + require_bin peekaboo + require_bin magick + require_bin python3 + require_bin screencapture + + [[ -d "$REPO_ROOT/www/src/images" ]] || fail "could not find repo root from $REPO_ROOT" + + peekaboo permissions --json >/dev/null || fail "Peekaboo needs Screen Recording + Accessibility permissions" + capture_state + + log "building and running $APP_NAME" + defaults write "$BUNDLE_ID" showMenuBarIcon -bool true >/dev/null 2>&1 || true + (cd "$REPO_ROOT" && make build >/dev/null && make run-app >/dev/null) + sleep 2 + + capture_theme light + capture_theme dark + + log "done" +} + +main "$@" diff --git a/www/src/images/mouse-dark.jpg b/www/src/images/mouse-dark.jpg index a353308..c4c510f 100644 Binary files a/www/src/images/mouse-dark.jpg and b/www/src/images/mouse-dark.jpg differ diff --git a/www/src/images/mouse-light.jpg b/www/src/images/mouse-light.jpg index f97bff6..aad9ea0 100644 Binary files a/www/src/images/mouse-light.jpg and b/www/src/images/mouse-light.jpg differ diff --git a/www/src/images/screenshot-dark.jpg b/www/src/images/screenshot-dark.jpg index ee6c55d..b5b2db9 100644 Binary files a/www/src/images/screenshot-dark.jpg and b/www/src/images/screenshot-dark.jpg differ diff --git a/www/src/images/screenshot-light.jpg b/www/src/images/screenshot-light.jpg index d32f76c..0c53442 100644 Binary files a/www/src/images/screenshot-light.jpg and b/www/src/images/screenshot-light.jpg differ