feat: Add new GUI effects view and numerous image assets.

This commit is contained in:
kiyreload27
2025-12-31 16:29:12 +00:00
parent 3b58d234c4
commit 04a2c1bcb3
1051 changed files with 531 additions and 243 deletions

67
README.md Normal file
View File

@@ -0,0 +1,67 @@
# Umamusume Support Card Manager
A tool for managing support cards and their effects in Umamusume (Granblue Fantasy Relink).
## Features
- Web scraping of support card data from GameTora
- Database storage of card information including effects at different levels
- GUI application for viewing and managing support cards
- Deck building functionality
- Character art downloading
## Project Structure
```
.
├── main.py # Main entry point
├── version.py # Version information
├── requirements.txt # Python dependencies
├── scraper/ # Web scraping modules
│ └── gametora_scraper.py # GameTora scraper implementation
├── db/ # Database modules
│ ├── db_init.py # Database initialization
│ └── db_queries.py # Database queries
├── gui/ # GUI components
├── updater/ # Update checking functionality
├── database/ # Database files
├── images/ # Character art images
├── build/ # Build artifacts
└── dist/ # Distribution files
```
## Installation
1. Clone the repository
2. Install dependencies: `pip install -r requirements.txt`
3. Run the application: `python main.py`
## Usage
### GUI Mode (default)
```bash
python main.py
```
### Scraping Mode
```bash
python main.py --scrape
```
## Development
### Code Structure
- `main.py`: Entry point and argument parsing
- `scraper/gametora_scraper.py`: Web scraping logic
- `db/db_init.py`: Database schema initialization
- `gui/`: GUI components (MainWindow, views, etc.)
- `updater/update_checker.py`: Update checking functionality
### Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
## License
MIT

0
config/__init__.py Normal file
View File

35
config/settings.py Normal file
View File

@@ -0,0 +1,35 @@
"""
Application settings and configuration
"""
import os
from pathlib import Path
# Base directory of the application
BASE_DIR = Path(__file__).parent.parent
# Database configuration
DATABASE_PATH = BASE_DIR / "database" / "umamusume.db"
DATABASE_SEED_PATH = BASE_DIR / "database" / "umamusume_seed.db"
# Image directory
IMAGES_DIR = BASE_DIR / "images"
# Application settings
APP_NAME = "UmamusumeCardManager"
VERSION = "7.0.0"
# Scraping settings
SCRAPING_DELAY_MIN = 0.2
SCRAPING_DELAY_MAX = 0.5
MAX_RETRIES = 3
# Browser settings
HEADLESS_MODE = True
BROWSER_TIMEOUT = 60000
# Database schema version
DB_SCHEMA_VERSION = "1.0"
"""
Database schema version for migration tracking
"""

View File

@@ -33,6 +33,10 @@ def init_db(reset=False):
cur.execute("DROP TABLE IF EXISTS support_effects") cur.execute("DROP TABLE IF EXISTS support_effects")
cur.execute("DROP TABLE IF EXISTS owned_cards") cur.execute("DROP TABLE IF EXISTS owned_cards")
cur.execute("DROP TABLE IF EXISTS support_cards") cur.execute("DROP TABLE IF EXISTS support_cards")
else:
# Run migrations for existing database
migrate_add_image_path()
migrate_event_skills_columns()
# Support Cards - main card info # Support Cards - main card info
cur.execute(""" cur.execute("""
@@ -87,6 +91,8 @@ def init_db(reset=False):
skill_id INTEGER PRIMARY KEY AUTOINCREMENT, skill_id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id INTEGER, event_id INTEGER,
skill_name TEXT, skill_name TEXT,
is_gold INTEGER DEFAULT 0,
is_or INTEGER DEFAULT 0,
FOREIGN KEY (event_id) REFERENCES support_events(event_id) FOREIGN KEY (event_id) REFERENCES support_events(event_id)
) )
""") """)
@@ -150,5 +156,24 @@ def migrate_add_image_path():
pass # Column already exists pass # Column already exists
conn.close() conn.close()
def migrate_event_skills_columns():
"""Add is_gold and is_or columns to event_skills if they don't exist"""
conn = get_conn()
cur = conn.cursor()
try:
cur.execute("ALTER TABLE event_skills ADD COLUMN is_gold INTEGER DEFAULT 0")
print("Added is_gold column to event_skills")
except sqlite3.OperationalError:
pass # Column already exists
try:
cur.execute("ALTER TABLE event_skills ADD COLUMN is_or INTEGER DEFAULT 0")
print("Added is_or column to event_skills")
except sqlite3.OperationalError:
pass # Column already exists
conn.commit()
conn.close()
if __name__ == "__main__": if __name__ == "__main__":
init_db(reset=True) init_db(reset=True)

