diff --git a/badge/apps/pokedex/__init__.py b/badge/apps/pokedex/__init__.py new file mode 100644 index 0000000..a30aecf --- /dev/null +++ b/badge/apps/pokedex/__init__.py @@ -0,0 +1,422 @@ +import sys +import os + +sys.path.insert(0, "/system/apps/pokedex") +os.chdir("/system/apps/pokedex") + +from badgeware import io, brushes, shapes, Image, run, PixelFont, screen, Matrix, file_exists +import random +import math +import network +from urllib.urequest import urlopen +import gc +import json + + +# Colours +white = brushes.color(235, 245, 255) +faded = brushes.color(235, 245, 255, 100) +red = brushes.color(220, 50, 50) +dark_red = brushes.color(150, 30, 30) +dark_bg = brushes.color(15, 15, 25) + +# Type colours +TYPE_COLOURS = { + "normal": (168, 168, 120), + "fire": (240, 80, 48), + "water": (104, 144, 240), + "grass": (120, 200, 80), + "electric": (248, 208, 48), + "ice": (152, 216, 216), + "fighting": (192, 48, 40), + "poison": (160, 64, 160), + "ground": (224, 192, 104), + "flying": (168, 144, 240), + "psychic": (248, 88, 136), + "bug": (168, 184, 32), + "rock": (184, 160, 56), + "ghost": (112, 88, 152), + "dragon": (112, 56, 248), + "dark": (112, 88, 72), + "steel": (184, 184, 208), + "fairy": (238, 153, 172), +} + +small_font = PixelFont.load("/system/assets/fonts/ark.ppf") +large_font = PixelFont.load("/system/assets/fonts/absolute.ppf") + +SPRITE_URL = "https://wsrv.nl/?url=https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{path}{id}.png&w=80&h=80&output=png" +STAT_NAMES = ["hp", "atk", "def", "sp.a", "sp.d", "spd"] +MAX_POKEMON = 151 + +# Load pokemon database from local file (12KB, all Gen 1) +pokedex_db = None +DB_PATH = "/system/apps/pokedex/gen1.json" +try: + f = open(DB_PATH, "r") + pokedex_db = json.loads(f.read()) + f.close() +except Exception as e: + print("Failed to load pokedex db: {}".format(e)) + +WIFI_PASSWORD = None +WIFI_SSID = None +wlan = None +connected = False +ticks_start = None +WIFI_TIMEOUT = 30 + +# State +current_id = 4 # Start with Charmander +pokemon_data = None +pokemon_sprite = None +sprite_task = None +sprite_fetching = False +shiny = False + + +def get_wifi_details(): + global WIFI_PASSWORD, WIFI_SSID + if WIFI_SSID is not None: + return True + try: + sys.path.insert(0, "/") + import secrets + sys.path.pop(0) + WIFI_SSID = getattr(secrets, "WIFI_SSID", None) + WIFI_PASSWORD = getattr(secrets, "WIFI_PASSWORD", None) + del secrets + except ImportError: + WIFI_PASSWORD = None + WIFI_SSID = None + return WIFI_SSID is not None + + +def wlan_start(): + global wlan, ticks_start, connected + if ticks_start is None: + ticks_start = io.ticks + if connected: + return True + try: + if wlan is None: + wlan = network.WLAN(network.STA_IF) + wlan.active(True) + if wlan.isconnected(): + connected = True + return True + wlan.connect(WIFI_SSID, WIFI_PASSWORD) + connected = wlan.isconnected() + except Exception as e: + print("WiFi error: {}".format(e)) + return False + if io.ticks - ticks_start < WIFI_TIMEOUT * 1000: + return connected + return False + + +def sprite_path(pokemon_id, is_shiny=False): + if is_shiny: + return "/system/shiny/{}.png".format(pokemon_id) + return "/system/sprites/{}.png".format(pokemon_id) + + +def fetch_sprite(pokemon_id, is_shiny=False): + path = sprite_path(pokemon_id, is_shiny) + if file_exists(path): + return + try: + url_path = "shiny/" if is_shiny else "" + response = urlopen(SPRITE_URL.format(id=pokemon_id, path=url_path), headers={"User-Agent": "Pokedex Badge"}) + data = bytearray(512) + with open(path, "wb") as f: + while True: + length = response.readinto(data) + if length == 0: + break + f.write(data[:length]) + yield + del data + del response + except Exception as e: + try: + os.remove(path) + except: + pass + raise RuntimeError("Sprite fetch failed: {}".format(e)) + finally: + gc.collect() + + +def load_pokemon(pokemon_id): + global pokemon_data + if pokedex_db is None: + pokemon_data = None + return + entry = pokedex_db.get(str(pokemon_id)) + if entry is None: + pokemon_data = None + return + # db format: n=name, t=types, s=[hp,atk,def,spatk,spdef,spd], h=height, w=weight + pokemon_data = { + "name": entry["n"].upper(), + "id": pokemon_id, + "types": entry["t"], + "stats": entry["s"], + "height": entry["h"], + "weight": entry["w"], + } + + +def type_brush(type_name): + rgb = TYPE_COLOURS.get(type_name, (168, 168, 120)) + return brushes.color(*rgb) + + +def draw_type_badge(type_name, x, y): + label = type_name.upper() + screen.font = small_font + w, h = screen.measure_text(label) + screen.brush = type_brush(type_name) + screen.draw(shapes.rounded_rectangle(x, y, w + 4, h + 1, 2)) + screen.brush = white + screen.text(label, x + 2, y) + + +# Fixed bar layout +BAR_X = 97 +BAR_W = 36 + +def draw_stat_bar(name, value, x, y, max_val=154): + fill = min(int((value / max_val) * BAR_W), BAR_W) + + screen.font = small_font + screen.brush = faded + screen.text(name, x, y) + + # Bar background + screen.brush = brushes.color(40, 40, 50) + screen.draw(shapes.rounded_rectangle(BAR_X, y + 2, BAR_W, 5, 2)) + + # Bar fill + if value >= 100: + screen.brush = brushes.color(100, 220, 100) + elif value >= 60: + screen.brush = brushes.color(220, 200, 60) + else: + screen.brush = brushes.color(220, 80, 60) + if fill > 0: + screen.draw(shapes.rounded_rectangle(BAR_X, y + 2, fill, 5, 2)) + + # Value — right-aligned, leaving 2px margin from screen edge + val_str = str(value) + vw, _ = screen.measure_text(val_str) + screen.brush = white + screen.text(val_str, 155 - vw, y) + + +def center_text(text, y): + w, _ = screen.measure_text(text) + screen.text(text, 80 - (w / 2), y) + + +def draw_main_view(): + # Pokeball red header bar + screen.brush = red + screen.draw(shapes.rectangle(0, 0, 160, 14)) + screen.brush = brushes.color(180, 50, 50) + screen.draw(shapes.rectangle(0, 14, 160, 1)) + + # Title + number + screen.font = large_font + screen.brush = white + if pokemon_data: + screen.text("#{:03d}".format(pokemon_data["id"]), 2, -2) + name = pokemon_data["name"] + w, _ = screen.measure_text(name) + screen.text(name, 157 - w, -2) + else: + center_text("POKEDEX", -2) + + # Sprite area (centred in left section) + # Sprite centred in left column (0-65 x, 15-107 y) + sprite_cx = 32 + sprite_cy = 65 + if pokemon_sprite: + bob = math.sin(io.ticks / 400) * 2 + sx = 0 + sy = int(sprite_cy - 40 + bob) + screen.blit(pokemon_sprite, sx, sy) + + # Shiny sparkles — alternating gold and white + if shiny: + sparkle = shapes.squircle(0, 0, 3, 2) + for i in range(6): + t = io.ticks / (500 + i * 130) + phase = i * 1.047 + angle = t * 0.4 + phase + r = 20 + i * 5 + cx = sprite_cx + math.cos(angle) * r + cy = sprite_cy + bob + math.sin(angle) * (r * 0.65) + pulse = math.sin(t * 2.5 + phase * 2) + if pulse > 0.1: + alpha = int(pulse * 200) + 55 + if i % 2 == 0: + screen.brush = brushes.color(248, 220, 48, alpha) + else: + screen.brush = brushes.color(255, 255, 255, alpha) + s = 0.3 + pulse * 0.7 + sparkle.transform = Matrix().translate(cx, cy).rotate(t * 50 + i * 60).scale(s) + screen.draw(sparkle) + else: + screen.brush = faded + squircle = shapes.squircle(0, 0, 8, 4) + for i in range(3): + squircle.transform = Matrix().translate(sprite_cx, sprite_cy).rotate( + (io.ticks + i * 5000) / 40).scale(1 + i / 1.5) + screen.draw(squircle) + + if pokemon_data: + rx = 66 + types = pokemon_data["types"] + + # Type badges right-aligned from screen edge + screen.font = small_font + badge_x = 158 + for t in reversed(types): + label = t.upper() + tw, _ = screen.measure_text(label) + badge_w = tw + 4 + badge_x -= badge_w + draw_type_badge(t, badge_x, 17) + badge_x -= 3 # gap between badges + + # Height / Weight — right-aligned + screen.brush = faded + h_m = pokemon_data["height"] / 10 + w_kg = pokemon_data["weight"] / 10 + hw_text = "{:.1f}m {:.1f}kg".format(h_m, w_kg) + hw_w, _ = screen.measure_text(hw_text) + screen.text(hw_text, 158 - hw_w, 30) + + # Stats + y_start = 40 + for i, name in enumerate(STAT_NAMES): + draw_stat_bar(name, pokemon_data["stats"][i], 69, y_start + i * 10) + + # Shiny indicator + if shiny: + screen.font = small_font + screen.brush = brushes.color(248, 208, 48) + screen.text("SHINY", 3, 16) + + # Button bar at bottom — matches header style + screen.brush = dark_red + screen.draw(shapes.rectangle(0, 107, 160, 1)) + screen.brush = red + screen.draw(shapes.rectangle(0, 108, 160, 12)) + screen.font = small_font + screen.brush = white + screen.text("") + screen.text("next>", 153 - nw, 108) + + +def load_sprite(): + global pokemon_sprite + path = sprite_path(current_id, shiny) + if file_exists(path): + try: + pokemon_sprite = Image.load(path) + except: + pokemon_sprite = None + else: + pokemon_sprite = None + + +def update(): + global current_id, sprite_task, sprite_fetching, pokemon_data, pokemon_sprite, connected, shiny + + # Clear screen + screen.brush = dark_bg + screen.draw(shapes.rectangle(0, 0, 160, 120)) + + if pokedex_db is None: + screen.font = large_font + screen.brush = red + center_text("POKEDEX", 20) + screen.font = small_font + screen.brush = white + center_text("gen1.json missing!", 50) + center_text("Re-flash badge files", 65) + return + + # Input handling (0 = KEEFYMON, 1-151 = Gen 1) + changed = False + if io.BUTTON_C in io.pressed: + current_id = (current_id + 1) % (MAX_POKEMON + 1) + changed = True + if io.BUTTON_A in io.pressed: + current_id = (current_id - 1) % (MAX_POKEMON + 1) + changed = True + if io.BUTTON_B in io.pressed: + current_id = random.randint(0, MAX_POKEMON) + changed = True + + # Toggle shiny with UP button + if io.BUTTON_UP in io.pressed: + shiny = not shiny + pokemon_sprite = None + sprite_task = None + sprite_fetching = False + load_sprite() + gc.collect() + + # Force sprite refresh on A+C hold + if io.BUTTON_A in io.held and io.BUTTON_C in io.held: + changed = True + try: + os.remove(sprite_path(current_id, shiny)) + except: + pass + + if changed: + pokemon_sprite = None + sprite_task = None + sprite_fetching = False + load_pokemon(current_id) + load_sprite() + gc.collect() + + # Fetch sprite over WiFi if not cached + if pokemon_sprite is None and not sprite_fetching: + if get_wifi_details(): + if wlan_start(): + connected = True + sprite_fetching = True + gc.collect() + sprite_task = fetch_sprite(current_id, shiny) + + if sprite_task: + try: + next(sprite_task) + except StopIteration: + sprite_task = None + sprite_fetching = False + load_sprite() + except Exception as e: + print("Sprite error: {}".format(e)) + sprite_task = None + sprite_fetching = False + + draw_main_view() + + +# Load initial pokemon on startup +load_pokemon(current_id) +load_sprite() + +if __name__ == "__main__": + run(update) diff --git a/badge/apps/pokedex/fetch_sprites.py b/badge/apps/pokedex/fetch_sprites.py new file mode 100644 index 0000000..bb628f5 --- /dev/null +++ b/badge/apps/pokedex/fetch_sprites.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Download Gen 1 Pokemon sprites for the badge Pokedex app. + +Fetches normal and shiny sprites (64x64 PNG) from PokeAPI via wsrv.nl proxy. +Sprites are Nintendo/Game Freak/The Pokemon Company's intellectual property +and are NOT included in this repository. This script fetches them for +personal use on your badge. + +Usage: + python3 fetch_sprites.py # Download to ./sprites/ and ./shiny/ + python3 fetch_sprites.py /Volumes/BADGER # Download directly to badge +""" + +import os +import sys +import subprocess +from concurrent.futures import ThreadPoolExecutor, as_completed + +BASE_URL = "https://wsrv.nl/?url=https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{path}{id}.png&w=80&h=80&output=png" +MAX_POKEMON = 151 +WORKERS = 10 + + +def fetch_sprite(pokemon_id, output_dir, shiny=False): + path = "shiny/" if shiny else "" + subdir = "shiny" if shiny else "sprites" + out_dir = os.path.join(output_dir, subdir) + os.makedirs(out_dir, exist_ok=True) + out_path = os.path.join(out_dir, f"{pokemon_id}.png") + + if os.path.exists(out_path): + return pokemon_id, shiny, True + + url = BASE_URL.format(id=pokemon_id, path=path) + try: + result = subprocess.run( + ["curl", "-sf", url, "-o", out_path], + capture_output=True, timeout=30, + ) + return pokemon_id, shiny, result.returncode == 0 + except Exception: + return pokemon_id, shiny, False + + +def main(): + output_dir = sys.argv[1] if len(sys.argv) > 1 else "." + + tasks = [] + for pid in range(1, MAX_POKEMON + 1): + tasks.append((pid, False)) # normal + tasks.append((pid, True)) # shiny + + total = len(tasks) + done = 0 + failed = [] + + print(f"Fetching {total} sprites ({MAX_POKEMON} normal + {MAX_POKEMON} shiny)...") + + with ThreadPoolExecutor(max_workers=WORKERS) as executor: + futures = { + executor.submit(fetch_sprite, pid, output_dir, is_shiny): (pid, is_shiny) + for pid, is_shiny in tasks + } + for future in as_completed(futures): + pid, is_shiny, ok = future.result() + done += 1 + label = "shiny" if is_shiny else "normal" + if not ok: + failed.append((pid, label)) + print(f" FAILED: {label} #{pid}") + if done % 50 == 0: + print(f" {done}/{total} complete...") + + print(f"\nDone! {done - len(failed)}/{total} sprites downloaded.") + if failed: + print(f"{len(failed)} failed: {failed}") + + # Verify + sprites_dir = os.path.join(output_dir, "sprites") + shiny_dir = os.path.join(output_dir, "shiny") + normal_count = len(os.listdir(sprites_dir)) if os.path.isdir(sprites_dir) else 0 + shiny_count = len(os.listdir(shiny_dir)) if os.path.isdir(shiny_dir) else 0 + print(f"Normal: {normal_count}, Shiny: {shiny_count}") + + +if __name__ == "__main__": + main() diff --git a/badge/apps/pokedex/gen1.json b/badge/apps/pokedex/gen1.json new file mode 100644 index 0000000..2c7058f --- /dev/null +++ b/badge/apps/pokedex/gen1.json @@ -0,0 +1 @@ +{"1":{"n":"bulbasaur","t":["grass","poison"],"s":[45,49,49,65,65,45],"h":7,"w":69},"2":{"n":"ivysaur","t":["grass","poison"],"s":[60,62,63,80,80,60],"h":10,"w":130},"3":{"n":"venusaur","t":["grass","poison"],"s":[80,82,83,100,100,80],"h":20,"w":1000},"4":{"n":"charmander","t":["fire"],"s":[39,52,43,60,50,65],"h":6,"w":85},"5":{"n":"charmeleon","t":["fire"],"s":[58,64,58,80,65,80],"h":11,"w":190},"6":{"n":"charizard","t":["fire","flying"],"s":[78,84,78,109,85,100],"h":17,"w":905},"7":{"n":"squirtle","t":["water"],"s":[44,48,65,50,64,43],"h":5,"w":90},"8":{"n":"wartortle","t":["water"],"s":[59,63,80,65,80,58],"h":10,"w":225},"9":{"n":"blastoise","t":["water"],"s":[79,83,100,85,105,78],"h":16,"w":855},"10":{"n":"caterpie","t":["bug"],"s":[45,30,35,20,20,45],"h":3,"w":29},"11":{"n":"metapod","t":["bug"],"s":[50,20,55,25,25,30],"h":7,"w":99},"12":{"n":"butterfree","t":["bug","flying"],"s":[60,45,50,90,80,70],"h":11,"w":320},"13":{"n":"weedle","t":["bug","poison"],"s":[40,35,30,20,20,50],"h":3,"w":32},"14":{"n":"kakuna","t":["bug","poison"],"s":[45,25,50,25,25,35],"h":6,"w":100},"15":{"n":"beedrill","t":["bug","poison"],"s":[65,90,40,45,80,75],"h":10,"w":295},"16":{"n":"pidgey","t":["normal","flying"],"s":[40,45,40,35,35,56],"h":3,"w":18},"17":{"n":"pidgeotto","t":["normal","flying"],"s":[63,60,55,50,50,71],"h":11,"w":300},"18":{"n":"pidgeot","t":["normal","flying"],"s":[83,80,75,70,70,101],"h":15,"w":395},"19":{"n":"rattata","t":["normal"],"s":[30,56,35,25,35,72],"h":3,"w":35},"20":{"n":"raticate","t":["normal"],"s":[55,81,60,50,70,97],"h":7,"w":185},"21":{"n":"spearow","t":["normal","flying"],"s":[40,60,30,31,31,70],"h":3,"w":20},"22":{"n":"fearow","t":["normal","flying"],"s":[65,90,65,61,61,100],"h":12,"w":380},"23":{"n":"ekans","t":["poison"],"s":[35,60,44,40,54,55],"h":20,"w":69},"24":{"n":"arbok","t":["poison"],"s":[60,95,69,65,79,80],"h":35,"w":650},"25":{"n":"pikachu","t":["electric"],"s":[35,55,40,50,50,90],"h":4,"w":60},"26":{"n":"raichu","t":["electric"],"s":[60,90,55,90,80,110],"h":8,"w":300},"27":{"n":"sandshrew","t":["ground"],"s":[50,75,85,20,30,40],"h":6,"w":120},"28":{"n":"sandslash","t":["ground"],"s":[75,100,110,45,55,65],"h":10,"w":295},"29":{"n":"nidoran-f","t":["poison"],"s":[55,47,52,40,40,41],"h":4,"w":70},"30":{"n":"nidorina","t":["poison"],"s":[70,62,67,55,55,56],"h":8,"w":200},"31":{"n":"nidoqueen","t":["poison","ground"],"s":[90,92,87,75,85,76],"h":13,"w":600},"32":{"n":"nidoran-m","t":["poison"],"s":[46,57,40,40,40,50],"h":5,"w":90},"33":{"n":"nidorino","t":["poison"],"s":[61,72,57,55,55,65],"h":9,"w":195},"34":{"n":"nidoking","t":["poison","ground"],"s":[81,102,77,85,75,85],"h":14,"w":620},"35":{"n":"clefairy","t":["fairy"],"s":[70,45,48,60,65,35],"h":6,"w":75},"36":{"n":"clefable","t":["fairy"],"s":[95,70,73,95,90,60],"h":13,"w":400},"37":{"n":"vulpix","t":["fire"],"s":[38,41,40,50,65,65],"h":6,"w":99},"38":{"n":"ninetales","t":["fire"],"s":[73,76,75,81,100,100],"h":11,"w":199},"39":{"n":"jigglypuff","t":["normal","fairy"],"s":[115,45,20,45,25,20],"h":5,"w":55},"40":{"n":"wigglytuff","t":["normal","fairy"],"s":[140,70,45,85,50,45],"h":10,"w":120},"41":{"n":"zubat","t":["poison","flying"],"s":[40,45,35,30,40,55],"h":8,"w":75},"42":{"n":"golbat","t":["poison","flying"],"s":[75,80,70,65,75,90],"h":16,"w":550},"43":{"n":"oddish","t":["grass","poison"],"s":[45,50,55,75,65,30],"h":5,"w":54},"44":{"n":"gloom","t":["grass","poison"],"s":[60,65,70,85,75,40],"h":8,"w":86},"45":{"n":"vileplume","t":["grass","poison"],"s":[75,80,85,110,90,50],"h":12,"w":186},"46":{"n":"paras","t":["bug","grass"],"s":[35,70,55,45,55,25],"h":3,"w":54},"47":{"n":"parasect","t":["bug","grass"],"s":[60,95,80,60,80,30],"h":10,"w":295},"48":{"n":"venonat","t":["bug","poison"],"s":[60,55,50,40,55,45],"h":10,"w":300},"49":{"n":"venomoth","t":["bug","poison"],"s":[70,65,60,90,75,90],"h":15,"w":125},"50":{"n":"diglett","t":["ground"],"s":[10,55,25,35,45,95],"h":2,"w":8},"51":{"n":"dugtrio","t":["ground"],"s":[35,100,50,50,70,120],"h":7,"w":333},"52":{"n":"meowth","t":["normal"],"s":[40,45,35,40,40,90],"h":4,"w":42},"53":{"n":"persian","t":["normal"],"s":[65,70,60,65,65,115],"h":10,"w":320},"54":{"n":"psyduck","t":["water"],"s":[50,52,48,65,50,55],"h":8,"w":196},"55":{"n":"golduck","t":["water"],"s":[80,82,78,95,80,85],"h":17,"w":766},"56":{"n":"mankey","t":["fighting"],"s":[40,80,35,35,45,70],"h":5,"w":280},"57":{"n":"primeape","t":["fighting"],"s":[65,105,60,60,70,95],"h":10,"w":320},"58":{"n":"growlithe","t":["fire"],"s":[55,70,45,70,50,60],"h":7,"w":190},"59":{"n":"arcanine","t":["fire"],"s":[90,110,80,100,80,95],"h":19,"w":1550},"60":{"n":"poliwag","t":["water"],"s":[40,50,40,40,40,90],"h":6,"w":124},"61":{"n":"poliwhirl","t":["water"],"s":[65,65,65,50,50,90],"h":10,"w":200},"62":{"n":"poliwrath","t":["water","fighting"],"s":[90,95,95,70,90,70],"h":13,"w":540},"63":{"n":"abra","t":["psychic"],"s":[25,20,15,105,55,90],"h":9,"w":195},"64":{"n":"kadabra","t":["psychic"],"s":[40,35,30,120,70,105],"h":13,"w":565},"65":{"n":"alakazam","t":["psychic"],"s":[55,50,45,135,95,120],"h":15,"w":480},"66":{"n":"machop","t":["fighting"],"s":[70,80,50,35,35,35],"h":8,"w":195},"67":{"n":"machoke","t":["fighting"],"s":[80,100,70,50,60,45],"h":15,"w":705},"68":{"n":"machamp","t":["fighting"],"s":[90,130,80,65,85,55],"h":16,"w":1300},"69":{"n":"bellsprout","t":["grass","poison"],"s":[50,75,35,70,30,40],"h":7,"w":40},"70":{"n":"weepinbell","t":["grass","poison"],"s":[65,90,50,85,45,55],"h":10,"w":64},"71":{"n":"victreebel","t":["grass","poison"],"s":[80,105,65,100,70,70],"h":17,"w":155},"72":{"n":"tentacool","t":["water","poison"],"s":[40,40,35,50,100,70],"h":9,"w":455},"73":{"n":"tentacruel","t":["water","poison"],"s":[80,70,65,80,120,100],"h":16,"w":550},"74":{"n":"geodude","t":["rock","ground"],"s":[40,80,100,30,30,20],"h":4,"w":200},"75":{"n":"graveler","t":["rock","ground"],"s":[55,95,115,45,45,35],"h":10,"w":1050},"76":{"n":"golem","t":["rock","ground"],"s":[80,120,130,55,65,45],"h":14,"w":3000},"77":{"n":"ponyta","t":["fire"],"s":[50,85,55,65,65,90],"h":10,"w":300},"78":{"n":"rapidash","t":["fire"],"s":[65,100,70,80,80,105],"h":17,"w":950},"79":{"n":"slowpoke","t":["water","psychic"],"s":[90,65,65,40,40,15],"h":12,"w":360},"80":{"n":"slowbro","t":["water","psychic"],"s":[95,75,110,100,80,30],"h":16,"w":785},"81":{"n":"magnemite","t":["electric","steel"],"s":[25,35,70,95,55,45],"h":3,"w":60},"82":{"n":"magneton","t":["electric","steel"],"s":[50,60,95,120,70,70],"h":10,"w":600},"83":{"n":"farfetchd","t":["normal","flying"],"s":[52,90,55,58,62,60],"h":8,"w":150},"84":{"n":"doduo","t":["normal","flying"],"s":[35,85,45,35,35,75],"h":14,"w":392},"85":{"n":"dodrio","t":["normal","flying"],"s":[60,110,70,60,60,110],"h":18,"w":852},"86":{"n":"seel","t":["water"],"s":[65,45,55,45,70,45],"h":11,"w":900},"87":{"n":"dewgong","t":["water","ice"],"s":[90,70,80,70,95,70],"h":17,"w":1200},"88":{"n":"grimer","t":["poison"],"s":[80,80,50,40,50,25],"h":9,"w":300},"89":{"n":"muk","t":["poison"],"s":[105,105,75,65,100,50],"h":12,"w":300},"90":{"n":"shellder","t":["water"],"s":[30,65,100,45,25,40],"h":3,"w":40},"91":{"n":"cloyster","t":["water","ice"],"s":[50,95,180,85,45,70],"h":15,"w":1325},"92":{"n":"gastly","t":["ghost","poison"],"s":[30,35,30,100,35,80],"h":13,"w":1},"93":{"n":"haunter","t":["ghost","poison"],"s":[45,50,45,115,55,95],"h":16,"w":1},"94":{"n":"gengar","t":["ghost","poison"],"s":[60,65,60,130,75,110],"h":15,"w":405},"95":{"n":"onix","t":["rock","ground"],"s":[35,45,160,30,45,70],"h":88,"w":2100},"96":{"n":"drowzee","t":["psychic"],"s":[60,48,45,43,90,42],"h":10,"w":324},"97":{"n":"hypno","t":["psychic"],"s":[85,73,70,73,115,67],"h":16,"w":756},"98":{"n":"krabby","t":["water"],"s":[30,105,90,25,25,50],"h":4,"w":65},"99":{"n":"kingler","t":["water"],"s":[55,130,115,50,50,75],"h":13,"w":600},"100":{"n":"voltorb","t":["electric"],"s":[40,30,50,55,55,100],"h":5,"w":104},"101":{"n":"electrode","t":["electric"],"s":[60,50,70,80,80,150],"h":12,"w":666},"102":{"n":"exeggcute","t":["grass","psychic"],"s":[60,40,80,60,45,40],"h":4,"w":25},"103":{"n":"exeggutor","t":["grass","psychic"],"s":[95,95,85,125,75,55],"h":20,"w":1200},"104":{"n":"cubone","t":["ground"],"s":[50,50,95,40,50,35],"h":4,"w":65},"105":{"n":"marowak","t":["ground"],"s":[60,80,110,50,80,45],"h":10,"w":450},"106":{"n":"hitmonlee","t":["fighting"],"s":[50,120,53,35,110,87],"h":15,"w":498},"107":{"n":"hitmonchan","t":["fighting"],"s":[50,105,79,35,110,76],"h":14,"w":502},"108":{"n":"lickitung","t":["normal"],"s":[90,55,75,60,75,30],"h":12,"w":655},"109":{"n":"koffing","t":["poison"],"s":[40,65,95,60,45,35],"h":6,"w":10},"110":{"n":"weezing","t":["poison"],"s":[65,90,120,85,70,60],"h":12,"w":95},"111":{"n":"rhyhorn","t":["ground","rock"],"s":[80,85,95,30,30,25],"h":10,"w":1150},"112":{"n":"rhydon","t":["ground","rock"],"s":[105,130,120,45,45,40],"h":19,"w":1200},"113":{"n":"chansey","t":["normal"],"s":[250,5,5,35,105,50],"h":11,"w":346},"114":{"n":"tangela","t":["grass"],"s":[65,55,115,100,40,60],"h":10,"w":350},"115":{"n":"kangaskhan","t":["normal"],"s":[105,95,80,40,80,90],"h":22,"w":800},"116":{"n":"horsea","t":["water"],"s":[30,40,70,70,25,60],"h":4,"w":80},"117":{"n":"seadra","t":["water"],"s":[55,65,95,95,45,85],"h":12,"w":250},"118":{"n":"goldeen","t":["water"],"s":[45,67,60,35,50,63],"h":6,"w":150},"119":{"n":"seaking","t":["water"],"s":[80,92,65,65,80,68],"h":13,"w":390},"120":{"n":"staryu","t":["water"],"s":[30,45,55,70,55,85],"h":8,"w":345},"121":{"n":"starmie","t":["water","psychic"],"s":[60,75,85,100,85,115],"h":11,"w":800},"122":{"n":"mr-mime","t":["psychic","fairy"],"s":[40,45,65,100,120,90],"h":13,"w":545},"123":{"n":"scyther","t":["bug","flying"],"s":[70,110,80,55,80,105],"h":15,"w":560},"124":{"n":"jynx","t":["ice","psychic"],"s":[65,50,35,115,95,95],"h":14,"w":406},"125":{"n":"electabuzz","t":["electric"],"s":[65,83,57,95,85,105],"h":11,"w":300},"126":{"n":"magmar","t":["fire"],"s":[65,95,57,100,85,93],"h":13,"w":445},"127":{"n":"pinsir","t":["bug"],"s":[65,125,100,55,70,85],"h":15,"w":550},"128":{"n":"tauros","t":["normal"],"s":[75,100,95,40,70,110],"h":14,"w":884},"129":{"n":"magikarp","t":["water"],"s":[20,10,55,15,20,80],"h":9,"w":100},"130":{"n":"gyarados","t":["water","flying"],"s":[95,125,79,60,100,81],"h":65,"w":2350},"131":{"n":"lapras","t":["water","ice"],"s":[130,85,80,85,95,60],"h":25,"w":2200},"132":{"n":"ditto","t":["normal"],"s":[48,48,48,48,48,48],"h":3,"w":40},"133":{"n":"eevee","t":["normal"],"s":[55,55,50,45,65,55],"h":3,"w":65},"134":{"n":"vaporeon","t":["water"],"s":[130,65,60,110,95,65],"h":10,"w":290},"135":{"n":"jolteon","t":["electric"],"s":[65,65,60,110,95,130],"h":8,"w":245},"136":{"n":"flareon","t":["fire"],"s":[65,130,60,95,110,65],"h":9,"w":250},"137":{"n":"porygon","t":["normal"],"s":[65,60,70,85,75,40],"h":8,"w":365},"138":{"n":"omanyte","t":["rock","water"],"s":[35,40,100,90,55,35],"h":4,"w":75},"139":{"n":"omastar","t":["rock","water"],"s":[70,60,125,115,70,55],"h":10,"w":350},"140":{"n":"kabuto","t":["rock","water"],"s":[30,80,90,55,45,55],"h":5,"w":115},"141":{"n":"kabutops","t":["rock","water"],"s":[60,115,105,65,70,80],"h":13,"w":405},"142":{"n":"aerodactyl","t":["rock","flying"],"s":[80,105,65,60,75,130],"h":18,"w":590},"143":{"n":"snorlax","t":["normal"],"s":[160,110,65,65,110,30],"h":21,"w":4600},"144":{"n":"articuno","t":["ice","flying"],"s":[90,85,100,95,125,85],"h":17,"w":554},"145":{"n":"zapdos","t":["electric","flying"],"s":[90,90,85,125,90,100],"h":16,"w":526},"146":{"n":"moltres","t":["fire","flying"],"s":[90,100,90,125,85,90],"h":20,"w":600},"147":{"n":"dratini","t":["dragon"],"s":[41,64,45,50,50,50],"h":18,"w":33},"148":{"n":"dragonair","t":["dragon"],"s":[61,84,65,70,70,70],"h":40,"w":165},"149":{"n":"dragonite","t":["dragon","flying"],"s":[91,134,95,100,100,80],"h":22,"w":2100},"150":{"n":"mewtwo","t":["psychic"],"s":[106,110,90,154,90,130],"h":20,"w":1220},"151":{"n":"mew","t":["psychic"],"s":[100,100,100,100,100,100],"h":4,"w":40},"0":{"n":"keefymon","t":["fighting","fairy"],"s":[154,154,154,154,154,154],"h":18,"w":900}} \ No newline at end of file diff --git a/badge/apps/pokedex/icon.png b/badge/apps/pokedex/icon.png new file mode 100644 index 0000000..9725a75 Binary files /dev/null and b/badge/apps/pokedex/icon.png differ diff --git a/badge/apps/pokedex/keefymon.png b/badge/apps/pokedex/keefymon.png new file mode 100644 index 0000000..21638b6 Binary files /dev/null and b/badge/apps/pokedex/keefymon.png differ diff --git a/badge/apps/pokedex/keefymon_shiny.png b/badge/apps/pokedex/keefymon_shiny.png new file mode 100644 index 0000000..a3f86b7 Binary files /dev/null and b/badge/apps/pokedex/keefymon_shiny.png differ diff --git a/badge/apps/pokedex/screenshot_charmander.png b/badge/apps/pokedex/screenshot_charmander.png new file mode 100644 index 0000000..2778930 Binary files /dev/null and b/badge/apps/pokedex/screenshot_charmander.png differ diff --git a/badge/apps/pokedex/screenshot_mewtwo.png b/badge/apps/pokedex/screenshot_mewtwo.png new file mode 100644 index 0000000..f44d537 Binary files /dev/null and b/badge/apps/pokedex/screenshot_mewtwo.png differ diff --git a/badge/apps/pokedex/screenshot_pikachu.png b/badge/apps/pokedex/screenshot_pikachu.png new file mode 100644 index 0000000..f49a67b Binary files /dev/null and b/badge/apps/pokedex/screenshot_pikachu.png differ diff --git a/badge/apps/pokedex/screenshot_shiny_pikachu.png b/badge/apps/pokedex/screenshot_shiny_pikachu.png new file mode 100644 index 0000000..20a86ca Binary files /dev/null and b/badge/apps/pokedex/screenshot_shiny_pikachu.png differ