feat: Add new GUI effects view and numerous image assets.
67
README.md
Normal 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
35
config/settings.py
Normal 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
|
||||
"""
|
||||
@@ -33,6 +33,10 @@ def init_db(reset=False):
|
||||
cur.execute("DROP TABLE IF EXISTS support_effects")
|
||||
cur.execute("DROP TABLE IF EXISTS owned_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
|
||||
cur.execute("""
|
||||
@@ -87,6 +91,8 @@ def init_db(reset=False):
|
||||
skill_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER,
|
||||
skill_name TEXT,
|
||||
is_gold INTEGER DEFAULT 0,
|
||||
is_or INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (event_id) REFERENCES support_events(event_id)
|
||||
)
|
||||
""")
|
||||
@@ -150,5 +156,24 @@ def migrate_add_image_path():
|
||||
pass # Column already exists
|
||||
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__":
|
||||
init_db(reset=True)
|
||||
|
||||
@@ -780,7 +780,7 @@ def cleanup_orphaned_data():
|
||||
# ============================================
|
||||
|
||||
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()
|
||||
cur = conn.cursor()
|
||||
|
||||
@@ -788,12 +788,22 @@ def get_all_unique_skills():
|
||||
cur.execute("SELECT DISTINCT hint_name FROM support_hints")
|
||||
hint_skills = {row[0] for row in cur.fetchall() if row[0]}
|
||||
|
||||
# Get skills from events
|
||||
cur.execute("SELECT DISTINCT skill_name FROM event_skills")
|
||||
event_skills = {row[0] for row in cur.fetchall() if row[0]}
|
||||
# Get skills from events, marking which are golden
|
||||
cur.execute("SELECT DISTINCT skill_name, MAX(is_gold) as is_gold FROM event_skills GROUP BY skill_name")
|
||||
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
|
||||
all_skills = sorted(list(hint_skills.union(event_skills)))
|
||||
# Combine and mark golden 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()
|
||||
return all_skills
|
||||
@@ -833,9 +843,10 @@ def get_cards_with_skill(skill_name):
|
||||
if entry_key not in seen_entries:
|
||||
results.append({
|
||||
'card_id': row[0],
|
||||
'name': row[1],
|
||||
'rarity': row[2],
|
||||
'type': row[3],
|
||||
'name': row[1] or 'Unknown',
|
||||
'rarity': row[2] or 'Unknown',
|
||||
'type': row[3] or 'Unknown', # Also include 'card_type' for compatibility
|
||||
'card_type': row[3] or 'Unknown',
|
||||
'image_path': row[4],
|
||||
'source': 'Training Hint',
|
||||
'details': row[5] or "Random hint event",
|
||||
@@ -843,10 +854,11 @@ def get_cards_with_skill(skill_name):
|
||||
})
|
||||
seen_entries.add(entry_key)
|
||||
|
||||
# 2. Check Event Skills
|
||||
# 2. Check Event Skills (including golden perks)
|
||||
cur.execute("""
|
||||
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
|
||||
JOIN support_events se ON es.event_id = se.event_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()
|
||||
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()
|
||||
|
||||
# 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)
|
||||
|
||||
# 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}')
|
||||
|
||||
if entry_key not in seen_entries:
|
||||
results.append({
|
||||
'card_id': card_id,
|
||||
'name': name,
|
||||
'rarity': rarity,
|
||||
'type': card_type,
|
||||
'name': name or 'Unknown',
|
||||
'rarity': rarity or 'Unknown',
|
||||
'type': card_type or 'Unknown', # Also include 'card_type' for compatibility
|
||||
'card_type': card_type or 'Unknown',
|
||||
'image_path': image_path,
|
||||
'source': 'Event',
|
||||
'details': details,
|
||||
'is_owned': bool(is_owned)
|
||||
'source': source,
|
||||
'details': details or 'No details available',
|
||||
'is_owned': bool(is_owned),
|
||||
'is_gold': bool(is_gold)
|
||||
})
|
||||
seen_entries.add(entry_key)
|
||||
|
||||
|
||||
@@ -331,7 +331,7 @@ class CardListFrame(ttk.Frame):
|
||||
if resolved_path and os.path.exists(resolved_path):
|
||||
try:
|
||||
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)
|
||||
self.icon_cache[card_id] = img
|
||||
except:
|
||||
|
||||
@@ -49,13 +49,13 @@ class CardSlot(tk.Frame):
|
||||
|
||||
# Image Area (Left)
|
||||
self.image_label = tk.Label(self, bg=BG_MEDIUM, text="📭", fg=TEXT_MUTED,
|
||||
font=('Segoe UI', 20))
|
||||
self.image_label.grid(row=0, column=0, rowspan=3, padx=8, pady=8)
|
||||
font=('Segoe UI', 32))
|
||||
self.image_label.grid(row=0, column=0, rowspan=3, padx=12, pady=12)
|
||||
|
||||
# Details Area (Right)
|
||||
self.name_label = tk.Label(self, text="Empty Slot", bg=BG_MEDIUM, fg=TEXT_MUTED,
|
||||
font=FONT_BODY_BOLD, anchor='w', wraplength=130)
|
||||
self.name_label.grid(row=0, column=1, sticky='w', padx=8, pady=(10, 0))
|
||||
self.name_label = tk.Label(self, text="Empty Slot", bg=BG_MEDIUM, fg=TEXT_PRIMARY,
|
||||
font=FONT_BODY_BOLD, anchor='w', wraplength=180) # Increased wrap
|
||||
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,
|
||||
font=FONT_SMALL, anchor='w')
|
||||
@@ -139,7 +139,7 @@ class CardSlot(tk.Frame):
|
||||
def reset(self):
|
||||
self.name_label.config(text="Empty Slot", fg=TEXT_MUTED)
|
||||
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.image_ref = None
|
||||
self.toggle_controls(False)
|
||||
@@ -149,7 +149,8 @@ class CardSlot(tk.Frame):
|
||||
if resolved_path and os.path.exists(resolved_path):
|
||||
try:
|
||||
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_label.config(image=self.image_ref, text='')
|
||||
except Exception as e:
|
||||
@@ -369,7 +370,8 @@ class DeckBuilderFrame(ttk.Frame):
|
||||
if not img and resolved_path and os.path.exists(resolved_path):
|
||||
try:
|
||||
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)
|
||||
self.icon_cache[card_id] = img
|
||||
except:
|
||||
|
||||
@@ -152,7 +152,7 @@ class DeckSkillsFrame(ttk.Frame):
|
||||
if resolved_path and os.path.exists(resolved_path):
|
||||
try:
|
||||
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)
|
||||
self.icon_cache[card_id] = img
|
||||
except: pass
|
||||
|
||||
@@ -82,7 +82,7 @@ class EffectsFrame(ttk.Frame):
|
||||
button_frame = tk.Frame(control_frame, bg=BG_MEDIUM)
|
||||
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:
|
||||
btn = create_styled_button(button_frame, text=f"Lv{lvl}",
|
||||
command=lambda l=lvl: self.set_level(l),
|
||||
|
||||
@@ -132,14 +132,23 @@ class SkillSearchFrame(ttk.Frame):
|
||||
|
||||
def load_skills(self):
|
||||
"""Load all unique skills into listbox"""
|
||||
self.all_skills = get_all_unique_skills()
|
||||
self.update_listbox(self.all_skills)
|
||||
skills_data = get_all_unique_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):
|
||||
"""Update listbox content"""
|
||||
self.skill_listbox.delete(0, tk.END)
|
||||
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):
|
||||
"""Filter skills based on search text"""
|
||||
@@ -148,7 +157,17 @@ class SkillSearchFrame(ttk.Frame):
|
||||
self.update_listbox(self.all_skills)
|
||||
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)
|
||||
|
||||
def on_filter_changed(self):
|
||||
@@ -162,7 +181,13 @@ class SkillSearchFrame(ttk.Frame):
|
||||
if not selection:
|
||||
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.show_cards_for_skill(skill_name)
|
||||
|
||||
@@ -191,22 +216,34 @@ class SkillSearchFrame(ttk.Frame):
|
||||
if resolved_path and os.path.exists(resolved_path):
|
||||
try:
|
||||
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)
|
||||
self.icon_cache[card_id] = img
|
||||
except:
|
||||
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 ""
|
||||
|
||||
# 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 = (
|
||||
owned_mark,
|
||||
card['name'],
|
||||
card['rarity'],
|
||||
card_name,
|
||||
card_rarity,
|
||||
type_display,
|
||||
card['source'],
|
||||
card['details']
|
||||
source,
|
||||
card_details
|
||||
)
|
||||
|
||||
if img:
|
||||
|
||||
@@ -37,6 +37,7 @@ class MainWindow:
|
||||
self.root.geometry("1400x850")
|
||||
self.root.minsize(1350, 800)
|
||||
|
||||
|
||||
# Set icon
|
||||
try:
|
||||
icon_path = resolve_image_path("1_Special Week.png")
|
||||
@@ -240,7 +241,9 @@ class MainWindow:
|
||||
show_update_dialog(self.root)
|
||||
|
||||
def run(self):
|
||||
"""Start the application"""
|
||||
"""
|
||||
Start the GUI application and display the main window.
|
||||
"""
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
|
||||
12
gui/theme.py
@@ -209,7 +209,7 @@ def configure_styles(root: tk.Tk):
|
||||
foreground=TEXT_SECONDARY,
|
||||
fieldbackground=BG_MEDIUM,
|
||||
font=FONT_BODY,
|
||||
rowheight=40)
|
||||
rowheight=60)
|
||||
|
||||
# Deck list style
|
||||
style.configure('DeckList.Treeview',
|
||||
@@ -217,7 +217,7 @@ def configure_styles(root: tk.Tk):
|
||||
foreground=TEXT_SECONDARY,
|
||||
fieldbackground=BG_MEDIUM,
|
||||
font=FONT_BODY,
|
||||
rowheight=40)
|
||||
rowheight=60)
|
||||
style.map('DeckList.Treeview',
|
||||
background=[('selected', ACCENT_PRIMARY)])
|
||||
|
||||
@@ -231,14 +231,6 @@ def configure_styles(root: tk.Tk):
|
||||
style.configure('Horizontal.TScale',
|
||||
background=BG_DARK)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Progressbar styles
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
style.configure('TProgressbar',
|
||||
background=ACCENT_PRIMARY,
|
||||
troughcolor=BG_MEDIUM,
|
||||
borderwidth=0,
|
||||
thickness=8)
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Scrollbar styles
|
||||
|
||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |