Compare commits
3 Commits
eeebed10c0
...
23b43a6f2a
| Author | SHA1 | Date |
|---|---|---|
|
|
23b43a6f2a | |
|
|
02e0e70b99 | |
|
|
ada2535b62 |
|
|
@ -0,0 +1,167 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Process new puzzle images end-to-end:
|
||||||
|
# 1. Rename files in prep/ to lowercase with underscores
|
||||||
|
# 2. Move them to puzzles/
|
||||||
|
# 3. Generate thumbnails for any images missing one
|
||||||
|
# 4. Sync puzzles.json and thumbnails.json
|
||||||
|
#
|
||||||
|
# Run from: assets/images/
|
||||||
|
# Requires: ImageMagick (convert command)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
PREP_DIR="prep"
|
||||||
|
PUZZLE_DIR="puzzles"
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
PUZZLES_JSON="$REPO_ROOT/assets/puzzles.json"
|
||||||
|
THUMBS_JSON="$REPO_ROOT/assets/thumbnails.json"
|
||||||
|
THUMB_WIDTH=576
|
||||||
|
THUMB_HEIGHT=324
|
||||||
|
|
||||||
|
# ─── 1. Rename files in prep/ ──────────────────────────────────────────
|
||||||
|
|
||||||
|
echo "=== Step 1: Renaming files in $PREP_DIR/ ==="
|
||||||
|
renamed=0
|
||||||
|
for f in "$PREP_DIR"/*.png "$PREP_DIR"/*.jpg "$PREP_DIR"/*.jpeg; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
dir=$(dirname "$f")
|
||||||
|
base=$(basename "$f")
|
||||||
|
newname=$(echo "$base" | tr '[:upper:]' '[:lower:]' | tr ' ' '_')
|
||||||
|
if [ "$base" != "$newname" ]; then
|
||||||
|
mv "$f" "$dir/$newname"
|
||||||
|
echo " Renamed: $base -> $newname"
|
||||||
|
renamed=$((renamed + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo " Renamed $renamed file(s)"
|
||||||
|
|
||||||
|
# ─── 2. Move files from prep/ to puzzles/ ──────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 2: Moving images from $PREP_DIR/ to $PUZZLE_DIR/ ==="
|
||||||
|
moved=0
|
||||||
|
for f in "$PREP_DIR"/*.png "$PREP_DIR"/*.jpg "$PREP_DIR"/*.jpeg; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
base=$(basename "$f")
|
||||||
|
# Skip shell scripts
|
||||||
|
case "$base" in *.sh) continue ;; esac
|
||||||
|
if [ -f "$PUZZLE_DIR/$base" ]; then
|
||||||
|
echo " SKIP $base (already exists in $PUZZLE_DIR/)"
|
||||||
|
else
|
||||||
|
mv "$f" "$PUZZLE_DIR/$base"
|
||||||
|
echo " Moved: $base"
|
||||||
|
moved=$((moved + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo " Moved $moved file(s)"
|
||||||
|
|
||||||
|
# ─── 3. Generate thumbnails ────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 3: Generating thumbnails ==="
|
||||||
|
|
||||||
|
if ! command -v convert &> /dev/null; then
|
||||||
|
echo "Error: ImageMagick is not installed."
|
||||||
|
echo " Ubuntu/Debian: sudo apt install imagemagick"
|
||||||
|
echo " macOS: brew install imagemagick"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
created=0
|
||||||
|
skipped=0
|
||||||
|
for file in "$PUZZLE_DIR"/*; do
|
||||||
|
[ -f "$file" ] || continue
|
||||||
|
base=$(basename "$file")
|
||||||
|
# Skip thumbnails, scripts
|
||||||
|
case "$base" in
|
||||||
|
thumb_*|*.sh) continue ;;
|
||||||
|
esac
|
||||||
|
# Check it's actually an image
|
||||||
|
mime=$(file --mime-type -b "$file")
|
||||||
|
case "$mime" in
|
||||||
|
image/*) ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
thumb="$PUZZLE_DIR/thumb_${base}"
|
||||||
|
if [ -f "$thumb" ]; then
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
convert "$file" -resize "${THUMB_WIDTH}x${THUMB_HEIGHT}!" -strip "$thumb"
|
||||||
|
echo " Created: thumb_${base}"
|
||||||
|
created=$((created + 1))
|
||||||
|
done
|
||||||
|
echo " Created $created thumbnail(s), skipped $skipped existing"
|
||||||
|
|
||||||
|
# ─── 4. Sync JSON manifests ────────────────────────────────────────────
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 4: Syncing JSON manifests ==="
|
||||||
|
|
||||||
|
puzzles="["
|
||||||
|
thumbs="["
|
||||||
|
first=true
|
||||||
|
|
||||||
|
for img in "$PUZZLE_DIR"/*.png "$PUZZLE_DIR"/*.jpg "$PUZZLE_DIR"/*.jpeg; do
|
||||||
|
[ -f "$img" ] || continue
|
||||||
|
base=$(basename "$img")
|
||||||
|
ext="${base##*.}"
|
||||||
|
name="${base%.*}"
|
||||||
|
|
||||||
|
# Skip thumbnails
|
||||||
|
[[ "$name" == thumb_* ]] && continue
|
||||||
|
|
||||||
|
# Derive keys (replace hyphens with underscores)
|
||||||
|
safe="${name//-/_}"
|
||||||
|
puzzle_key="puzzle_${safe}"
|
||||||
|
thumb_key="thumb_${safe}"
|
||||||
|
thumb_file="$PUZZLE_DIR/thumb_${name}.${ext}"
|
||||||
|
|
||||||
|
# Skip if no matching thumbnail
|
||||||
|
if [ ! -f "$thumb_file" ]; then
|
||||||
|
echo " WARNING: No thumbnail for $name — skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build label: replace underscores/hyphens with spaces, then title-case
|
||||||
|
label=$(echo "$name" | sed 's/[_-]/ /g' | sed 's/\b\(.\)/\u\1/g')
|
||||||
|
|
||||||
|
# Paths relative to repo root
|
||||||
|
rel_img="assets/images/puzzles/${base}"
|
||||||
|
rel_thumb="assets/images/puzzles/thumb_${name}.${ext}"
|
||||||
|
|
||||||
|
sep=""
|
||||||
|
$first || sep=","
|
||||||
|
first=false
|
||||||
|
|
||||||
|
puzzles+="${sep}
|
||||||
|
{
|
||||||
|
\"key\": \"${puzzle_key}\",
|
||||||
|
\"path\": \"${rel_img}\",
|
||||||
|
\"label\": \"${label}\"
|
||||||
|
}"
|
||||||
|
|
||||||
|
thumbs+="${sep}
|
||||||
|
{
|
||||||
|
\"key\": \"${thumb_key}\",
|
||||||
|
\"path\": \"${rel_thumb}\",
|
||||||
|
\"puzzleKey\": \"${puzzle_key}\"
|
||||||
|
}"
|
||||||
|
done
|
||||||
|
|
||||||
|
puzzles+="
|
||||||
|
]"
|
||||||
|
thumbs+="
|
||||||
|
]"
|
||||||
|
|
||||||
|
echo "$puzzles" > "$PUZZLES_JSON"
|
||||||
|
echo "$thumbs" > "$THUMBS_JSON"
|
||||||
|
|
||||||
|
echo " Updated puzzles.json ($(grep -c '"key"' "$PUZZLES_JSON") entries)"
|
||||||
|
echo " Updated thumbnails.json ($(grep -c '"key"' "$THUMBS_JSON") entries)"
|
||||||
|
echo ""
|
||||||
|
echo "=== Done! ==="
|
||||||
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 4.3 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 349 KiB |
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 319 KiB |
|
After Width: | Height: | Size: 362 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 295 KiB |
|
After Width: | Height: | Size: 323 KiB |
|
|
@ -44,6 +44,11 @@
|
||||||
"path": "assets/images/puzzles/cottages_on_the_street.png",
|
"path": "assets/images/puzzles/cottages_on_the_street.png",
|
||||||
"label": "Cottages On The Street"
|
"label": "Cottages On The Street"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_cownappers",
|
||||||
|
"path": "assets/images/puzzles/cownappers.png",
|
||||||
|
"label": "Cownappers"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_dwarven_blacksmith",
|
"key": "puzzle_dwarven_blacksmith",
|
||||||
"path": "assets/images/puzzles/dwarven_blacksmith.png",
|
"path": "assets/images/puzzles/dwarven_blacksmith.png",
|
||||||
|
|
@ -69,6 +74,11 @@
|
||||||
"path": "assets/images/puzzles/floating_wizards_village.png",
|
"path": "assets/images/puzzles/floating_wizards_village.png",
|
||||||
"label": "Floating Wizards Village"
|
"label": "Floating Wizards Village"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_forest_conjurer",
|
||||||
|
"path": "assets/images/puzzles/forest_conjurer.png",
|
||||||
|
"label": "Forest Conjurer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_four_seasons_2",
|
"key": "puzzle_four_seasons_2",
|
||||||
"path": "assets/images/puzzles/four_seasons_2.png",
|
"path": "assets/images/puzzles/four_seasons_2.png",
|
||||||
|
|
@ -84,6 +94,11 @@
|
||||||
"path": "assets/images/puzzles/fuzzy_halloween.png",
|
"path": "assets/images/puzzles/fuzzy_halloween.png",
|
||||||
"label": "Fuzzy Halloween"
|
"label": "Fuzzy Halloween"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_germanic_castle",
|
||||||
|
"path": "assets/images/puzzles/germanic_castle.png",
|
||||||
|
"label": "Germanic Castle"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_golden_in_the_mountains",
|
"key": "puzzle_golden_in_the_mountains",
|
||||||
"path": "assets/images/puzzles/golden_in_the_mountains.png",
|
"path": "assets/images/puzzles/golden_in_the_mountains.png",
|
||||||
|
|
@ -114,6 +129,11 @@
|
||||||
"path": "assets/images/puzzles/home_sweet_home.png",
|
"path": "assets/images/puzzles/home_sweet_home.png",
|
||||||
"label": "Home Sweet Home"
|
"label": "Home Sweet Home"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_hong_kong_phonecall",
|
||||||
|
"path": "assets/images/puzzles/hong_kong_phonecall.png",
|
||||||
|
"label": "Hong Kong Phonecall"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_jeweled_frogs",
|
"key": "puzzle_jeweled_frogs",
|
||||||
"path": "assets/images/puzzles/jeweled_frogs.png",
|
"path": "assets/images/puzzles/jeweled_frogs.png",
|
||||||
|
|
@ -129,6 +149,11 @@
|
||||||
"path": "assets/images/puzzles/lake_treehouse.png",
|
"path": "assets/images/puzzles/lake_treehouse.png",
|
||||||
"label": "Lake Treehouse"
|
"label": "Lake Treehouse"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_lonely_robot_2",
|
||||||
|
"path": "assets/images/puzzles/lonely_robot_2.png",
|
||||||
|
"label": "Lonely Robot 2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_lone_ship_in_the_seas",
|
"key": "puzzle_lone_ship_in_the_seas",
|
||||||
"path": "assets/images/puzzles/lone_ship_in_the_seas.png",
|
"path": "assets/images/puzzles/lone_ship_in_the_seas.png",
|
||||||
|
|
@ -159,6 +184,21 @@
|
||||||
"path": "assets/images/puzzles/mountain_blanket.png",
|
"path": "assets/images/puzzles/mountain_blanket.png",
|
||||||
"label": "Mountain Blanket"
|
"label": "Mountain Blanket"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_mushroom_world",
|
||||||
|
"path": "assets/images/puzzles/mushroom_world.png",
|
||||||
|
"label": "Mushroom World"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_octomech_2",
|
||||||
|
"path": "assets/images/puzzles/octomech_2.png",
|
||||||
|
"label": "Octomech 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_old_polish_town",
|
||||||
|
"path": "assets/images/puzzles/old_polish_town.png",
|
||||||
|
"label": "Old Polish Town"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_opulant_flowers",
|
"key": "puzzle_opulant_flowers",
|
||||||
"path": "assets/images/puzzles/opulant_flowers.png",
|
"path": "assets/images/puzzles/opulant_flowers.png",
|
||||||
|
|
@ -169,6 +209,11 @@
|
||||||
"path": "assets/images/puzzles/orc_and_the_undead.png",
|
"path": "assets/images/puzzles/orc_and_the_undead.png",
|
||||||
"label": "Orc And The Undead"
|
"label": "Orc And The Undead"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_red_eyes",
|
||||||
|
"path": "assets/images/puzzles/red_eyes.png",
|
||||||
|
"label": "Red Eyes"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_red_panda_in_the_bamboo_forest",
|
"key": "puzzle_red_panda_in_the_bamboo_forest",
|
||||||
"path": "assets/images/puzzles/red_panda_in_the_bamboo_forest.png",
|
"path": "assets/images/puzzles/red_panda_in_the_bamboo_forest.png",
|
||||||
|
|
@ -204,6 +249,11 @@
|
||||||
"path": "assets/images/puzzles/the_disassembled_android.png",
|
"path": "assets/images/puzzles/the_disassembled_android.png",
|
||||||
"label": "The Disassembled Android"
|
"label": "The Disassembled Android"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "puzzle_the_mall_interior",
|
||||||
|
"path": "assets/images/puzzles/the_mall_interior.png",
|
||||||
|
"label": "The Mall Interior"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "puzzle_the_old_w0rm",
|
"key": "puzzle_the_old_w0rm",
|
||||||
"path": "assets/images/puzzles/the_old_w0rm.png",
|
"path": "assets/images/puzzles/the_old_w0rm.png",
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@
|
||||||
"path": "assets/images/puzzles/thumb_cottages_on_the_street.png",
|
"path": "assets/images/puzzles/thumb_cottages_on_the_street.png",
|
||||||
"puzzleKey": "puzzle_cottages_on_the_street"
|
"puzzleKey": "puzzle_cottages_on_the_street"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_cownappers",
|
||||||
|
"path": "assets/images/puzzles/thumb_cownappers.png",
|
||||||
|
"puzzleKey": "puzzle_cownappers"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_dwarven_blacksmith",
|
"key": "thumb_dwarven_blacksmith",
|
||||||
"path": "assets/images/puzzles/thumb_dwarven_blacksmith.png",
|
"path": "assets/images/puzzles/thumb_dwarven_blacksmith.png",
|
||||||
|
|
@ -69,6 +74,11 @@
|
||||||
"path": "assets/images/puzzles/thumb_floating_wizards_village.png",
|
"path": "assets/images/puzzles/thumb_floating_wizards_village.png",
|
||||||
"puzzleKey": "puzzle_floating_wizards_village"
|
"puzzleKey": "puzzle_floating_wizards_village"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_forest_conjurer",
|
||||||
|
"path": "assets/images/puzzles/thumb_forest_conjurer.png",
|
||||||
|
"puzzleKey": "puzzle_forest_conjurer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_four_seasons_2",
|
"key": "thumb_four_seasons_2",
|
||||||
"path": "assets/images/puzzles/thumb_four_seasons_2.png",
|
"path": "assets/images/puzzles/thumb_four_seasons_2.png",
|
||||||
|
|
@ -84,6 +94,11 @@
|
||||||
"path": "assets/images/puzzles/thumb_fuzzy_halloween.png",
|
"path": "assets/images/puzzles/thumb_fuzzy_halloween.png",
|
||||||
"puzzleKey": "puzzle_fuzzy_halloween"
|
"puzzleKey": "puzzle_fuzzy_halloween"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_germanic_castle",
|
||||||
|
"path": "assets/images/puzzles/thumb_germanic_castle.png",
|
||||||
|
"puzzleKey": "puzzle_germanic_castle"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_golden_in_the_mountains",
|
"key": "thumb_golden_in_the_mountains",
|
||||||
"path": "assets/images/puzzles/thumb_golden_in_the_mountains.png",
|
"path": "assets/images/puzzles/thumb_golden_in_the_mountains.png",
|
||||||
|
|
@ -114,6 +129,11 @@
|
||||||
"path": "assets/images/puzzles/thumb_home_sweet_home.png",
|
"path": "assets/images/puzzles/thumb_home_sweet_home.png",
|
||||||
"puzzleKey": "puzzle_home_sweet_home"
|
"puzzleKey": "puzzle_home_sweet_home"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_hong_kong_phonecall",
|
||||||
|
"path": "assets/images/puzzles/thumb_hong_kong_phonecall.png",
|
||||||
|
"puzzleKey": "puzzle_hong_kong_phonecall"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_jeweled_frogs",
|
"key": "thumb_jeweled_frogs",
|
||||||
"path": "assets/images/puzzles/thumb_jeweled_frogs.png",
|
"path": "assets/images/puzzles/thumb_jeweled_frogs.png",
|
||||||
|
|
@ -129,6 +149,11 @@
|
||||||
"path": "assets/images/puzzles/thumb_lake_treehouse.png",
|
"path": "assets/images/puzzles/thumb_lake_treehouse.png",
|
||||||
"puzzleKey": "puzzle_lake_treehouse"
|
"puzzleKey": "puzzle_lake_treehouse"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_lonely_robot_2",
|
||||||
|
"path": "assets/images/puzzles/thumb_lonely_robot_2.png",
|
||||||
|
"puzzleKey": "puzzle_lonely_robot_2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_lone_ship_in_the_seas",
|
"key": "thumb_lone_ship_in_the_seas",
|
||||||
"path": "assets/images/puzzles/thumb_lone_ship_in_the_seas.png",
|
"path": "assets/images/puzzles/thumb_lone_ship_in_the_seas.png",
|
||||||
|
|
@ -159,6 +184,21 @@
|
||||||
"path": "assets/images/puzzles/thumb_mountain_blanket.png",
|
"path": "assets/images/puzzles/thumb_mountain_blanket.png",
|
||||||
"puzzleKey": "puzzle_mountain_blanket"
|
"puzzleKey": "puzzle_mountain_blanket"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_mushroom_world",
|
||||||
|
"path": "assets/images/puzzles/thumb_mushroom_world.png",
|
||||||
|
"puzzleKey": "puzzle_mushroom_world"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_octomech_2",
|
||||||
|
"path": "assets/images/puzzles/thumb_octomech_2.png",
|
||||||
|
"puzzleKey": "puzzle_octomech_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_old_polish_town",
|
||||||
|
"path": "assets/images/puzzles/thumb_old_polish_town.png",
|
||||||
|
"puzzleKey": "puzzle_old_polish_town"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_opulant_flowers",
|
"key": "thumb_opulant_flowers",
|
||||||
"path": "assets/images/puzzles/thumb_opulant_flowers.png",
|
"path": "assets/images/puzzles/thumb_opulant_flowers.png",
|
||||||
|
|
@ -169,6 +209,11 @@
|
||||||
"path": "assets/images/puzzles/thumb_orc_and_the_undead.png",
|
"path": "assets/images/puzzles/thumb_orc_and_the_undead.png",
|
||||||
"puzzleKey": "puzzle_orc_and_the_undead"
|
"puzzleKey": "puzzle_orc_and_the_undead"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_red_eyes",
|
||||||
|
"path": "assets/images/puzzles/thumb_red_eyes.png",
|
||||||
|
"puzzleKey": "puzzle_red_eyes"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_red_panda_in_the_bamboo_forest",
|
"key": "thumb_red_panda_in_the_bamboo_forest",
|
||||||
"path": "assets/images/puzzles/thumb_red_panda_in_the_bamboo_forest.png",
|
"path": "assets/images/puzzles/thumb_red_panda_in_the_bamboo_forest.png",
|
||||||
|
|
@ -204,6 +249,11 @@
|
||||||
"path": "assets/images/puzzles/thumb_the_disassembled_android.png",
|
"path": "assets/images/puzzles/thumb_the_disassembled_android.png",
|
||||||
"puzzleKey": "puzzle_the_disassembled_android"
|
"puzzleKey": "puzzle_the_disassembled_android"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "thumb_the_mall_interior",
|
||||||
|
"path": "assets/images/puzzles/thumb_the_mall_interior.png",
|
||||||
|
"puzzleKey": "puzzle_the_mall_interior"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "thumb_the_old_w0rm",
|
"key": "thumb_the_old_w0rm",
|
||||||
"path": "assets/images/puzzles/thumb_the_old_w0rm.png",
|
"path": "assets/images/puzzles/thumb_the_old_w0rm.png",
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
if (!this.cache.audio.exists('sfx_grab')) {
|
if (!this.cache.audio.exists('sfx_grab')) {
|
||||||
this.load.audio('sfx_grab', 'assets/audio/fx/grab.mp3');
|
this.load.audio('sfx_grab', 'assets/audio/fx/grab.mp3');
|
||||||
}
|
}
|
||||||
|
if (!this.cache.audio.exists('sfx_stats')) {
|
||||||
|
this.load.audio('sfx_stats', 'assets/audio/fx/stats.mp3');
|
||||||
|
}
|
||||||
|
|
||||||
// Music track list
|
// Music track list
|
||||||
if (!this.cache.json.exists('music_tracks')) {
|
if (!this.cache.json.exists('music_tracks')) {
|
||||||
|
|
@ -365,7 +368,7 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
|
|
||||||
this._edgeIds = this._pieces
|
this._edgeIds = this._pieces
|
||||||
.filter(p => p.edges.top === 'flat' || p.edges.bottom === 'flat' ||
|
.filter(p => p.edges.top === 'flat' || p.edges.bottom === 'flat' ||
|
||||||
p.edges.left === 'flat' || p.edges.right === 'flat')
|
p.edges.left === 'flat' || p.edges.right === 'flat')
|
||||||
.map(p => p.id);
|
.map(p => p.id);
|
||||||
|
|
||||||
const midCol = Math.ceil(this._cols / 2);
|
const midCol = Math.ceil(this._cols / 2);
|
||||||
|
|
@ -1172,6 +1175,16 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
if (po.image.texture.key !== expectedKey && this.textures.exists(expectedKey)) {
|
if (po.image.texture.key !== expectedKey && this.textures.exists(expectedKey)) {
|
||||||
po.image.setTexture(expectedKey);
|
po.image.setTexture(expectedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contour-following drop shadow via preFX (Phaser 3.60+)
|
||||||
|
if (po.image.preFX) {
|
||||||
|
if (!isMerged && !po._shadowFX) {
|
||||||
|
po._shadowFX = po.image.preFX.addShadow(2, 2, 0.06, 1, 0x000000, 6);
|
||||||
|
} else if (isMerged && po._shadowFX) {
|
||||||
|
po.image.preFX.remove(po._shadowFX);
|
||||||
|
po._shadowFX = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1218,7 +1231,7 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
if (!this._glowingPieceIds) this._glowingPieceIds = new Set();
|
if (!this._glowingPieceIds) this._glowingPieceIds = new Set();
|
||||||
if (!this._glowTimers) this._glowTimers = new Map();
|
if (!this._glowTimers) this._glowTimers = new Map();
|
||||||
|
|
||||||
const GLOW_DELAY = 500; // ms a neighbor must stay in range before glow appears
|
const GLOW_DELAY = 700; // ms a neighbor must stay in range before glow appears
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Track which neighbors are in proximity this frame
|
// Track which neighbors are in proximity this frame
|
||||||
|
|
@ -1254,8 +1267,8 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
[
|
[
|
||||||
[20, 0.06],
|
[20, 0.06],
|
||||||
[14, 0.12],
|
[14, 0.12],
|
||||||
[8, 0.30],
|
[8, 0.30],
|
||||||
[4, 0.70],
|
[4, 0.70],
|
||||||
].forEach(([baseLW, baseAlpha]) => {
|
].forEach(([baseLW, baseAlpha]) => {
|
||||||
const lw = Math.max(1, baseLW * intensity);
|
const lw = Math.max(1, baseLW * intensity);
|
||||||
const alpha = baseAlpha * intensity;
|
const alpha = baseAlpha * intensity;
|
||||||
|
|
@ -2166,9 +2179,9 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
if (!this._uiLayer) return;
|
if (!this._uiLayer) return;
|
||||||
const rect = this.sys.game.canvas.getBoundingClientRect();
|
const rect = this.sys.game.canvas.getBoundingClientRect();
|
||||||
Object.assign(this._uiLayer.style, {
|
Object.assign(this._uiLayer.style, {
|
||||||
left: rect.left + 'px',
|
left: rect.left + 'px',
|
||||||
top: rect.top + 'px',
|
top: rect.top + 'px',
|
||||||
transform: `scale(${rect.width / 1920}, ${rect.height / 1080})`,
|
transform: `scale(${rect.width / 1920}, ${rect.height / 1080})`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2178,13 +2191,13 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
// except on the interactive children.
|
// except on the interactive children.
|
||||||
this._uiLayer = document.createElement('div');
|
this._uiLayer = document.createElement('div');
|
||||||
Object.assign(this._uiLayer.style, {
|
Object.assign(this._uiLayer.style, {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
width: '1920px',
|
width: '1920px',
|
||||||
height: '1080px',
|
height: '1080px',
|
||||||
transformOrigin: 'top left',
|
transformOrigin: 'top left',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
zIndex: '10',
|
zIndex: '10',
|
||||||
fontFamily: 'Arial, sans-serif',
|
fontFamily: 'Arial, sans-serif',
|
||||||
});
|
});
|
||||||
document.body.appendChild(this._uiLayer);
|
document.body.appendChild(this._uiLayer);
|
||||||
this._syncOverlayToCanvas();
|
this._syncOverlayToCanvas();
|
||||||
|
|
@ -2801,11 +2814,13 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
const typeNext = () => {
|
const typeNext = () => {
|
||||||
if (this._completionOverlay === null) {
|
if (this._completionOverlay === null) {
|
||||||
clearInterval(cursorBlink);
|
clearInterval(cursorBlink);
|
||||||
|
if (this._statsSound) { this._statsSound.stop(); this._statsSound.destroy(); this._statsSound = null; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineIdx >= lines.length) {
|
if (lineIdx >= lines.length) {
|
||||||
// Done — keep cursor blinking at end
|
// Done — stop stats sound, keep cursor blinking at end
|
||||||
|
if (this._statsSound) { this._statsSound.stop(); this._statsSound.destroy(); this._statsSound = null; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2845,7 +2860,9 @@ class PuzzleScene extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start typing after a short delay
|
// Start stats sound loop and typing after a short delay
|
||||||
|
this._statsSound = this.sound.add('sfx_stats', { loop: true, volume: 0.5 });
|
||||||
|
this._statsSound.play();
|
||||||
setTimeout(typeNext, 500);
|
setTimeout(typeNext, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||