diff --git a/public/cards/j_8_ball.png b/public/cards/j_8_ball.png new file mode 100644 index 0000000..9a533f3 Binary files /dev/null and b/public/cards/j_8_ball.png differ diff --git a/public/cards/j_abstract.png b/public/cards/j_abstract.png new file mode 100644 index 0000000..3582e16 Binary files /dev/null and b/public/cards/j_abstract.png differ diff --git a/public/cards/j_acrobat.png b/public/cards/j_acrobat.png new file mode 100644 index 0000000..1375607 Binary files /dev/null and b/public/cards/j_acrobat.png differ diff --git a/public/cards/j_ancient.png b/public/cards/j_ancient.png new file mode 100644 index 0000000..8e02f7f Binary files /dev/null and b/public/cards/j_ancient.png differ diff --git a/public/cards/j_arrowhead.png b/public/cards/j_arrowhead.png new file mode 100644 index 0000000..f12165c Binary files /dev/null and b/public/cards/j_arrowhead.png differ diff --git a/public/cards/j_astronomer.png b/public/cards/j_astronomer.png new file mode 100644 index 0000000..d2f236e Binary files /dev/null and b/public/cards/j_astronomer.png differ diff --git a/public/cards/j_banner.png b/public/cards/j_banner.png new file mode 100644 index 0000000..628bee1 Binary files /dev/null and b/public/cards/j_banner.png differ diff --git a/public/cards/j_baron.png b/public/cards/j_baron.png new file mode 100644 index 0000000..370847c Binary files /dev/null and b/public/cards/j_baron.png differ diff --git a/public/cards/j_baseball.png b/public/cards/j_baseball.png new file mode 100644 index 0000000..50ee74d Binary files /dev/null and b/public/cards/j_baseball.png differ diff --git a/public/cards/j_blackboard.png b/public/cards/j_blackboard.png new file mode 100644 index 0000000..38f31a8 Binary files /dev/null and b/public/cards/j_blackboard.png differ diff --git a/public/cards/j_bloodstone.png b/public/cards/j_bloodstone.png new file mode 100644 index 0000000..1d530c5 Binary files /dev/null and b/public/cards/j_bloodstone.png differ diff --git a/public/cards/j_blue_joker.png b/public/cards/j_blue_joker.png new file mode 100644 index 0000000..669e6af Binary files /dev/null and b/public/cards/j_blue_joker.png differ diff --git a/public/cards/j_blueprint.png b/public/cards/j_blueprint.png new file mode 100644 index 0000000..94b130b Binary files /dev/null and b/public/cards/j_blueprint.png differ diff --git a/public/cards/j_bootstraps.png b/public/cards/j_bootstraps.png new file mode 100644 index 0000000..dc55093 Binary files /dev/null and b/public/cards/j_bootstraps.png differ diff --git a/public/cards/j_brainstorm.png b/public/cards/j_brainstorm.png new file mode 100644 index 0000000..59ce5e4 Binary files /dev/null and b/public/cards/j_brainstorm.png differ diff --git a/public/cards/j_bull.png b/public/cards/j_bull.png new file mode 100644 index 0000000..1682587 Binary files /dev/null and b/public/cards/j_bull.png differ diff --git a/public/cards/j_burglar.png b/public/cards/j_burglar.png new file mode 100644 index 0000000..c3d3582 Binary files /dev/null and b/public/cards/j_burglar.png differ diff --git a/public/cards/j_burnt.png b/public/cards/j_burnt.png new file mode 100644 index 0000000..62ad8bb Binary files /dev/null and b/public/cards/j_burnt.png differ diff --git a/public/cards/j_business.png b/public/cards/j_business.png new file mode 100644 index 0000000..721ac9b Binary files /dev/null and b/public/cards/j_business.png differ diff --git a/public/cards/j_caino.png b/public/cards/j_caino.png new file mode 100644 index 0000000..575166c Binary files /dev/null and b/public/cards/j_caino.png differ diff --git a/public/cards/j_campfire.png b/public/cards/j_campfire.png new file mode 100644 index 0000000..e5ea030 Binary files /dev/null and b/public/cards/j_campfire.png differ diff --git a/public/cards/j_card_sharp.png b/public/cards/j_card_sharp.png new file mode 100644 index 0000000..baea074 Binary files /dev/null and b/public/cards/j_card_sharp.png differ diff --git a/public/cards/j_cartomancer.png b/public/cards/j_cartomancer.png new file mode 100644 index 0000000..79502f0 Binary files /dev/null and b/public/cards/j_cartomancer.png differ diff --git a/public/cards/j_castle.png b/public/cards/j_castle.png new file mode 100644 index 0000000..0b2d45f Binary files /dev/null and b/public/cards/j_castle.png differ diff --git a/public/cards/j_cavendish.png b/public/cards/j_cavendish.png new file mode 100644 index 0000000..ab97ec3 Binary files /dev/null and b/public/cards/j_cavendish.png differ diff --git a/public/cards/j_ceremonial.png b/public/cards/j_ceremonial.png new file mode 100644 index 0000000..dc4226b Binary files /dev/null and b/public/cards/j_ceremonial.png differ diff --git a/public/cards/j_certificate.png b/public/cards/j_certificate.png new file mode 100644 index 0000000..654dac3 Binary files /dev/null and b/public/cards/j_certificate.png differ diff --git a/public/cards/j_chaos.png b/public/cards/j_chaos.png new file mode 100644 index 0000000..fb466f1 Binary files /dev/null and b/public/cards/j_chaos.png differ diff --git a/public/cards/j_clever.png b/public/cards/j_clever.png new file mode 100644 index 0000000..9d14cba Binary files /dev/null and b/public/cards/j_clever.png differ diff --git a/public/cards/j_cloud_9.png b/public/cards/j_cloud_9.png new file mode 100644 index 0000000..31294ca Binary files /dev/null and b/public/cards/j_cloud_9.png differ diff --git a/public/cards/j_constellation.png b/public/cards/j_constellation.png new file mode 100644 index 0000000..81618fe Binary files /dev/null and b/public/cards/j_constellation.png differ diff --git a/public/cards/j_crafty.png b/public/cards/j_crafty.png new file mode 100644 index 0000000..61886c5 Binary files /dev/null and b/public/cards/j_crafty.png differ diff --git a/public/cards/j_crazy.png b/public/cards/j_crazy.png new file mode 100644 index 0000000..131b12b Binary files /dev/null and b/public/cards/j_crazy.png differ diff --git a/public/cards/j_credit_card.png b/public/cards/j_credit_card.png new file mode 100644 index 0000000..b43f73a Binary files /dev/null and b/public/cards/j_credit_card.png differ diff --git a/public/cards/j_delayed_grat.png b/public/cards/j_delayed_grat.png new file mode 100644 index 0000000..897bc26 Binary files /dev/null and b/public/cards/j_delayed_grat.png differ diff --git a/public/cards/j_devious.png b/public/cards/j_devious.png new file mode 100644 index 0000000..7657859 Binary files /dev/null and b/public/cards/j_devious.png differ diff --git a/public/cards/j_diet_cola.png b/public/cards/j_diet_cola.png new file mode 100644 index 0000000..57e184f Binary files /dev/null and b/public/cards/j_diet_cola.png differ diff --git a/public/cards/j_dna.png b/public/cards/j_dna.png new file mode 100644 index 0000000..589bfcf Binary files /dev/null and b/public/cards/j_dna.png differ diff --git a/public/cards/j_drivers_license.png b/public/cards/j_drivers_license.png new file mode 100644 index 0000000..5c7d5f8 Binary files /dev/null and b/public/cards/j_drivers_license.png differ diff --git a/public/cards/j_droll.png b/public/cards/j_droll.png new file mode 100644 index 0000000..a56e14c Binary files /dev/null and b/public/cards/j_droll.png differ diff --git a/public/cards/j_drunkard.png b/public/cards/j_drunkard.png new file mode 100644 index 0000000..a73c2b0 Binary files /dev/null and b/public/cards/j_drunkard.png differ diff --git a/public/cards/j_duo.png b/public/cards/j_duo.png new file mode 100644 index 0000000..b90a969 Binary files /dev/null and b/public/cards/j_duo.png differ diff --git a/public/cards/j_dusk.png b/public/cards/j_dusk.png new file mode 100644 index 0000000..b1ac695 Binary files /dev/null and b/public/cards/j_dusk.png differ diff --git a/public/cards/j_egg.png b/public/cards/j_egg.png new file mode 100644 index 0000000..5751d60 Binary files /dev/null and b/public/cards/j_egg.png differ diff --git a/public/cards/j_erosion.png b/public/cards/j_erosion.png new file mode 100644 index 0000000..dfd3d2b Binary files /dev/null and b/public/cards/j_erosion.png differ diff --git a/public/cards/j_even_steven.png b/public/cards/j_even_steven.png new file mode 100644 index 0000000..cd06a74 Binary files /dev/null and b/public/cards/j_even_steven.png differ diff --git a/public/cards/j_faceless.png b/public/cards/j_faceless.png new file mode 100644 index 0000000..28ebbac Binary files /dev/null and b/public/cards/j_faceless.png differ diff --git a/public/cards/j_family.png b/public/cards/j_family.png new file mode 100644 index 0000000..b2b062b Binary files /dev/null and b/public/cards/j_family.png differ diff --git a/public/cards/j_fibonacci.png b/public/cards/j_fibonacci.png new file mode 100644 index 0000000..557a73f Binary files /dev/null and b/public/cards/j_fibonacci.png differ diff --git a/public/cards/j_flash.png b/public/cards/j_flash.png new file mode 100644 index 0000000..0c19216 Binary files /dev/null and b/public/cards/j_flash.png differ diff --git a/public/cards/j_flower_pot.png b/public/cards/j_flower_pot.png new file mode 100644 index 0000000..e9edadb Binary files /dev/null and b/public/cards/j_flower_pot.png differ diff --git a/public/cards/j_fortune_teller.png b/public/cards/j_fortune_teller.png new file mode 100644 index 0000000..f3a6c40 Binary files /dev/null and b/public/cards/j_fortune_teller.png differ diff --git a/public/cards/j_four_fingers.png b/public/cards/j_four_fingers.png new file mode 100644 index 0000000..1a9fcea Binary files /dev/null and b/public/cards/j_four_fingers.png differ diff --git a/public/cards/j_gift.png b/public/cards/j_gift.png new file mode 100644 index 0000000..4423442 Binary files /dev/null and b/public/cards/j_gift.png differ diff --git a/public/cards/j_glass.png b/public/cards/j_glass.png new file mode 100644 index 0000000..3d8bb0a Binary files /dev/null and b/public/cards/j_glass.png differ diff --git a/public/cards/j_gluttenous_joker.png b/public/cards/j_gluttenous_joker.png new file mode 100644 index 0000000..4b998c0 Binary files /dev/null and b/public/cards/j_gluttenous_joker.png differ diff --git a/public/cards/j_golden.png b/public/cards/j_golden.png new file mode 100644 index 0000000..23bd88f Binary files /dev/null and b/public/cards/j_golden.png differ diff --git a/public/cards/j_greedy_joker.png b/public/cards/j_greedy_joker.png new file mode 100644 index 0000000..2e40d36 Binary files /dev/null and b/public/cards/j_greedy_joker.png differ diff --git a/public/cards/j_green_joker.png b/public/cards/j_green_joker.png new file mode 100644 index 0000000..cdad252 Binary files /dev/null and b/public/cards/j_green_joker.png differ diff --git a/public/cards/j_gros_michel.png b/public/cards/j_gros_michel.png new file mode 100644 index 0000000..dda4a35 Binary files /dev/null and b/public/cards/j_gros_michel.png differ diff --git a/public/cards/j_hack.png b/public/cards/j_hack.png new file mode 100644 index 0000000..cd2dabc Binary files /dev/null and b/public/cards/j_hack.png differ diff --git a/public/cards/j_half.png b/public/cards/j_half.png new file mode 100644 index 0000000..c5bd4e2 Binary files /dev/null and b/public/cards/j_half.png differ diff --git a/public/cards/j_hallucination.png b/public/cards/j_hallucination.png new file mode 100644 index 0000000..b4f01b6 Binary files /dev/null and b/public/cards/j_hallucination.png differ diff --git a/public/cards/j_hiker.png b/public/cards/j_hiker.png new file mode 100644 index 0000000..580a428 Binary files /dev/null and b/public/cards/j_hiker.png differ diff --git a/public/cards/j_hit_the_road.png b/public/cards/j_hit_the_road.png new file mode 100644 index 0000000..e200f83 Binary files /dev/null and b/public/cards/j_hit_the_road.png differ diff --git a/public/cards/j_hologram.png b/public/cards/j_hologram.png new file mode 100644 index 0000000..d895b76 Binary files /dev/null and b/public/cards/j_hologram.png differ diff --git a/public/cards/j_ice_cream.png b/public/cards/j_ice_cream.png new file mode 100644 index 0000000..cfd8f42 Binary files /dev/null and b/public/cards/j_ice_cream.png differ diff --git a/public/cards/j_idol.png b/public/cards/j_idol.png new file mode 100644 index 0000000..dde1ac2 Binary files /dev/null and b/public/cards/j_idol.png differ diff --git a/public/cards/j_invisible.png b/public/cards/j_invisible.png new file mode 100644 index 0000000..f70878e Binary files /dev/null and b/public/cards/j_invisible.png differ diff --git a/public/cards/j_joker.png b/public/cards/j_joker.png new file mode 100644 index 0000000..4ca9c05 Binary files /dev/null and b/public/cards/j_joker.png differ diff --git a/public/cards/j_jolly.png b/public/cards/j_jolly.png new file mode 100644 index 0000000..9b07c20 Binary files /dev/null and b/public/cards/j_jolly.png differ diff --git a/public/cards/j_juggler.png b/public/cards/j_juggler.png new file mode 100644 index 0000000..6312ddb Binary files /dev/null and b/public/cards/j_juggler.png differ diff --git a/public/cards/j_locked.png b/public/cards/j_locked.png new file mode 100644 index 0000000..fd00da8 Binary files /dev/null and b/public/cards/j_locked.png differ diff --git a/public/cards/j_loyalty_card.png b/public/cards/j_loyalty_card.png new file mode 100644 index 0000000..c0fbb40 Binary files /dev/null and b/public/cards/j_loyalty_card.png differ diff --git a/public/cards/j_lucky_cat.png b/public/cards/j_lucky_cat.png new file mode 100644 index 0000000..2fe7499 Binary files /dev/null and b/public/cards/j_lucky_cat.png differ diff --git a/public/cards/j_lusty_joker.png b/public/cards/j_lusty_joker.png new file mode 100644 index 0000000..8e764fe Binary files /dev/null and b/public/cards/j_lusty_joker.png differ diff --git a/public/cards/j_mad.png b/public/cards/j_mad.png new file mode 100644 index 0000000..e3a61ea Binary files /dev/null and b/public/cards/j_mad.png differ diff --git a/public/cards/j_madness.png b/public/cards/j_madness.png new file mode 100644 index 0000000..8c4ee2b Binary files /dev/null and b/public/cards/j_madness.png differ diff --git a/public/cards/j_mail.png b/public/cards/j_mail.png new file mode 100644 index 0000000..f9e4048 Binary files /dev/null and b/public/cards/j_mail.png differ diff --git a/public/cards/j_marble.png b/public/cards/j_marble.png new file mode 100644 index 0000000..d1cebdc Binary files /dev/null and b/public/cards/j_marble.png differ diff --git a/public/cards/j_merry_andy.png b/public/cards/j_merry_andy.png new file mode 100644 index 0000000..ae09be8 Binary files /dev/null and b/public/cards/j_merry_andy.png differ diff --git a/public/cards/j_midas_mask.png b/public/cards/j_midas_mask.png new file mode 100644 index 0000000..f036f73 Binary files /dev/null and b/public/cards/j_midas_mask.png differ diff --git a/public/cards/j_mime.png b/public/cards/j_mime.png new file mode 100644 index 0000000..e69a5d8 Binary files /dev/null and b/public/cards/j_mime.png differ diff --git a/public/cards/j_misprint.png b/public/cards/j_misprint.png new file mode 100644 index 0000000..46c7ed5 Binary files /dev/null and b/public/cards/j_misprint.png differ diff --git a/public/cards/j_mp_hanging_chad.png b/public/cards/j_mp_hanging_chad.png new file mode 100644 index 0000000..f3cf504 Binary files /dev/null and b/public/cards/j_mp_hanging_chad.png differ diff --git a/public/cards/j_mystic_summit.png b/public/cards/j_mystic_summit.png new file mode 100644 index 0000000..bb83817 Binary files /dev/null and b/public/cards/j_mystic_summit.png differ diff --git a/public/cards/j_obelisk.png b/public/cards/j_obelisk.png new file mode 100644 index 0000000..8f7b440 Binary files /dev/null and b/public/cards/j_obelisk.png differ diff --git a/public/cards/j_odd_todd.png b/public/cards/j_odd_todd.png new file mode 100644 index 0000000..5c65908 Binary files /dev/null and b/public/cards/j_odd_todd.png differ diff --git a/public/cards/j_onyx_agate.png b/public/cards/j_onyx_agate.png new file mode 100644 index 0000000..28f51e9 Binary files /dev/null and b/public/cards/j_onyx_agate.png differ diff --git a/public/cards/j_oops.png b/public/cards/j_oops.png new file mode 100644 index 0000000..f8dbecc Binary files /dev/null and b/public/cards/j_oops.png differ diff --git a/public/cards/j_order.png b/public/cards/j_order.png new file mode 100644 index 0000000..a9505ff Binary files /dev/null and b/public/cards/j_order.png differ diff --git a/public/cards/j_pareidolia.png b/public/cards/j_pareidolia.png new file mode 100644 index 0000000..a5beb27 Binary files /dev/null and b/public/cards/j_pareidolia.png differ diff --git a/public/cards/j_perkeo.png b/public/cards/j_perkeo.png new file mode 100644 index 0000000..9ce8d7f Binary files /dev/null and b/public/cards/j_perkeo.png differ diff --git a/public/cards/j_photograph.png b/public/cards/j_photograph.png new file mode 100644 index 0000000..1b1921c Binary files /dev/null and b/public/cards/j_photograph.png differ diff --git a/public/cards/j_popcorn.png b/public/cards/j_popcorn.png new file mode 100644 index 0000000..44cd05c Binary files /dev/null and b/public/cards/j_popcorn.png differ diff --git a/public/cards/j_raised_fist.png b/public/cards/j_raised_fist.png new file mode 100644 index 0000000..d056a21 Binary files /dev/null and b/public/cards/j_raised_fist.png differ diff --git a/public/cards/j_ramen.png b/public/cards/j_ramen.png new file mode 100644 index 0000000..1b58afd Binary files /dev/null and b/public/cards/j_ramen.png differ diff --git a/public/cards/j_red_card.png b/public/cards/j_red_card.png new file mode 100644 index 0000000..4a1b579 Binary files /dev/null and b/public/cards/j_red_card.png differ diff --git a/public/cards/j_reserved_parking.png b/public/cards/j_reserved_parking.png new file mode 100644 index 0000000..1b5ac3b Binary files /dev/null and b/public/cards/j_reserved_parking.png differ diff --git a/public/cards/j_ride_the_bus.png b/public/cards/j_ride_the_bus.png new file mode 100644 index 0000000..c09d985 Binary files /dev/null and b/public/cards/j_ride_the_bus.png differ diff --git a/public/cards/j_riff_raff.png b/public/cards/j_riff_raff.png new file mode 100644 index 0000000..781e831 Binary files /dev/null and b/public/cards/j_riff_raff.png differ diff --git a/public/cards/j_ring_master.png b/public/cards/j_ring_master.png new file mode 100644 index 0000000..0658bba Binary files /dev/null and b/public/cards/j_ring_master.png differ diff --git a/public/cards/j_rocket.png b/public/cards/j_rocket.png new file mode 100644 index 0000000..1d1ea55 Binary files /dev/null and b/public/cards/j_rocket.png differ diff --git a/public/cards/j_rough_gem.png b/public/cards/j_rough_gem.png new file mode 100644 index 0000000..2f6590e Binary files /dev/null and b/public/cards/j_rough_gem.png differ diff --git a/public/cards/j_runner.png b/public/cards/j_runner.png new file mode 100644 index 0000000..0d915f5 Binary files /dev/null and b/public/cards/j_runner.png differ diff --git a/public/cards/j_satellite.png b/public/cards/j_satellite.png new file mode 100644 index 0000000..97965f6 Binary files /dev/null and b/public/cards/j_satellite.png differ diff --git a/public/cards/j_scary_face.png b/public/cards/j_scary_face.png new file mode 100644 index 0000000..b7cbdc7 Binary files /dev/null and b/public/cards/j_scary_face.png differ diff --git a/public/cards/j_scholar.png b/public/cards/j_scholar.png new file mode 100644 index 0000000..d5cc0d6 Binary files /dev/null and b/public/cards/j_scholar.png differ diff --git a/public/cards/j_seance.png b/public/cards/j_seance.png new file mode 100644 index 0000000..c4587df Binary files /dev/null and b/public/cards/j_seance.png differ diff --git a/public/cards/j_seeing_double.png b/public/cards/j_seeing_double.png new file mode 100644 index 0000000..a4df5ad Binary files /dev/null and b/public/cards/j_seeing_double.png differ diff --git a/public/cards/j_selzer.png b/public/cards/j_selzer.png new file mode 100644 index 0000000..982a739 Binary files /dev/null and b/public/cards/j_selzer.png differ diff --git a/public/cards/j_shoot_the_moon.png b/public/cards/j_shoot_the_moon.png new file mode 100644 index 0000000..0aaf201 Binary files /dev/null and b/public/cards/j_shoot_the_moon.png differ diff --git a/public/cards/j_shortcut.png b/public/cards/j_shortcut.png new file mode 100644 index 0000000..21991c1 Binary files /dev/null and b/public/cards/j_shortcut.png differ diff --git a/public/cards/j_sixth_sense.png b/public/cards/j_sixth_sense.png new file mode 100644 index 0000000..e9d10fb Binary files /dev/null and b/public/cards/j_sixth_sense.png differ diff --git a/public/cards/j_sly.png b/public/cards/j_sly.png new file mode 100644 index 0000000..abfaccd Binary files /dev/null and b/public/cards/j_sly.png differ diff --git a/public/cards/j_smeared.png b/public/cards/j_smeared.png new file mode 100644 index 0000000..af7e423 Binary files /dev/null and b/public/cards/j_smeared.png differ diff --git a/public/cards/j_smiley.png b/public/cards/j_smiley.png new file mode 100644 index 0000000..1328ca3 Binary files /dev/null and b/public/cards/j_smiley.png differ diff --git a/public/cards/j_sock_and_buskin.png b/public/cards/j_sock_and_buskin.png new file mode 100644 index 0000000..090096f Binary files /dev/null and b/public/cards/j_sock_and_buskin.png differ diff --git a/public/cards/j_space.png b/public/cards/j_space.png new file mode 100644 index 0000000..ea01823 Binary files /dev/null and b/public/cards/j_space.png differ diff --git a/public/cards/j_splash.png b/public/cards/j_splash.png new file mode 100644 index 0000000..61cfada Binary files /dev/null and b/public/cards/j_splash.png differ diff --git a/public/cards/j_square.png b/public/cards/j_square.png new file mode 100644 index 0000000..2069041 Binary files /dev/null and b/public/cards/j_square.png differ diff --git a/public/cards/j_steel_joker.png b/public/cards/j_steel_joker.png new file mode 100644 index 0000000..a8fe6bb Binary files /dev/null and b/public/cards/j_steel_joker.png differ diff --git a/public/cards/j_stencil.png b/public/cards/j_stencil.png new file mode 100644 index 0000000..955c78e Binary files /dev/null and b/public/cards/j_stencil.png differ diff --git a/public/cards/j_stone.png b/public/cards/j_stone.png new file mode 100644 index 0000000..a8ab3c9 Binary files /dev/null and b/public/cards/j_stone.png differ diff --git a/public/cards/j_stuntman.png b/public/cards/j_stuntman.png new file mode 100644 index 0000000..1f59bcd Binary files /dev/null and b/public/cards/j_stuntman.png differ diff --git a/public/cards/j_supernova.png b/public/cards/j_supernova.png new file mode 100644 index 0000000..a29f241 Binary files /dev/null and b/public/cards/j_supernova.png differ diff --git a/public/cards/j_superposition.png b/public/cards/j_superposition.png new file mode 100644 index 0000000..ebf7172 Binary files /dev/null and b/public/cards/j_superposition.png differ diff --git a/public/cards/j_swashbuckler.png b/public/cards/j_swashbuckler.png new file mode 100644 index 0000000..4ee819a Binary files /dev/null and b/public/cards/j_swashbuckler.png differ diff --git a/public/cards/j_throwback.png b/public/cards/j_throwback.png new file mode 100644 index 0000000..eef30c0 Binary files /dev/null and b/public/cards/j_throwback.png differ diff --git a/public/cards/j_ticket.png b/public/cards/j_ticket.png new file mode 100644 index 0000000..d7ab42f Binary files /dev/null and b/public/cards/j_ticket.png differ diff --git a/public/cards/j_to_the_moon.png b/public/cards/j_to_the_moon.png new file mode 100644 index 0000000..90c2f48 Binary files /dev/null and b/public/cards/j_to_the_moon.png differ diff --git a/public/cards/j_todo_list.png b/public/cards/j_todo_list.png new file mode 100644 index 0000000..28d3f0c Binary files /dev/null and b/public/cards/j_todo_list.png differ diff --git a/public/cards/j_trading.png b/public/cards/j_trading.png new file mode 100644 index 0000000..f912941 Binary files /dev/null and b/public/cards/j_trading.png differ diff --git a/public/cards/j_tribe.png b/public/cards/j_tribe.png new file mode 100644 index 0000000..2a4c935 Binary files /dev/null and b/public/cards/j_tribe.png differ diff --git a/public/cards/j_triboulet.png b/public/cards/j_triboulet.png new file mode 100644 index 0000000..2a11444 Binary files /dev/null and b/public/cards/j_triboulet.png differ diff --git a/public/cards/j_trio.png b/public/cards/j_trio.png new file mode 100644 index 0000000..9ddbd68 Binary files /dev/null and b/public/cards/j_trio.png differ diff --git a/public/cards/j_troubadour.png b/public/cards/j_troubadour.png new file mode 100644 index 0000000..b3166fe Binary files /dev/null and b/public/cards/j_troubadour.png differ diff --git a/public/cards/j_trousers.png b/public/cards/j_trousers.png new file mode 100644 index 0000000..5768aa8 Binary files /dev/null and b/public/cards/j_trousers.png differ diff --git a/public/cards/j_turtle_bean.png b/public/cards/j_turtle_bean.png new file mode 100644 index 0000000..98aa076 Binary files /dev/null and b/public/cards/j_turtle_bean.png differ diff --git a/public/cards/j_undiscovered.png b/public/cards/j_undiscovered.png new file mode 100644 index 0000000..de40bc0 Binary files /dev/null and b/public/cards/j_undiscovered.png differ diff --git a/public/cards/j_vagabond.png b/public/cards/j_vagabond.png new file mode 100644 index 0000000..628dbb4 Binary files /dev/null and b/public/cards/j_vagabond.png differ diff --git a/public/cards/j_vampire.png b/public/cards/j_vampire.png new file mode 100644 index 0000000..a687f2a Binary files /dev/null and b/public/cards/j_vampire.png differ diff --git a/public/cards/j_walkie_talkie.png b/public/cards/j_walkie_talkie.png new file mode 100644 index 0000000..b47e196 Binary files /dev/null and b/public/cards/j_walkie_talkie.png differ diff --git a/public/cards/j_wee.png b/public/cards/j_wee.png new file mode 100644 index 0000000..4ca9c05 Binary files /dev/null and b/public/cards/j_wee.png differ diff --git a/public/cards/j_wily.png b/public/cards/j_wily.png new file mode 100644 index 0000000..e4e8ef4 Binary files /dev/null and b/public/cards/j_wily.png differ diff --git a/public/cards/j_wrathful_joker.png b/public/cards/j_wrathful_joker.png new file mode 100644 index 0000000..320efd3 Binary files /dev/null and b/public/cards/j_wrathful_joker.png differ diff --git a/public/cards/j_yorick.png b/public/cards/j_yorick.png new file mode 100644 index 0000000..93b134b Binary files /dev/null and b/public/cards/j_yorick.png differ diff --git a/public/cards/j_zany.png b/public/cards/j_zany.png new file mode 100644 index 0000000..2ecaf5f Binary files /dev/null and b/public/cards/j_zany.png differ diff --git a/src/app/(home)/log-parser/page.tsx b/src/app/(home)/log-parser/page.tsx index bb160cc..6814d2a 100644 --- a/src/app/(home)/log-parser/page.tsx +++ b/src/app/(home)/log-parser/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { OptimizedImage } from '@/components/optimized-image' import { Card, CardContent, @@ -26,6 +27,13 @@ import { TableRow, } from '@/components/ui/table' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { jokers } from '@/shared/jokers' import { useFormatter } from 'next-intl' import { useState } from 'react' @@ -50,27 +58,30 @@ type GameOptions = { stake?: number | null } -// Refined Game type to hold structured data type Game = { id: number // Simple identifier for keys host: string | null guest: string | null + logOwnerName: string | null // Name of the player whose log this is for this game + opponentName: string | null // Name of the opponent relative to the log owner hostMods: string[] guestMods: string[] - isHost: boolean | null - opponentName: string | null + isHost: boolean | null // Log owner's role in lobby creation deck: string | null seed: string | null options: GameOptions | null - moneyGained: number - moneySpent: number - opponentMoneySpent: number + moneyGained: number // Log owner's gains + moneySpent: number // Log owner's spending + opponentMoneySpent: number // Opponent's reported spending (from got message) startDate: Date endDate: Date | null durationSeconds: number | null - lastLives: number - moneySpentPerShop: (number | null)[] - moneySpentPerShopOpponent: (number | null)[] + opponentLastLives: number // Opponent's last known lives (from enemyInfo) + opponentLastSkips: number // Opponent's last known skip count (from enemyInfo) + moneySpentPerShop: (number | null)[] // Log owner's spending/skips per shop + moneySpentPerShopOpponent: (number | null)[] // Opponent's spending/skips per shop + logOwnerFinalJokers: string[] // Log owner's final jokers + opponentFinalJokers: string[] // Opponent's final jokers events: LogEvent[] } @@ -79,10 +90,11 @@ const initGame = (id: number, startDate: Date): Game => ({ id, host: null, guest: null, + logOwnerName: null, // Initialize + opponentName: null, // Initialize hostMods: [], guestMods: [], isHost: null, - opponentName: null, deck: null, seed: null, options: null, @@ -92,9 +104,12 @@ const initGame = (id: number, startDate: Date): Game => ({ startDate, endDate: null, durationSeconds: null, - lastLives: 4, // Default starting lives, might be overridden by options + opponentLastLives: 4, + opponentLastSkips: 0, moneySpentPerShop: [], moneySpentPerShopOpponent: [], + logOwnerFinalJokers: [], + opponentFinalJokers: [], events: [], }) @@ -122,7 +137,7 @@ function boolStrToText(str: string | boolean | undefined | null): string { const lower = str.toLowerCase() if (lower === 'true') return 'Yes' if (lower === 'false') return 'No' - return str // Return original if not true/false + return str } // Main component @@ -135,7 +150,7 @@ export default function LogParser() { const parseLogFile = async (file: File) => { setIsLoading(true) setError(null) - setParsedGames([]) // Clear previous results + setParsedGames([]) try { const content = await file.text() @@ -146,21 +161,57 @@ export default function LogParser() { let lastSeenLobbyOptions: GameOptions | null = null let gameCounter = 0 - // Pre-process to find lobby info associated with game starts - // This is simplified; a more robust approach might be needed for complex logs const gameStartInfos = extractGameStartInfo(logLines) let gameInfoIndex = 0 - + let lastProcessedTimestamp: Date | null = null for (const line of logLines) { + if (!line.trim()) continue const timeMatch = line.match(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/) - const timestamp = timeMatch?.[1] ? new Date(timeMatch[1]) : new Date() // Use current time as fallback + const timestamp = timeMatch?.[1] ? new Date(timeMatch[1]) : new Date() const lineLower = line.toLowerCase() - + lastProcessedTimestamp = timestamp // --- Game Lifecycle --- - if (lineLower.includes('startgame message')) { - // Finalize previous game if it exists + if (line.includes('Client got receiveEndGameJokers message')) { if (currentGame) { - if (!currentGame.endDate) currentGame.endDate = timestamp // Use current line time if no end signal seen + // Mark end date if not already set + if (!currentGame.endDate) { + currentGame.endDate = timestamp + } + // Extract Opponent Jokers + const keysMatch = line.match(/\(keys: ([^)]+)\)/) + if (keysMatch?.[1]) { + currentGame.opponentFinalJokers = keysMatch[1] + .split(';') + .filter(Boolean) // Remove empty strings if any + } + // Extract Seed (often found here) + const seedMatch = line.match(/seed: ([A-Z0-9]+)/) + if (!currentGame.seed && seedMatch?.[1]) { + currentGame.seed = seedMatch[1] + } + } + continue + } + if (line.includes('Client sent message: action:receiveEndGameJokers')) { + if (currentGame) { + // Mark end date if not already set (might happen slightly before 'got') + if (!currentGame.endDate) { + currentGame.endDate = timestamp + } + // Extract Log Owner Jokers + const keysMatch = line.match(/keys:(.+)$/) // Match from keys: to end of line + if (keysMatch?.[1]) { + currentGame.logOwnerFinalJokers = keysMatch[1] + .split(';') + + .filter(Boolean) // Remove empty strings + } + } + continue + } + if (lineLower.includes('startgame message')) { + if (currentGame) { + if (!currentGame.endDate) currentGame.endDate = timestamp currentGame.durationSeconds = currentGame.endDate ? (currentGame.endDate.getTime() - currentGame.startDate.getTime()) / @@ -169,28 +220,45 @@ export default function LogParser() { games.push(currentGame) } - // Start new game gameCounter++ currentGame = initGame(gameCounter, timestamp) - const currentInfo = gameStartInfos[gameInfoIndex++] ?? {} + const currentInfo = + gameStartInfos[gameInfoIndex++] ?? ({} as GameStartInfo) - // Apply pre-parsed lobby info and options + // Assign host/guest first currentGame.host = currentInfo.lobbyInfo?.host ?? null currentGame.guest = currentInfo.lobbyInfo?.guest ?? null currentGame.hostMods = currentInfo.lobbyInfo?.hostHash ?? [] currentGame.guestMods = currentInfo.lobbyInfo?.guestHash ?? [] - currentGame.isHost = currentInfo.lobbyInfo?.isHost ?? null - currentGame.opponentName = currentGame.isHost - ? currentGame.guest - : currentGame.host - currentGame.options = lastSeenLobbyOptions // Apply last seen options - currentGame.deck = lastSeenLobbyOptions?.back ?? null - currentGame.seed = currentInfo.seed ?? null // Use seed found near startGame - if (currentGame.options?.starting_lives) { - currentGame.lastLives = currentGame.options.starting_lives + currentGame.isHost = currentInfo.lobbyInfo?.isHost ?? null // Log owner's role + + // *** Determine Log Owner and Opponent Names based on isHost *** + if (currentGame.isHost !== null) { + if (currentGame.isHost) { + // Log owner was the host + currentGame.logOwnerName = currentGame.host + currentGame.opponentName = currentGame.guest + } else { + // Log owner was the guest + currentGame.logOwnerName = currentGame.guest + currentGame.opponentName = currentGame.host + } + } + // Fallback if names are missing but role is known + if (!currentGame.logOwnerName && currentGame.isHost !== null) { + currentGame.logOwnerName = currentGame.isHost ? 'Host' : 'Guest' + } + if (!currentGame.opponentName && currentGame.isHost !== null) { + currentGame.opponentName = currentGame.isHost ? 'Guest' : 'Host' + } + + currentGame.options = lastSeenLobbyOptions + currentGame.deck = lastSeenLobbyOptions?.back ?? null + currentGame.seed = currentInfo.seed ?? null + if (currentGame.options?.starting_lives) { + currentGame.opponentLastLives = currentGame.options.starting_lives } - // Add system event for game start currentGame.events.push({ timestamp, text: `Game ${gameCounter} Started`, @@ -201,6 +269,12 @@ export default function LogParser() { text: `Host: ${currentGame.host || 'Unknown'}, Guest: ${currentGame.guest || 'Unknown'}`, type: 'info', }) + // Add event indicating log owner's role + currentGame.events.push({ + timestamp, + text: `Log Owner Role: ${currentGame.isHost === null ? 'Unknown' : currentGame.isHost ? 'Host' : 'Guest'} (${currentGame.logOwnerName || 'Unknown'})`, + type: 'info', + }) currentGame.events.push({ timestamp, text: `Deck: ${currentGame.deck || 'Unknown'}`, @@ -211,14 +285,12 @@ export default function LogParser() { text: `Seed: ${currentGame.seed || 'Unknown'}`, type: 'info', }) - // Add more info events for options if needed - continue // Move to next line + continue } if (line.includes('Client got receiveEndGameJokers')) { if (currentGame && !currentGame.endDate) { currentGame.endDate = timestamp - // Sometimes seed is only available here const seedMatch = line.match(/seed: ([A-Z0-9]+)/) if (!currentGame.seed && seedMatch?.[1]) { currentGame.seed = seedMatch[1] @@ -232,12 +304,12 @@ export default function LogParser() { const optionsStr = line.split(' Client sent message:')[1]?.trim() if (optionsStr) { lastSeenLobbyOptions = parseLobbyOptions(optionsStr) - // If a game is active, update its options (might happen mid-game?) if (currentGame && !currentGame.options) { currentGame.options = lastSeenLobbyOptions currentGame.deck = lastSeenLobbyOptions.back ?? currentGame.deck if (lastSeenLobbyOptions.starting_lives) { - currentGame.lastLives = lastSeenLobbyOptions.starting_lives + currentGame.opponentLastLives = + lastSeenLobbyOptions.starting_lives } } } @@ -245,20 +317,48 @@ export default function LogParser() { } // --- In-Game Event Parsing (requires currentGame) --- - if (!currentGame) continue // Skip lines if no game is active + if (!currentGame) continue + // enemyInfo ALWAYS refers to the opponent from the log owner's perspective if (lineLower.includes('enemyinfo')) { - const match = line.match(/lives:(\d+)/) - if (match?.[1]) { - const newLives = Number.parseInt(match[1], 10) - if (!isNaN(newLives) && newLives < currentGame.lastLives) { + // Parse opponent lives + const livesMatch = line.match(/lives:(\d+)/) + if (livesMatch?.[1]) { + const newLives = Number.parseInt(livesMatch[1], 10) + if ( + !Number.isNaN(newLives) && + newLives < currentGame.opponentLastLives + ) { currentGame.events.push({ timestamp, - text: `Opponent lost a life (${currentGame.lastLives} -> ${newLives})`, + text: `Opponent lost a life (${currentGame.opponentLastLives} -> ${newLives})`, type: 'event', }) } - currentGame.lastLives = newLives + currentGame.opponentLastLives = newLives + } + + // Parse opponent skips + const skipsMatch = line.match(/skips: *(\d+)/) + if (skipsMatch?.[1]) { + const newSkips = Number.parseInt(skipsMatch[1], 10) + if ( + !Number.isNaN(newSkips) && + newSkips > currentGame.opponentLastSkips + ) { + const numSkipsOccurred = newSkips - currentGame.opponentLastSkips + for (let i = 0; i < numSkipsOccurred; i++) { + currentGame.moneySpentPerShopOpponent.push(null) + } + currentGame.events.push({ + timestamp, + text: `Opponent skipped ${numSkipsOccurred} shop${numSkipsOccurred > 1 ? 's' : ''} (Total: ${newSkips})`, + type: 'shop', + }) + currentGame.opponentLastSkips = newSkips + } else if (!Number.isNaN(newSkips)) { + currentGame.opponentLastSkips = newSkips + } } continue } @@ -274,12 +374,12 @@ export default function LogParser() { } continue } - + // This message indicates opponent's spending report if (line.includes(' Client got spentLastShop message')) { const match = line.match(/amount: (\d+)/) if (match?.[1]) { const amount = Number.parseInt(match[1], 10) - if (!isNaN(amount)) { + if (!Number.isNaN(amount)) { currentGame.opponentMoneySpent += amount currentGame.moneySpentPerShopOpponent.push(amount) currentGame.events.push({ @@ -292,13 +392,13 @@ export default function LogParser() { continue } + // This message indicates the log owner reporting their spending if (line.includes('Client sent message: action:spentLastShop')) { const match = line.match(/amount:(\d+)/) if (match?.[1]) { const amount = Number.parseInt(match[1], 10) - if (!isNaN(amount)) { + if (!Number.isNaN(amount)) { currentGame.moneySpentPerShop.push(amount) - // Note: Total money spent is tracked via moneymoved/reroll/buy currentGame.events.push({ timestamp, text: `Reported spending $${amount} last shop`, @@ -309,8 +409,9 @@ export default function LogParser() { continue } + // This message indicates the log owner skipped if (line.includes('Client sent message: action:skip')) { - currentGame.moneySpentPerShop.push(null) // Mark shop as skipped + currentGame.moneySpentPerShop.push(null) currentGame.events.push({ timestamp, text: 'Skipped shop', @@ -319,13 +420,14 @@ export default function LogParser() { continue } - // --- Player Actions/Events --- + // --- Log Owner Actions/Events (Client sent ...) --- if (lineLower.includes('client sent')) { + // Log owner gained/spent money directly if (lineLower.includes('moneymoved')) { const match = line.match(/amount: *(-?\d+)/) if (match?.[1]) { const amount = Number.parseInt(match[1], 10) - if (!isNaN(amount)) { + if (!Number.isNaN(amount)) { if (amount >= 0) { currentGame.moneyGained += amount currentGame.events.push({ @@ -335,7 +437,7 @@ export default function LogParser() { }) } else { const spent = Math.abs(amount) - currentGame.moneySpent += spent // Track spending here + currentGame.moneySpent += spent currentGame.events.push({ timestamp, text: `Spent $${spent}`, @@ -345,23 +447,25 @@ export default function LogParser() { } } } else if (line.includes('boughtCardFromShop')) { + // Log owner bought card const cardMatch = line.match(/card:([^,\n]+)/i) - const costMatch = line.match(/cost: *(\d+)/i) // Assuming cost is logged + const costMatch = line.match(/cost: *(\d+)/i) const cardRaw = cardMatch?.[1]?.trim() ?? 'Unknown Card' const cardClean = cardRaw.replace(/^(c_mp_|j_mp_)/, '') const cost = costMatch?.[1] ? Number.parseInt(costMatch[1], 10) : 0 - if (cost > 0) currentGame.moneySpent += cost // Add purchase cost + if (cost > 0) currentGame.moneySpent += cost currentGame.events.push({ timestamp, text: `Bought ${cardClean}${cost > 0 ? ` for $${cost}` : ''}`, type: 'shop', }) } else if (line.includes('rerollShop')) { + // Log owner rerolled const costMatch = line.match(/cost: *(\d+)/i) if (costMatch?.[1]) { const cost = Number.parseInt(costMatch[1], 10) - if (!isNaN(cost)) { - currentGame.moneySpent += cost // Add reroll cost + if (!Number.isNaN(cost)) { + currentGame.moneySpent += cost currentGame.events.push({ timestamp, text: `Rerolled shop for $${cost}`, @@ -370,6 +474,7 @@ export default function LogParser() { } } } else if (lineLower.includes('usedcard')) { + // Log owner used card const match = line.match(/card:([^,\n]+)/i) if (match?.[1]) { const raw = match[1].trim() @@ -388,6 +493,7 @@ export default function LogParser() { }) } } else if (lineLower.includes('setlocation')) { + // Log owner changed location const locMatch = line.match(/location:([a-zA-Z0-9_-]+)/) if (locMatch?.[1]) { const locCode = locMatch[1] @@ -403,17 +509,14 @@ export default function LogParser() { } } // End of line processing loop - // Add the last game if it exists if (currentGame) { if (!currentGame.endDate) { - // Find the timestamp of the last event or the last line processed const lastEventTime = currentGame.events.length > 0 - ? currentGame.events[currentGame.events.length - 1].timestamp + ? currentGame.events[currentGame.events.length - 1]?.timestamp : null - const lastLineTime = timeMatch?.[1] ? new Date(timeMatch[1]) : null currentGame.endDate = - lastEventTime ?? lastLineTime ?? currentGame.startDate // Fallback chain + lastEventTime ?? lastProcessedTimestamp ?? currentGame.startDate // Fallback chain } currentGame.durationSeconds = currentGame.endDate ? (currentGame.endDate.getTime() - currentGame.startDate.getTime()) / @@ -438,222 +541,349 @@ export default function LogParser() { } } + // Generate a default tab value using determined names or fallbacks const defaultTabValue = parsedGames.length > 0 - ? `game-${parsedGames[0].id}-${parsedGames[0].opponentName || 'Unknown'}` + ? `game-${parsedGames![0]!.id}-${parsedGames![0]!.logOwnerName || 'LogOwner'}-vs-${parsedGames![0]!.opponentName || 'Opponent'}` : '' return ( -
Loading and parsing log...
} - {error &&{error}
} + {isLoading &&Loading and parsing log...
} + {error &&{error}
} - {parsedGames.length > 0 && ( -- You Were:{' '} - {game.isHost ? 'Host' : 'Guest'} ( - {game.isHost ? game.host : game.guest}) -
-- Deck: {game.deck || 'Unknown'} -
-- Seed: {game.seed || 'Unknown'} -
-- Ruleset:{' '} - {game.options?.ruleset || 'Default'} -
-- Stake:{' '} - {game.options?.stake ?? 'Unknown'} -
- {/* Add more options as needed */} -- Different Decks:{' '} - {boolStrToText(game.options?.different_decks)} -
-- Different Seeds:{' '} - {boolStrToText(game.options?.different_seeds)} -
-- Death on Round Loss:{' '} - {boolStrToText(game.options?.death_on_round_loss)} -
-- Gold on Life Loss:{' '} - {boolStrToText(game.options?.gold_on_life_loss)} -
-- No Gold on Round Loss:{' '} - {boolStrToText(game.options?.no_gold_on_round_loss)} -
-+ Log Owner's Role:{' '} + {game.isHost === null + ? 'Unknown' + : game.isHost + ? 'Host' + : 'Guest'}{' '} + ({ownerLabel}) +
++ Deck: {game.deck || 'Unknown'} +
++ Seed: {game.seed || 'Unknown'} +
++ Ruleset:{' '} + {game.options?.ruleset || 'Default'} +
++ Stake:{' '} + {game.options?.stake ?? 'Unknown'} +
++ Different Decks:{' '} + {boolStrToText(game.options?.different_decks)} +
++ Different Seeds:{' '} + {boolStrToText(game.options?.different_seeds)} +
++ Death on Round Loss:{' '} + {boolStrToText(game.options?.death_on_round_loss)} +
++ Gold on Life Loss:{' '} + {boolStrToText(game.options?.gold_on_life_loss)} +
++ No Gold on Round Loss:{' '} + {boolStrToText( + game.options?.no_gold_on_round_loss + )} +
+- None detected -
- )} -- None detected -
- )} -+ No data found. +
+ )} ++ No data found. +
+ )} ++ None detected +
+ )} ++ None detected +
+ )} +