View File

@@ -780,7 +780,7 @@ def cleanup_orphaned_data():
# ============================================ # ============================================
def get_all_unique_skills(): def get_all_unique_skills():
"""Get a sorted list of all unique skills from hints and events""" """Get a sorted list of all unique skills from hints and events, with golden indicator"""
conn = get_conn() conn = get_conn()
cur = conn.cursor() cur = conn.cursor()
@@ -788,12 +788,22 @@ def get_all_unique_skills():
cur.execute("SELECT DISTINCT hint_name FROM support_hints") cur.execute("SELECT DISTINCT hint_name FROM support_hints")
hint_skills = {row[0] for row in cur.fetchall() if row[0]} hint_skills = {row[0] for row in cur.fetchall() if row[0]}
# Get skills from events # Get skills from events, marking which are golden
cur.execute("SELECT DISTINCT skill_name FROM event_skills") cur.execute("SELECT DISTINCT skill_name, MAX(is_gold) as is_gold FROM event_skills GROUP BY skill_name")
event_skills = {row[0] for row in cur.fetchall() if row[0]} event_skills_data = cur.fetchall()
event_skills = {}
for skill_name, is_gold in event_skills_data:
if skill_name:
event_skills[skill_name] = bool(is_gold)
# Combine and sort # Combine and mark golden skills
all_skills = sorted(list(hint_skills.union(event_skills))) all_skills = []
for skill in sorted(list(hint_skills.union(event_skills.keys()))):
if skill in event_skills and event_skills[skill]:
# Mark as golden
all_skills.append((skill, True)) # (skill_name, is_golden)
else:
all_skills.append((skill, False))
conn.close() conn.close()
return all_skills return all_skills
@@ -833,9 +843,10 @@ def get_cards_with_skill(skill_name):
if entry_key not in seen_entries: if entry_key not in seen_entries:
results.append({ results.append({
'card_id': row[0], 'card_id': row[0],
'name': row[1], 'name': row[1] or 'Unknown',
'rarity': row[2], 'rarity': row[2] or 'Unknown',
'type': row[3], 'type': row[3] or 'Unknown', # Also include 'card_type' for compatibility
'card_type': row[3] or 'Unknown',
'image_path': row[4], 'image_path': row[4],
'source': 'Training Hint', 'source': 'Training Hint',
'details': row[5] or "Random hint event", 'details': row[5] or "Random hint event",
@@ -843,10 +854,11 @@ def get_cards_with_skill(skill_name):
}) })
seen_entries.add(entry_key) seen_entries.add(entry_key)
# 2. Check Event Skills # 2. Check Event Skills (including golden perks)
cur.execute(""" cur.execute("""
SELECT sc.card_id, sc.name, sc.rarity, sc.card_type, sc.image_path, se.event_name, se.event_id, SELECT sc.card_id, sc.name, sc.rarity, sc.card_type, sc.image_path, se.event_name, se.event_id,
CASE WHEN oc.card_id IS NOT NULL THEN 1 ELSE 0 END as is_owned CASE WHEN oc.card_id IS NOT NULL THEN 1 ELSE 0 END as is_owned,
es.is_gold
FROM event_skills es FROM event_skills es
JOIN support_events se ON es.event_id = se.event_id JOIN support_events se ON es.event_id = se.event_id
JOIN support_cards sc ON se.card_id = sc.card_id JOIN support_cards sc ON se.card_id = sc.card_id
@@ -856,7 +868,7 @@ def get_cards_with_skill(skill_name):
rows = cur.fetchall() rows = cur.fetchall()
for row in rows: for row in rows:
card_id, name, rarity, card_type, image_path, event_name, event_id, is_owned = row card_id, name, rarity, card_type, image_path, event_name, event_id, is_owned, is_gold = row
event_name = event_name.replace('\n', ' ').strip() event_name = event_name.replace('\n', ' ').strip()
# Format event skills (handle OR groups and gold skills) # Format event skills (handle OR groups and gold skills)
@@ -884,20 +896,30 @@ def get_cards_with_skill(skill_name):
formatted_event_skills.extend(other_event_skills) formatted_event_skills.extend(other_event_skills)
# Create a nice string like "Event Name (Skill1, Skill2)" # Create a nice string like "Event Name (Skill1, Skill2)"
details = f"{event_name} ({', '.join(formatted_event_skills)})" if formatted_event_skills else event_name if formatted_event_skills:
details = f"{event_name} ({', '.join(formatted_event_skills)})"
elif event_name:
details = f"{event_name} (Golden Perk)"
else:
details = "Golden Perk Event"
# Mark source as GOLDEN if this is a golden skill
source = "✨ GOLDEN Event" if is_gold else "Event"
entry_key = (card_id, f'Event: {event_name}') entry_key = (card_id, f'Event: {event_name}')
if entry_key not in seen_entries: if entry_key not in seen_entries:
results.append({ results.append({
'card_id': card_id, 'card_id': card_id,
'name': name, 'name': name or 'Unknown',
'rarity': rarity, 'rarity': rarity or 'Unknown',
'type': card_type, 'type': card_type or 'Unknown', # Also include 'card_type' for compatibility
'card_type': card_type or 'Unknown',
'image_path': image_path, 'image_path': image_path,
'source': 'Event', 'source': source,
'details': details, 'details': details or 'No details available',
'is_owned': bool(is_owned) 'is_owned': bool(is_owned),
'is_gold': bool(is_gold)
}) })
seen_entries.add(entry_key) seen_entries.add(entry_key)

View File

@@ -331,7 +331,7 @@ class CardListFrame(ttk.Frame):
if resolved_path and os.path.exists(resolved_path): if resolved_path and os.path.exists(resolved_path):
try: try:
pil_img = Image.open(resolved_path) pil_img = Image.open(resolved_path)
pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS) pil_img.thumbnail((48, 48), Image.Resampling.LANCZOS)
img = ImageTk.PhotoImage(pil_img) img = ImageTk.PhotoImage(pil_img)
self.icon_cache[card_id] = img self.icon_cache[card_id] = img
except: except:

