diff --git a/assets/cards/backs/back_0.png b/assets/cards/backs/classic/back_0.png
similarity index 100%
rename from assets/cards/backs/back_0.png
rename to assets/cards/backs/classic/back_0.png
diff --git a/assets/cards/backs/back_1.png b/assets/cards/backs/classic/back_1.png
similarity index 100%
rename from assets/cards/backs/back_1.png
rename to assets/cards/backs/classic/back_1.png
diff --git a/assets/cards/backs/back_2.png b/assets/cards/backs/classic/back_2.png
similarity index 100%
rename from assets/cards/backs/back_2.png
rename to assets/cards/backs/classic/back_2.png
diff --git a/assets/cards/backs/back_3.png b/assets/cards/backs/classic/back_3.png
similarity index 100%
rename from assets/cards/backs/back_3.png
rename to assets/cards/backs/classic/back_3.png
diff --git a/assets/cards/backs/back_4.png b/assets/cards/backs/classic/back_4.png
similarity index 100%
rename from assets/cards/backs/back_4.png
rename to assets/cards/backs/classic/back_4.png
diff --git a/assets/cards/backs/dark/back_0.png b/assets/cards/backs/dark/back_0.png
new file mode 100644
index 0000000..edb190d
Binary files /dev/null and b/assets/cards/backs/dark/back_0.png differ
diff --git a/assets/cards/faces/10C.png b/assets/cards/faces/classic/10C.png
similarity index 100%
rename from assets/cards/faces/10C.png
rename to assets/cards/faces/classic/10C.png
diff --git a/assets/cards/faces/10D.png b/assets/cards/faces/classic/10D.png
similarity index 100%
rename from assets/cards/faces/10D.png
rename to assets/cards/faces/classic/10D.png
diff --git a/assets/cards/faces/10H.png b/assets/cards/faces/classic/10H.png
similarity index 100%
rename from assets/cards/faces/10H.png
rename to assets/cards/faces/classic/10H.png
diff --git a/assets/cards/faces/10S.png b/assets/cards/faces/classic/10S.png
similarity index 100%
rename from assets/cards/faces/10S.png
rename to assets/cards/faces/classic/10S.png
diff --git a/assets/cards/faces/2C.png b/assets/cards/faces/classic/2C.png
similarity index 100%
rename from assets/cards/faces/2C.png
rename to assets/cards/faces/classic/2C.png
diff --git a/assets/cards/faces/2D.png b/assets/cards/faces/classic/2D.png
similarity index 100%
rename from assets/cards/faces/2D.png
rename to assets/cards/faces/classic/2D.png
diff --git a/assets/cards/faces/2H.png b/assets/cards/faces/classic/2H.png
similarity index 100%
rename from assets/cards/faces/2H.png
rename to assets/cards/faces/classic/2H.png
diff --git a/assets/cards/faces/2S.png b/assets/cards/faces/classic/2S.png
similarity index 100%
rename from assets/cards/faces/2S.png
rename to assets/cards/faces/classic/2S.png
diff --git a/assets/cards/faces/3C.png b/assets/cards/faces/classic/3C.png
similarity index 100%
rename from assets/cards/faces/3C.png
rename to assets/cards/faces/classic/3C.png
diff --git a/assets/cards/faces/3D.png b/assets/cards/faces/classic/3D.png
similarity index 100%
rename from assets/cards/faces/3D.png
rename to assets/cards/faces/classic/3D.png
diff --git a/assets/cards/faces/3H.png b/assets/cards/faces/classic/3H.png
similarity index 100%
rename from assets/cards/faces/3H.png
rename to assets/cards/faces/classic/3H.png
diff --git a/assets/cards/faces/3S.png b/assets/cards/faces/classic/3S.png
similarity index 100%
rename from assets/cards/faces/3S.png
rename to assets/cards/faces/classic/3S.png
diff --git a/assets/cards/faces/4C.png b/assets/cards/faces/classic/4C.png
similarity index 100%
rename from assets/cards/faces/4C.png
rename to assets/cards/faces/classic/4C.png
diff --git a/assets/cards/faces/4D.png b/assets/cards/faces/classic/4D.png
similarity index 100%
rename from assets/cards/faces/4D.png
rename to assets/cards/faces/classic/4D.png
diff --git a/assets/cards/faces/4H.png b/assets/cards/faces/classic/4H.png
similarity index 100%
rename from assets/cards/faces/4H.png
rename to assets/cards/faces/classic/4H.png
diff --git a/assets/cards/faces/4S.png b/assets/cards/faces/classic/4S.png
similarity index 100%
rename from assets/cards/faces/4S.png
rename to assets/cards/faces/classic/4S.png
diff --git a/assets/cards/faces/5C.png b/assets/cards/faces/classic/5C.png
similarity index 100%
rename from assets/cards/faces/5C.png
rename to assets/cards/faces/classic/5C.png
diff --git a/assets/cards/faces/5D.png b/assets/cards/faces/classic/5D.png
similarity index 100%
rename from assets/cards/faces/5D.png
rename to assets/cards/faces/classic/5D.png
diff --git a/assets/cards/faces/5H.png b/assets/cards/faces/classic/5H.png
similarity index 100%
rename from assets/cards/faces/5H.png
rename to assets/cards/faces/classic/5H.png
diff --git a/assets/cards/faces/5S.png b/assets/cards/faces/classic/5S.png
similarity index 100%
rename from assets/cards/faces/5S.png
rename to assets/cards/faces/classic/5S.png
diff --git a/assets/cards/faces/6C.png b/assets/cards/faces/classic/6C.png
similarity index 100%
rename from assets/cards/faces/6C.png
rename to assets/cards/faces/classic/6C.png
diff --git a/assets/cards/faces/6D.png b/assets/cards/faces/classic/6D.png
similarity index 100%
rename from assets/cards/faces/6D.png
rename to assets/cards/faces/classic/6D.png
diff --git a/assets/cards/faces/6H.png b/assets/cards/faces/classic/6H.png
similarity index 100%
rename from assets/cards/faces/6H.png
rename to assets/cards/faces/classic/6H.png
diff --git a/assets/cards/faces/6S.png b/assets/cards/faces/classic/6S.png
similarity index 100%
rename from assets/cards/faces/6S.png
rename to assets/cards/faces/classic/6S.png
diff --git a/assets/cards/faces/7C.png b/assets/cards/faces/classic/7C.png
similarity index 100%
rename from assets/cards/faces/7C.png
rename to assets/cards/faces/classic/7C.png
diff --git a/assets/cards/faces/7D.png b/assets/cards/faces/classic/7D.png
similarity index 100%
rename from assets/cards/faces/7D.png
rename to assets/cards/faces/classic/7D.png
diff --git a/assets/cards/faces/7H.png b/assets/cards/faces/classic/7H.png
similarity index 100%
rename from assets/cards/faces/7H.png
rename to assets/cards/faces/classic/7H.png
diff --git a/assets/cards/faces/7S.png b/assets/cards/faces/classic/7S.png
similarity index 100%
rename from assets/cards/faces/7S.png
rename to assets/cards/faces/classic/7S.png
diff --git a/assets/cards/faces/8C.png b/assets/cards/faces/classic/8C.png
similarity index 100%
rename from assets/cards/faces/8C.png
rename to assets/cards/faces/classic/8C.png
diff --git a/assets/cards/faces/8D.png b/assets/cards/faces/classic/8D.png
similarity index 100%
rename from assets/cards/faces/8D.png
rename to assets/cards/faces/classic/8D.png
diff --git a/assets/cards/faces/8H.png b/assets/cards/faces/classic/8H.png
similarity index 100%
rename from assets/cards/faces/8H.png
rename to assets/cards/faces/classic/8H.png
diff --git a/assets/cards/faces/8S.png b/assets/cards/faces/classic/8S.png
similarity index 100%
rename from assets/cards/faces/8S.png
rename to assets/cards/faces/classic/8S.png
diff --git a/assets/cards/faces/9C.png b/assets/cards/faces/classic/9C.png
similarity index 100%
rename from assets/cards/faces/9C.png
rename to assets/cards/faces/classic/9C.png
diff --git a/assets/cards/faces/9D.png b/assets/cards/faces/classic/9D.png
similarity index 100%
rename from assets/cards/faces/9D.png
rename to assets/cards/faces/classic/9D.png
diff --git a/assets/cards/faces/9H.png b/assets/cards/faces/classic/9H.png
similarity index 100%
rename from assets/cards/faces/9H.png
rename to assets/cards/faces/classic/9H.png
diff --git a/assets/cards/faces/9S.png b/assets/cards/faces/classic/9S.png
similarity index 100%
rename from assets/cards/faces/9S.png
rename to assets/cards/faces/classic/9S.png
diff --git a/assets/cards/faces/AC.png b/assets/cards/faces/classic/AC.png
similarity index 100%
rename from assets/cards/faces/AC.png
rename to assets/cards/faces/classic/AC.png
diff --git a/assets/cards/faces/AD.png b/assets/cards/faces/classic/AD.png
similarity index 100%
rename from assets/cards/faces/AD.png
rename to assets/cards/faces/classic/AD.png
diff --git a/assets/cards/faces/AH.png b/assets/cards/faces/classic/AH.png
similarity index 100%
rename from assets/cards/faces/AH.png
rename to assets/cards/faces/classic/AH.png
diff --git a/assets/cards/faces/AS.png b/assets/cards/faces/classic/AS.png
similarity index 100%
rename from assets/cards/faces/AS.png
rename to assets/cards/faces/classic/AS.png
diff --git a/assets/cards/faces/JC.png b/assets/cards/faces/classic/JC.png
similarity index 100%
rename from assets/cards/faces/JC.png
rename to assets/cards/faces/classic/JC.png
diff --git a/assets/cards/faces/JD.png b/assets/cards/faces/classic/JD.png
similarity index 100%
rename from assets/cards/faces/JD.png
rename to assets/cards/faces/classic/JD.png
diff --git a/assets/cards/faces/JH.png b/assets/cards/faces/classic/JH.png
similarity index 100%
rename from assets/cards/faces/JH.png
rename to assets/cards/faces/classic/JH.png
diff --git a/assets/cards/faces/JS.png b/assets/cards/faces/classic/JS.png
similarity index 100%
rename from assets/cards/faces/JS.png
rename to assets/cards/faces/classic/JS.png
diff --git a/assets/cards/faces/KC.png b/assets/cards/faces/classic/KC.png
similarity index 100%
rename from assets/cards/faces/KC.png
rename to assets/cards/faces/classic/KC.png
diff --git a/assets/cards/faces/KD.png b/assets/cards/faces/classic/KD.png
similarity index 100%
rename from assets/cards/faces/KD.png
rename to assets/cards/faces/classic/KD.png
diff --git a/assets/cards/faces/KH.png b/assets/cards/faces/classic/KH.png
similarity index 100%
rename from assets/cards/faces/KH.png
rename to assets/cards/faces/classic/KH.png
diff --git a/assets/cards/faces/KS.png b/assets/cards/faces/classic/KS.png
similarity index 100%
rename from assets/cards/faces/KS.png
rename to assets/cards/faces/classic/KS.png
diff --git a/assets/cards/faces/QC.png b/assets/cards/faces/classic/QC.png
similarity index 100%
rename from assets/cards/faces/QC.png
rename to assets/cards/faces/classic/QC.png
diff --git a/assets/cards/faces/QD.png b/assets/cards/faces/classic/QD.png
similarity index 100%
rename from assets/cards/faces/QD.png
rename to assets/cards/faces/classic/QD.png
diff --git a/assets/cards/faces/QH.png b/assets/cards/faces/classic/QH.png
similarity index 100%
rename from assets/cards/faces/QH.png
rename to assets/cards/faces/classic/QH.png
diff --git a/assets/cards/faces/QS.png b/assets/cards/faces/classic/QS.png
similarity index 100%
rename from assets/cards/faces/QS.png
rename to assets/cards/faces/classic/QS.png
diff --git a/assets/cards/faces/dark/10C.png b/assets/cards/faces/dark/10C.png
new file mode 100644
index 0000000..b85de35
Binary files /dev/null and b/assets/cards/faces/dark/10C.png differ
diff --git a/assets/cards/faces/dark/10D.png b/assets/cards/faces/dark/10D.png
new file mode 100644
index 0000000..cdc6966
Binary files /dev/null and b/assets/cards/faces/dark/10D.png differ
diff --git a/assets/cards/faces/dark/10H.png b/assets/cards/faces/dark/10H.png
new file mode 100644
index 0000000..666ddd7
Binary files /dev/null and b/assets/cards/faces/dark/10H.png differ
diff --git a/assets/cards/faces/dark/10S.png b/assets/cards/faces/dark/10S.png
new file mode 100644
index 0000000..8b4ab70
Binary files /dev/null and b/assets/cards/faces/dark/10S.png differ
diff --git a/assets/cards/faces/dark/2C.png b/assets/cards/faces/dark/2C.png
new file mode 100644
index 0000000..fc274a4
Binary files /dev/null and b/assets/cards/faces/dark/2C.png differ
diff --git a/assets/cards/faces/dark/2D.png b/assets/cards/faces/dark/2D.png
new file mode 100644
index 0000000..fdb5b83
Binary files /dev/null and b/assets/cards/faces/dark/2D.png differ
diff --git a/assets/cards/faces/dark/2H.png b/assets/cards/faces/dark/2H.png
new file mode 100644
index 0000000..7caea36
Binary files /dev/null and b/assets/cards/faces/dark/2H.png differ
diff --git a/assets/cards/faces/dark/2S.png b/assets/cards/faces/dark/2S.png
new file mode 100644
index 0000000..e244549
Binary files /dev/null and b/assets/cards/faces/dark/2S.png differ
diff --git a/assets/cards/faces/dark/3C.png b/assets/cards/faces/dark/3C.png
new file mode 100644
index 0000000..3051407
Binary files /dev/null and b/assets/cards/faces/dark/3C.png differ
diff --git a/assets/cards/faces/dark/3D.png b/assets/cards/faces/dark/3D.png
new file mode 100644
index 0000000..ecfabeb
Binary files /dev/null and b/assets/cards/faces/dark/3D.png differ
diff --git a/assets/cards/faces/dark/3H.png b/assets/cards/faces/dark/3H.png
new file mode 100644
index 0000000..1a73c38
Binary files /dev/null and b/assets/cards/faces/dark/3H.png differ
diff --git a/assets/cards/faces/dark/3S.png b/assets/cards/faces/dark/3S.png
new file mode 100644
index 0000000..ebbb1c4
Binary files /dev/null and b/assets/cards/faces/dark/3S.png differ
diff --git a/assets/cards/faces/dark/4C.png b/assets/cards/faces/dark/4C.png
new file mode 100644
index 0000000..1b15576
Binary files /dev/null and b/assets/cards/faces/dark/4C.png differ
diff --git a/assets/cards/faces/dark/4D.png b/assets/cards/faces/dark/4D.png
new file mode 100644
index 0000000..7cca9d1
Binary files /dev/null and b/assets/cards/faces/dark/4D.png differ
diff --git a/assets/cards/faces/dark/4H.png b/assets/cards/faces/dark/4H.png
new file mode 100644
index 0000000..97d7e00
Binary files /dev/null and b/assets/cards/faces/dark/4H.png differ
diff --git a/assets/cards/faces/dark/4S.png b/assets/cards/faces/dark/4S.png
new file mode 100644
index 0000000..1c1d100
Binary files /dev/null and b/assets/cards/faces/dark/4S.png differ
diff --git a/assets/cards/faces/dark/5C.png b/assets/cards/faces/dark/5C.png
new file mode 100644
index 0000000..420d4e3
Binary files /dev/null and b/assets/cards/faces/dark/5C.png differ
diff --git a/assets/cards/faces/dark/5D.png b/assets/cards/faces/dark/5D.png
new file mode 100644
index 0000000..de03fd2
Binary files /dev/null and b/assets/cards/faces/dark/5D.png differ
diff --git a/assets/cards/faces/dark/5H.png b/assets/cards/faces/dark/5H.png
new file mode 100644
index 0000000..486f88e
Binary files /dev/null and b/assets/cards/faces/dark/5H.png differ
diff --git a/assets/cards/faces/dark/5S.png b/assets/cards/faces/dark/5S.png
new file mode 100644
index 0000000..8688980
Binary files /dev/null and b/assets/cards/faces/dark/5S.png differ
diff --git a/assets/cards/faces/dark/6C.png b/assets/cards/faces/dark/6C.png
new file mode 100644
index 0000000..7d9d8f6
Binary files /dev/null and b/assets/cards/faces/dark/6C.png differ
diff --git a/assets/cards/faces/dark/6D.png b/assets/cards/faces/dark/6D.png
new file mode 100644
index 0000000..0ffbd20
Binary files /dev/null and b/assets/cards/faces/dark/6D.png differ
diff --git a/assets/cards/faces/dark/6H.png b/assets/cards/faces/dark/6H.png
new file mode 100644
index 0000000..794e59a
Binary files /dev/null and b/assets/cards/faces/dark/6H.png differ
diff --git a/assets/cards/faces/dark/6S.png b/assets/cards/faces/dark/6S.png
new file mode 100644
index 0000000..93ab3bb
Binary files /dev/null and b/assets/cards/faces/dark/6S.png differ
diff --git a/assets/cards/faces/dark/7C.png b/assets/cards/faces/dark/7C.png
new file mode 100644
index 0000000..6dfa886
Binary files /dev/null and b/assets/cards/faces/dark/7C.png differ
diff --git a/assets/cards/faces/dark/7D.png b/assets/cards/faces/dark/7D.png
new file mode 100644
index 0000000..70234bf
Binary files /dev/null and b/assets/cards/faces/dark/7D.png differ
diff --git a/assets/cards/faces/dark/7H.png b/assets/cards/faces/dark/7H.png
new file mode 100644
index 0000000..b5ccb9c
Binary files /dev/null and b/assets/cards/faces/dark/7H.png differ
diff --git a/assets/cards/faces/dark/7S.png b/assets/cards/faces/dark/7S.png
new file mode 100644
index 0000000..96a5818
Binary files /dev/null and b/assets/cards/faces/dark/7S.png differ
diff --git a/assets/cards/faces/dark/8C.png b/assets/cards/faces/dark/8C.png
new file mode 100644
index 0000000..83fe3ae
Binary files /dev/null and b/assets/cards/faces/dark/8C.png differ
diff --git a/assets/cards/faces/dark/8D.png b/assets/cards/faces/dark/8D.png
new file mode 100644
index 0000000..69415a4
Binary files /dev/null and b/assets/cards/faces/dark/8D.png differ
diff --git a/assets/cards/faces/dark/8H.png b/assets/cards/faces/dark/8H.png
new file mode 100644
index 0000000..adc662e
Binary files /dev/null and b/assets/cards/faces/dark/8H.png differ
diff --git a/assets/cards/faces/dark/8S.png b/assets/cards/faces/dark/8S.png
new file mode 100644
index 0000000..bf2b5bd
Binary files /dev/null and b/assets/cards/faces/dark/8S.png differ
diff --git a/assets/cards/faces/dark/9C.png b/assets/cards/faces/dark/9C.png
new file mode 100644
index 0000000..aeacb3c
Binary files /dev/null and b/assets/cards/faces/dark/9C.png differ
diff --git a/assets/cards/faces/dark/9D.png b/assets/cards/faces/dark/9D.png
new file mode 100644
index 0000000..f848270
Binary files /dev/null and b/assets/cards/faces/dark/9D.png differ
diff --git a/assets/cards/faces/dark/9H.png b/assets/cards/faces/dark/9H.png
new file mode 100644
index 0000000..aa2aa1d
Binary files /dev/null and b/assets/cards/faces/dark/9H.png differ
diff --git a/assets/cards/faces/dark/9S.png b/assets/cards/faces/dark/9S.png
new file mode 100644
index 0000000..295781e
Binary files /dev/null and b/assets/cards/faces/dark/9S.png differ
diff --git a/assets/cards/faces/dark/AC.png b/assets/cards/faces/dark/AC.png
new file mode 100644
index 0000000..6b34f56
Binary files /dev/null and b/assets/cards/faces/dark/AC.png differ
diff --git a/assets/cards/faces/dark/AD.png b/assets/cards/faces/dark/AD.png
new file mode 100644
index 0000000..5bcc5ba
Binary files /dev/null and b/assets/cards/faces/dark/AD.png differ
diff --git a/assets/cards/faces/dark/AH.png b/assets/cards/faces/dark/AH.png
new file mode 100644
index 0000000..f18e9be
Binary files /dev/null and b/assets/cards/faces/dark/AH.png differ
diff --git a/assets/cards/faces/dark/AS.png b/assets/cards/faces/dark/AS.png
new file mode 100644
index 0000000..5a5d9e1
Binary files /dev/null and b/assets/cards/faces/dark/AS.png differ
diff --git a/assets/cards/faces/dark/JC.png b/assets/cards/faces/dark/JC.png
new file mode 100644
index 0000000..3a7443a
Binary files /dev/null and b/assets/cards/faces/dark/JC.png differ
diff --git a/assets/cards/faces/dark/JD.png b/assets/cards/faces/dark/JD.png
new file mode 100644
index 0000000..f2893f4
Binary files /dev/null and b/assets/cards/faces/dark/JD.png differ
diff --git a/assets/cards/faces/dark/JH.png b/assets/cards/faces/dark/JH.png
new file mode 100644
index 0000000..e565eac
Binary files /dev/null and b/assets/cards/faces/dark/JH.png differ
diff --git a/assets/cards/faces/dark/JS.png b/assets/cards/faces/dark/JS.png
new file mode 100644
index 0000000..e9298e1
Binary files /dev/null and b/assets/cards/faces/dark/JS.png differ
diff --git a/assets/cards/faces/dark/KC.png b/assets/cards/faces/dark/KC.png
new file mode 100644
index 0000000..1607fde
Binary files /dev/null and b/assets/cards/faces/dark/KC.png differ
diff --git a/assets/cards/faces/dark/KD.png b/assets/cards/faces/dark/KD.png
new file mode 100644
index 0000000..3607484
Binary files /dev/null and b/assets/cards/faces/dark/KD.png differ
diff --git a/assets/cards/faces/dark/KH.png b/assets/cards/faces/dark/KH.png
new file mode 100644
index 0000000..6888abf
Binary files /dev/null and b/assets/cards/faces/dark/KH.png differ
diff --git a/assets/cards/faces/dark/KS.png b/assets/cards/faces/dark/KS.png
new file mode 100644
index 0000000..9a15593
Binary files /dev/null and b/assets/cards/faces/dark/KS.png differ
diff --git a/assets/cards/faces/dark/QC.png b/assets/cards/faces/dark/QC.png
new file mode 100644
index 0000000..34f6b0a
Binary files /dev/null and b/assets/cards/faces/dark/QC.png differ
diff --git a/assets/cards/faces/dark/QD.png b/assets/cards/faces/dark/QD.png
new file mode 100644
index 0000000..ec0a139
Binary files /dev/null and b/assets/cards/faces/dark/QD.png differ
diff --git a/assets/cards/faces/dark/QH.png b/assets/cards/faces/dark/QH.png
new file mode 100644
index 0000000..faa9190
Binary files /dev/null and b/assets/cards/faces/dark/QH.png differ
diff --git a/assets/cards/faces/dark/QS.png b/assets/cards/faces/dark/QS.png
new file mode 100644
index 0000000..550672f
Binary files /dev/null and b/assets/cards/faces/dark/QS.png differ
diff --git a/scripts/gen_classic_pngs.sh b/scripts/gen_classic_pngs.sh
index 842976d..c010ab0 100644
--- a/scripts/gen_classic_pngs.sh
+++ b/scripts/gen_classic_pngs.sh
@@ -1,33 +1,33 @@
#!/usr/bin/env bash
-# Rasterize the classic SVG theme into the web game's card PNG assets.
+# Rasterize card SVG themes into the web game's PNG asset subdirectories.
+# Usage: bash scripts/gen_classic_pngs.sh
+# Outputs: assets/cards/faces/{classic,dark}/ and assets/cards/backs/{classic,dark}/
set -euo pipefail
-CLASSIC="solitaire_engine/assets/themes/classic"
-OUT="assets/cards/faces"
-BACKS_OUT="assets/cards/backs"
-
declare -A SUIT=([clubs]=C [diamonds]=D [hearts]=H [spades]=S)
declare -A RANK=([ace]=A [2]=2 [3]=3 [4]=4 [5]=5 [6]=6 [7]=7 [8]=8 [9]=9 [10]=10 [jack]=J [queen]=Q [king]=K)
-mkdir -p "$OUT" "$BACKS_OUT"
+rasterize_theme() {
+ local theme="$1"
+ local src="solitaire_engine/assets/themes/$theme"
+ local faces_out="assets/cards/faces/$theme"
+ local backs_out="assets/cards/backs/$theme"
+ mkdir -p "$faces_out" "$backs_out"
-for svg in "$CLASSIC"/*_*.svg; do
- base=$(basename "$svg" .svg) # e.g. clubs_ace
- suit_name="${base%%_*}" # clubs
- rank_name="${base#*_}" # ace
- suit_code="${SUIT[$suit_name]:-}"
- rank_code="${RANK[$rank_name]:-}"
- if [ -z "$suit_code" ] || [ -z "$rank_code" ]; then
- echo "skip: $base"
- continue
- fi
- out="$OUT/${rank_code}${suit_code}.png"
- rsvg-convert -w 256 -h 384 "$svg" -o "$out"
- echo " $base -> $out"
-done
+ for svg in "$src"/*_*.svg; do
+ local base suit_name rank_name suit_code rank_code
+ base=$(basename "$svg" .svg)
+ suit_name="${base%%_*}"
+ rank_name="${base#*_}"
+ suit_code="${SUIT[$suit_name]:-}"
+ rank_code="${RANK[$rank_name]:-}"
+ [ -z "$suit_code" ] || [ -z "$rank_code" ] && continue
+ rsvg-convert -w 256 -h 384 "$svg" -o "$faces_out/${rank_code}${suit_code}.png"
+ done
-# Back
-rsvg-convert -w 256 -h 384 "$CLASSIC/back.svg" -o "$BACKS_OUT/back_0.png"
-echo " back -> $BACKS_OUT/back_0.png"
+ rsvg-convert -w 256 -h 384 "$src/back.svg" -o "$backs_out/back_0.png"
+ echo "done: $theme ($(ls "$faces_out" | wc -l) faces)"
+}
-echo "Done."
+rasterize_theme classic
+rasterize_theme dark
diff --git a/solitaire_server/web/game.html b/solitaire_server/web/game.html
index 0e5519b..8de5265 100644
--- a/solitaire_server/web/game.html
+++ b/solitaire_server/web/game.html
@@ -39,6 +39,7 @@
+
diff --git a/solitaire_server/web/game.js b/solitaire_server/web/game.js
index 9775f47..04603c5 100644
--- a/solitaire_server/web/game.js
+++ b/solitaire_server/web/game.js
@@ -53,15 +53,21 @@ const SUIT_CODE = { clubs: "C", diamonds: "D", hearts: "H", spades: "S" };
const RANK_LABELS = ["","A","2","3","4","5","6","7","8","9","10","J","Q","K"];
const RED_SUITS = new Set(["diamonds", "hearts"]);
-// Preload all card images so face-up transitions never flash white.
-(function preloadCards() {
+// ── Theme ─────────────────────────────────────────────────────────────────────
+let cardTheme = localStorage.getItem("fs_theme") || "classic";
+
+function preloadTheme(theme) {
const suits = Object.values(SUIT_CODE);
- const ranks = RANK_LABELS.slice(1); // skip empty index 0
+ const ranks = RANK_LABELS.slice(1);
for (const r of ranks) for (const s of suits) {
- new Image().src = `/assets/cards/faces/${r}${s}.png`;
+ new Image().src = `/assets/cards/faces/${theme}/${r}${s}.png`;
}
- new Image().src = "/assets/cards/backs/back_0.png";
-}());
+ new Image().src = `/assets/cards/backs/${theme}/back_0.png`;
+}
+
+// Preload both themes on load so switching is instant.
+preloadTheme("classic");
+preloadTheme("dark");
// ── State ────────────────────────────────────────────────────────────────────
let game = null;
@@ -100,6 +106,7 @@ const hudSeed = document.getElementById("hud-seed");
const btnUndo = document.getElementById("btn-undo");
const btnNew = document.getElementById("btn-new");
const chkDraw3 = document.getElementById("chk-draw3");
+const btnTheme = document.getElementById("btn-theme");
const winOverlay = document.getElementById("win-overlay");
const winScore = document.getElementById("win-score");
const winMoves = document.getElementById("win-moves");
@@ -121,9 +128,14 @@ function scaleBoard() {
board.style.transformOrigin = "center center";
}
+function syncThemeButton() {
+ if (btnTheme) btnTheme.textContent = cardTheme === "classic" ? "Dark" : "Classic";
+}
+
// ── Bootstrap ────────────────────────────────────────────────────────────────
async function bootstrap() {
await init();
+ syncThemeButton();
const params = new URLSearchParams(window.location.search);
const urlSeed = params.has("seed") ? Number(params.get("seed")) : randomSeed();
@@ -302,12 +314,12 @@ function updateCardEl(el, card, pileName, idx, total) {
if (!card.face_up) {
el.className = "card face-down";
- el.style.backgroundImage = "url('/assets/cards/backs/back_0.png')";
+ el.style.backgroundImage = `url('/assets/cards/backs/${cardTheme}/back_0.png')`;
el.innerHTML = "";
} else {
const isRed = RED_SUITS.has(card.suit);
el.className = `card ${isRed ? "red" : "black"}`;
- el.style.backgroundImage = `url('/assets/cards/faces/${RANK_LABELS[card.rank]}${SUIT_CODE[card.suit]}.png')`;
+ el.style.backgroundImage = `url('/assets/cards/faces/${cardTheme}/${RANK_LABELS[card.rank]}${SUIT_CODE[card.suit]}.png')`;
el.innerHTML = "";
}
}
@@ -383,6 +395,12 @@ function attachHandlers() {
drawThree = chkDraw3.checked;
startGame(randomSeed());
});
+ btnTheme.addEventListener("click", () => {
+ cardTheme = cardTheme === "classic" ? "dark" : "classic";
+ localStorage.setItem("fs_theme", cardTheme);
+ syncThemeButton();
+ if (snap) render(snap);
+ });
document.addEventListener("keydown", (e) => {
if (e.target.tagName === "INPUT") return;