View File

@@ -49,13 +49,13 @@ class CardSlot(tk.Frame):
# Image Area (Left) # Image Area (Left)
self.image_label = tk.Label(self, bg=BG_MEDIUM, text="📭", fg=TEXT_MUTED, self.image_label = tk.Label(self, bg=BG_MEDIUM, text="📭", fg=TEXT_MUTED,
font=('Segoe UI', 20)) font=('Segoe UI', 32))
self.image_label.grid(row=0, column=0, rowspan=3, padx=8, pady=8) self.image_label.grid(row=0, column=0, rowspan=3, padx=12, pady=12)
# Details Area (Right) # Details Area (Right)
self.name_label = tk.Label(self, text="Empty Slot", bg=BG_MEDIUM, fg=TEXT_MUTED, self.name_label = tk.Label(self, text="Empty Slot", bg=BG_MEDIUM, fg=TEXT_PRIMARY,
font=FONT_BODY_BOLD, anchor='w', wraplength=130) font=FONT_BODY_BOLD, anchor='w', wraplength=180) # Increased wrap
self.name_label.grid(row=0, column=1, sticky='w', padx=8, pady=(10, 0)) self.name_label.grid(row=0, column=1, sticky='w', padx=8, pady=(15, 0))
self.meta_label = tk.Label(self, text="", bg=BG_MEDIUM, fg=TEXT_MUTED, self.meta_label = tk.Label(self, text="", bg=BG_MEDIUM, fg=TEXT_MUTED,
font=FONT_SMALL, anchor='w') font=FONT_SMALL, anchor='w')
@@ -139,7 +139,7 @@ class CardSlot(tk.Frame):
def reset(self): def reset(self):
self.name_label.config(text="Empty Slot", fg=TEXT_MUTED) self.name_label.config(text="Empty Slot", fg=TEXT_MUTED)
self.meta_label.config(text="Click a card to add") self.meta_label.config(text="Click a card to add")
self.image_label.config(image='', text="📭", font=('Segoe UI', 20)) self.image_label.config(image='', text="📭", font=('Segoe UI', 32))
self.config(highlightbackground=BG_LIGHT) self.config(highlightbackground=BG_LIGHT)
self.image_ref = None self.image_ref = None
self.toggle_controls(False) self.toggle_controls(False)
@@ -149,7 +149,8 @@ class CardSlot(tk.Frame):
if resolved_path and os.path.exists(resolved_path): if resolved_path and os.path.exists(resolved_path):
try: try:
pil_img = Image.open(resolved_path) pil_img = Image.open(resolved_path)
pil_img.thumbnail((65, 65), Image.Resampling.LANCZOS) # Significantly larger images as requested (120x120)
pil_img.thumbnail((120, 120), Image.Resampling.LANCZOS)
self.image_ref = ImageTk.PhotoImage(pil_img) self.image_ref = ImageTk.PhotoImage(pil_img)
self.image_label.config(image=self.image_ref, text='') self.image_label.config(image=self.image_ref, text='')
except Exception as e: except Exception as e:
@@ -369,7 +370,8 @@ class DeckBuilderFrame(ttk.Frame):
if not img and resolved_path and os.path.exists(resolved_path): if not img and resolved_path and os.path.exists(resolved_path):
try: try:
pil_img = Image.open(resolved_path) pil_img = Image.open(resolved_path)
pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS) # Larger thumbnails in the list too (48x48)
pil_img.thumbnail((48, 48), Image.Resampling.LANCZOS)
img = ImageTk.PhotoImage(pil_img) img = ImageTk.PhotoImage(pil_img)
self.icon_cache[card_id] = img self.icon_cache[card_id] = img
except: except:

View File

@@ -152,7 +152,7 @@ class DeckSkillsFrame(ttk.Frame):
if resolved_path and os.path.exists(resolved_path): if resolved_path and os.path.exists(resolved_path):
try: try:
pil_img = Image.open(resolved_path) pil_img = Image.open(resolved_path)
pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS) pil_img.thumbnail((48, 48), Image.Resampling.LANCZOS)
img = ImageTk.PhotoImage(pil_img) img = ImageTk.PhotoImage(pil_img)
self.icon_cache[card_id] = img self.icon_cache[card_id] = img
except: pass except: pass

View File

@@ -82,7 +82,7 @@ class EffectsFrame(ttk.Frame):
button_frame = tk.Frame(control_frame, bg=BG_MEDIUM) button_frame = tk.Frame(control_frame, bg=BG_MEDIUM)
button_frame.pack(side=tk.LEFT, padx=25) button_frame.pack(side=tk.LEFT, padx=25)
quick_levels = [1, 25, 40, 50] quick_levels = [25, 30, 35, 40, 45, 50]
for lvl in quick_levels: for lvl in quick_levels:
btn = create_styled_button(button_frame, text=f"Lv{lvl}", btn = create_styled_button(button_frame, text=f"Lv{lvl}",
command=lambda l=lvl: self.set_level(l), command=lambda l=lvl: self.set_level(l),

View File

@@ -132,14 +132,23 @@ class SkillSearchFrame(ttk.Frame):
def load_skills(self): def load_skills(self):
"""Load all unique skills into listbox""" """Load all unique skills into listbox"""
self.all_skills = get_all_unique_skills() skills_data = get_all_unique_skills()
self.update_listbox(self.all_skills) # Store as list of (skill_name, is_golden) tuples
self.all_skills = skills_data
self.update_listbox(skills_data)
def update_listbox(self, items): def update_listbox(self, items):
"""Update listbox content""" """Update listbox content"""
self.skill_listbox.delete(0, tk.END) self.skill_listbox.delete(0, tk.END)
for item in items: for item in items:
self.skill_listbox.insert(tk.END, item) if isinstance(item, tuple):
skill_name, is_golden = item
# Display with golden indicator
display_name = f"✨ GOLDEN {skill_name}" if is_golden else skill_name
self.skill_listbox.insert(tk.END, display_name)
else:
# Backward compatibility
self.skill_listbox.insert(tk.END, item)
def filter_skills(self, *args): def filter_skills(self, *args):
"""Filter skills based on search text""" """Filter skills based on search text"""
@@ -148,7 +157,17 @@ class SkillSearchFrame(ttk.Frame):
self.update_listbox(self.all_skills) self.update_listbox(self.all_skills)
return return
filtered = [s for s in self.all_skills if search in s.lower()] # Filter skills - handle both tuple format and string format
filtered = []
for item in self.all_skills:
if isinstance(item, tuple):
skill_name, is_golden = item
if search in skill_name.lower() or (search == "golden" and is_golden):
filtered.append(item)
else:
if search in item.lower():
filtered.append(item)
self.update_listbox(filtered) self.update_listbox(filtered)
def on_filter_changed(self): def on_filter_changed(self):
@@ -162,7 +181,13 @@ class SkillSearchFrame(ttk.Frame):
if not selection: if not selection:
return return
skill_name = self.skill_listbox.get(selection[0]) display_name = self.skill_listbox.get(selection[0])
# Extract actual skill name (remove "✨ GOLDEN " prefix if present)
if display_name.startswith("✨ GOLDEN "):
skill_name = display_name.replace("✨ GOLDEN ", "", 1)
else:
skill_name = display_name
self.current_skill = skill_name self.current_skill = skill_name
self.show_cards_for_skill(skill_name) self.show_cards_for_skill(skill_name)
@@ -191,22 +216,34 @@ class SkillSearchFrame(ttk.Frame):
if resolved_path and os.path.exists(resolved_path): if resolved_path and os.path.exists(resolved_path):
try: try:
pil_img = Image.open(resolved_path) pil_img = Image.open(resolved_path)
pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS) pil_img.thumbnail((48, 48), Image.Resampling.LANCZOS)
img = ImageTk.PhotoImage(pil_img) img = ImageTk.PhotoImage(pil_img)
self.icon_cache[card_id] = img self.icon_cache[card_id] = img
except: except:
pass pass
type_display = f"{get_type_icon(card['type'])} {card['type']}" # Handle both 'type' and 'card_type' keys for compatibility
card_type = card.get('type') or card.get('card_type') or 'Unknown'
type_display = f"{get_type_icon(card_type)} {card_type}"
owned_mark = "" if card.get('is_owned') else "" owned_mark = "" if card.get('is_owned') else ""
# Highlight golden skills in source column
source = card.get('source', 'Event')
if card.get('is_gold', False):
source = f"✨ GOLDEN {source.replace('✨ GOLDEN ', '')}" # Ensure no double prefix
# Handle potential None values
card_name = card.get('name') or 'Unknown'
card_rarity = card.get('rarity') or 'Unknown'
card_details = card.get('details') or 'No details available'
values = ( values = (
owned_mark, owned_mark,
card['name'], card_name,
card['rarity'], card_rarity,
type_display, type_display,
card['source'], source,
card['details'] card_details
) )
if img: if img:

View File

@@ -37,6 +37,7 @@ class MainWindow:
self.root.geometry("1400x850") self.root.geometry("1400x850")
self.root.minsize(1350, 800) self.root.minsize(1350, 800)
# Set icon # Set icon
try: try:
icon_path = resolve_image_path("1_Special Week.png") icon_path = resolve_image_path("1_Special Week.png")
@@ -240,7 +241,9 @@ class MainWindow:
show_update_dialog(self.root) show_update_dialog(self.root)
def run(self): def run(self):
"""Start the application""" """
Start the GUI application and display the main window.
"""
self.root.mainloop() self.root.mainloop()

View File

@@ -209,7 +209,7 @@ def configure_styles(root: tk.Tk):
foreground=TEXT_SECONDARY, foreground=TEXT_SECONDARY,
fieldbackground=BG_MEDIUM, fieldbackground=BG_MEDIUM,
font=FONT_BODY, font=FONT_BODY,
rowheight=40) rowheight=60)
# Deck list style # Deck list style
style.configure('DeckList.Treeview', style.configure('DeckList.Treeview',
@@ -217,7 +217,7 @@ def configure_styles(root: tk.Tk):
foreground=TEXT_SECONDARY, foreground=TEXT_SECONDARY,
fieldbackground=BG_MEDIUM, fieldbackground=BG_MEDIUM,
font=FONT_BODY, font=FONT_BODY,
rowheight=40) rowheight=60)
style.map('DeckList.Treeview', style.map('DeckList.Treeview',
background=[('selected', ACCENT_PRIMARY)]) background=[('selected', ACCENT_PRIMARY)])
@@ -231,14 +231,6 @@ def configure_styles(root: tk.Tk):
style.configure('Horizontal.TScale', style.configure('Horizontal.TScale',
background=BG_DARK) background=BG_DARK)
# ─────────────────────────────────────────────────────────────────────────
# Progressbar styles
# ─────────────────────────────────────────────────────────────────────────
style.configure('TProgressbar',
background=ACCENT_PRIMARY,
troughcolor=BG_MEDIUM,
borderwidth=0,
thickness=8)
# ───────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────
# Scrollbar styles # Scrollbar styles

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Some files were not shown because too many files have changed in this diff Show More