diff --git a/webserver.py b/webserver.py
index 374ef0f..709bd1f 100644
--- a/webserver.py
+++ b/webserver.py
@@ -25,6 +25,529 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
"""Get database instance from server"""
return self.server.database
+ @property
+ def bot(self):
+ """Get bot instance from server"""
+ return getattr(self.server, 'bot', None)
+
+ def send_json_response(self, data, status_code=200):
+ """Send a JSON response"""
+ import json
+ self.send_response(status_code)
+ self.send_header('Content-type', 'application/json')
+ self.end_headers()
+ self.wfile.write(json.dumps(data).encode())
+
+ def get_unified_css(self):
+ """Return unified CSS theme for all pages"""
+ return """
+ :root {
+ --bg-primary: #0f0f23;
+ --bg-secondary: #1e1e3f;
+ --bg-tertiary: #2a2a4a;
+ --text-primary: #cccccc;
+ --text-secondary: #aaaaaa;
+ --text-accent: #66ff66;
+ --accent-blue: #4dabf7;
+ --accent-purple: #845ec2;
+ --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ --gradient-secondary: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
+ --border-color: #444466;
+ --hover-color: #3a3a5a;
+ --shadow-color: rgba(0, 0, 0, 0.3);
+ --success-color: #51cf66;
+ --warning-color: #ffd43b;
+ --error-color: #ff6b6b;
+ }
+
+ * {
+ box-sizing: border-box;
+ }
+
+ body {
+ font-family: 'Segoe UI', 'Roboto', 'Arial', sans-serif;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.6;
+ min-height: 100vh;
+ }
+
+ .main-container {
+ padding: 20px;
+ min-height: calc(100vh - 80px);
+ }
+
+ /* Navigation Bar */
+ .navbar {
+ background: var(--gradient-primary);
+ padding: 15px 20px;
+ box-shadow: 0 2px 10px var(--shadow-color);
+ margin-bottom: 0;
+ }
+
+ .nav-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+
+ .nav-brand {
+ font-size: 1.5em;
+ font-weight: bold;
+ color: white;
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+
+ .nav-links {
+ display: flex;
+ gap: 20px;
+ align-items: center;
+ }
+
+ .nav-link {
+ color: white;
+ text-decoration: none;
+ padding: 8px 16px;
+ border-radius: 20px;
+ transition: all 0.3s ease;
+ font-weight: 500;
+ }
+
+ .nav-link:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: translateY(-1px);
+ }
+
+ .nav-link.active {
+ background: rgba(255, 255, 255, 0.3);
+ }
+
+ /* Dropdown Navigation */
+ .nav-dropdown {
+ position: relative;
+ display: inline-block;
+ }
+
+ .dropdown-arrow {
+ font-size: 0.8em;
+ margin-left: 5px;
+ transition: transform 0.3s ease;
+ }
+
+ .nav-dropdown:hover .dropdown-arrow {
+ transform: rotate(180deg);
+ }
+
+ .dropdown-content {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ background: var(--bg-secondary);
+ min-width: 180px;
+ box-shadow: 0 8px 16px var(--shadow-color);
+ border-radius: 8px;
+ z-index: 1000;
+ border: 1px solid var(--border-color);
+ overflow: hidden;
+ margin-top: 5px;
+ }
+
+ .nav-dropdown:hover .dropdown-content {
+ display: block;
+ animation: fadeIn 0.3s ease;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(-10px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ .dropdown-item {
+ color: var(--text-primary);
+ padding: 12px 16px;
+ text-decoration: none;
+ display: block;
+ transition: background-color 0.3s ease;
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ .dropdown-item:last-child {
+ border-bottom: none;
+ }
+
+ .dropdown-item:hover {
+ background: var(--bg-tertiary);
+ color: var(--text-accent);
+ }
+
+ @media (max-width: 768px) {
+ .nav-content {
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ .nav-links {
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 10px;
+ }
+
+ .dropdown-content {
+ position: static;
+ display: none;
+ width: 100%;
+ box-shadow: none;
+ border: none;
+ border-radius: 0;
+ background: var(--bg-tertiary);
+ margin-top: 0;
+ }
+
+ .nav-dropdown:hover .dropdown-content {
+ display: block;
+ }
+ }
+
+ /* Header styling */
+ .header {
+ text-align: center;
+ background: var(--gradient-primary);
+ color: white;
+ padding: 40px 20px;
+ border-radius: 15px;
+ margin-bottom: 30px;
+ box-shadow: 0 5px 20px var(--shadow-color);
+ }
+
+ .header h1 {
+ margin: 0 0 10px 0;
+ font-size: 2.5em;
+ font-weight: bold;
+ }
+
+ .header p {
+ margin: 0;
+ font-size: 1.1em;
+ opacity: 0.9;
+ }
+
+ /* Card styling */
+ .card {
+ background: var(--bg-secondary);
+ border-radius: 15px;
+ padding: 20px;
+ margin-bottom: 20px;
+ box-shadow: 0 4px 15px var(--shadow-color);
+ border: 1px solid var(--border-color);
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+ }
+
+ .card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 25px var(--shadow-color);
+ }
+
+ .card h2 {
+ margin-top: 0;
+ color: var(--text-accent);
+ border-bottom: 2px solid var(--text-accent);
+ padding-bottom: 10px;
+ }
+
+ .card h3 {
+ color: var(--accent-blue);
+ margin-top: 25px;
+ }
+
+ /* Grid layouts */
+ .grid {
+ display: grid;
+ gap: 20px;
+ margin-bottom: 30px;
+ }
+
+ .grid-2 { grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); }
+ .grid-3 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
+ .grid-4 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
+
+ /* Buttons */
+ .btn {
+ display: inline-block;
+ padding: 10px 20px;
+ border: none;
+ border-radius: 25px;
+ text-decoration: none;
+ font-weight: 500;
+ text-align: center;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ margin: 5px;
+ }
+
+ .btn-primary {
+ background: var(--gradient-primary);
+ color: white;
+ }
+
+ .btn-secondary {
+ background: var(--bg-tertiary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ }
+
+ .btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px var(--shadow-color);
+ }
+
+ /* Badges and tags */
+ .badge {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 15px;
+ font-size: 0.85em;
+ font-weight: 500;
+ margin: 2px;
+ }
+
+ .badge-primary { background: var(--accent-blue); color: white; }
+ .badge-secondary { background: var(--bg-tertiary); color: var(--text-primary); }
+ .badge-success { background: var(--success-color); color: white; }
+ .badge-warning { background: var(--warning-color); color: #333; }
+ .badge-error { background: var(--error-color); color: white; }
+
+ /* Tables */
+ .table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 20px 0;
+ background: var(--bg-secondary);
+ border-radius: 10px;
+ overflow: hidden;
+ }
+
+ .table th, .table td {
+ padding: 12px 15px;
+ text-align: left;
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ .table th {
+ background: var(--bg-tertiary);
+ font-weight: bold;
+ color: var(--text-accent);
+ }
+
+ .table tr:hover {
+ background: var(--bg-tertiary);
+ }
+
+ /* Loading and status messages */
+ .loading {
+ text-align: center;
+ padding: 40px;
+ color: var(--text-secondary);
+ }
+
+ .error-message {
+ background: var(--error-color);
+ color: white;
+ padding: 15px;
+ border-radius: 10px;
+ margin: 20px 0;
+ }
+
+ .success-message {
+ background: var(--success-color);
+ color: white;
+ padding: 15px;
+ border-radius: 10px;
+ margin: 20px 0;
+ }
+
+ /* Pet-specific styles for petdex */
+ .pets-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ gap: 20px;
+ margin-top: 20px;
+ }
+
+ .pet-card {
+ background: var(--bg-secondary);
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 4px 15px var(--shadow-color);
+ border: 1px solid var(--border-color);
+ transition: transform 0.3s ease;
+ }
+
+ .pet-card:hover {
+ transform: translateY(-3px);
+ }
+
+ .pet-header {
+ background: var(--bg-tertiary);
+ padding: 15px 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .pet-header h3 {
+ margin: 0;
+ font-size: 1.3em;
+ }
+
+ .type-badge {
+ background: var(--gradient-primary);
+ color: white;
+ padding: 4px 12px;
+ border-radius: 15px;
+ font-size: 0.85em;
+ font-weight: 500;
+ }
+
+ .pet-stats {
+ padding: 15px 20px;
+ background: var(--bg-secondary);
+ }
+
+ .stat-row {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ font-size: 0.9em;
+ }
+
+ .total-stats {
+ text-align: center;
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid var(--border-color);
+ font-weight: 600;
+ color: var(--text-accent);
+ }
+
+ .pet-info {
+ padding: 15px 20px;
+ background: var(--bg-tertiary);
+ font-size: 0.9em;
+ line-height: 1.5;
+ }
+
+ .rarity-section {
+ margin-bottom: 40px;
+ }
+
+ /* Responsive design */
+ @media (max-width: 768px) {
+ .main-container {
+ padding: 10px;
+ }
+
+ .header h1 {
+ font-size: 2em;
+ }
+
+ .card {
+ padding: 15px;
+ }
+
+ .grid-2, .grid-3, .grid-4 {
+ grid-template-columns: 1fr;
+ }
+ }
+ """
+
+ def get_navigation_bar(self, current_page=""):
+ """Return unified navigation bar HTML with dropdown menus"""
+
+ # Define navigation structure with dropdowns
+ nav_structure = [
+ ("", "š Home", []),
+ ("players", "š„ Players", [
+ ("leaderboard", "š Leaderboard"),
+ ("players", "š Statistics")
+ ]),
+ ("locations", "šŗļø Locations", [
+ ("locations", "š¤ļø Weather"),
+ ("locations", "šÆ Spawns"),
+ ("locations", "šļø Gyms")
+ ]),
+ ("petdex", "š Petdex", [
+ ("petdex", "š· by Type"),
+ ("petdex", "ā by Rarity"),
+ ("petdex", "š Search")
+ ]),
+ ("help", "š Help", [
+ ("help", "ā” Commands"),
+ ("help", "š Web Guide"),
+ ("help", "ā FAQ")
+ ])
+ ]
+
+ nav_links = ""
+ for page_path, page_name, subpages in nav_structure:
+ active_class = " active" if current_page == page_path else ""
+ href = f"/{page_path}" if page_path else "/"
+
+ if subpages:
+ # Create dropdown menu
+ dropdown_items = ""
+ for sub_path, sub_name in subpages:
+ sub_href = f"/{sub_path}" if sub_path else "/"
+ dropdown_items += f'{sub_name} '
+
+ nav_links += f'''
+
'''
+ else:
+ # Regular nav link
+ nav_links += f'{page_name} '
+
+ return f"""
+
+
+
š¾ PetBot
+
+ {nav_links}
+
+
+
+ """
+
+ def get_page_template(self, title, content, current_page=""):
+ """Return complete page HTML with unified theme"""
+ return f"""
+
+
+
+
+ {title} - PetBot
+
+
+
+ {self.get_navigation_bar(current_page)}
+
+ {content}
+
+
+"""
+
def do_GET(self):
"""Handle GET requests"""
parsed_path = urlparse(self.path)
@@ -68,139 +591,501 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
def serve_index(self):
"""Serve the main index page"""
- html = """
-
-
-
-
- PetBot Game Hub
-
-
-
-
-
-
-
-
-
š¤ Bot Status: Online and ready for commands!
-
Use !help in #petz for quick command reference
-
-
-"""
+ html = self.get_page_template("PetBot Game Hub", content, "")
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html.encode())
def serve_help(self):
- """Serve the help page"""
- try:
- with open('help.html', 'r') as f:
- content = f.read()
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- self.wfile.write(content.encode())
- except FileNotFoundError:
- self.send_error(404, "Help file not found")
+ """Serve the help page using unified template"""
+ content = """
+
+
+
+
+
+
+
+
!start
+
Begin your pet collecting journey! Creates your trainer account and gives you your first starter pet.
+
Example: !start
+
+
+
!help
+
Get a link to this comprehensive command reference page.
+
Example: !help
+
+
+
!stats
+
View your basic trainer information including level, experience, and money.
+
Example: !stats
+
+
+
+
+
+
+
+
+
+
+
!explore
+
Search your current location for wild pets or items. You might find pets to battle/catch or discover useful items!
+
Example: !explore
+
+
+
!travel <location>
+
Move to a different location. Each area has unique pets and gyms. Some locations require achievements to unlock.
+
Example: !travel whispering woods
+
+
+
!weather
+
Check the current weather effects in your location. Weather affects which pet types spawn more frequently.
+
Example: !weather
+
+
+
!where / !location
+
See which location you're currently in and get information about the area.
+
Example: !where
+
+
+
+
+
šŗļø Available Locations
+
+ Starter Town - Peaceful starting area (Fire/Water/Grass pets)
+ Whispering Woods - Ancient forest (Grass pets + new species: Vinewrap, Bloomtail)
+ Electric Canyon - Charged valley (Electric/Rock pets)
+ Crystal Caves - Underground caverns (Rock/Crystal pets)
+ Frozen Tundra - Icy wasteland (Ice/Water pets)
+ Dragon's Peak - Ultimate challenge (Fire/Rock/Ice pets)
+
+
+
+
+
š¤ļø Weather Effects
+
+ Sunny - 1.5x Fire/Grass spawns (1-2 hours)
+ Rainy - 2.0x Water spawns (45-90 minutes)
+ Thunderstorm - 2.0x Electric spawns (30-60 minutes)
+ Blizzard - 1.7x Ice/Water spawns (1-2 hours)
+ Earthquake - 1.8x Rock spawns (30-90 minutes)
+ Calm - Normal spawns (1.5-3 hours)
+
+
+
+
+
+
+
+
+
+
+
!catch / !capture
+
Attempt to catch a wild pet that appeared during exploration. Success depends on the pet's level and rarity.
+
Example: !catch
+
+
+
!battle
+
Start a turn-based battle with a wild pet. Defeat it to gain experience and money for your active pet.
+
Example: !battle
+
+
+
!attack <move>
+
Use a specific move during battle. Each move has different power, type, and effects.
+
Example: !attack flamethrower
+
+
+
!moves
+
View all available moves for your active pet, including their types and power levels.
+
Example: !moves
+
+
+
!flee
+
Attempt to escape from the current battle. Not always successful!
+
Example: !flee
+
+
+
+
+
+
+
+
+
+
+
!gym
+
List all gyms in your current location with your progress. Shows victories and next difficulty level.
+
Example: !gym
+
+
+
!gym list
+
Show all gyms across all locations with your badge collection progress.
+
Example: !gym list
+
+
+
!gym challenge "<name>"
+
Challenge a gym leader! You must be in the same location as the gym. Difficulty increases with each victory.
+
Example: !gym challenge "Forest Guardian"
+
+
+
!gym info "<name>"
+
Get detailed information about a gym including leader, theme, team, and badge details.
+
Example: !gym info "Storm Master"
+
+
+
+
+ š” Gym Strategy: Each gym specializes in a specific type. Bring pets with type advantages! The more you beat a gym, the harder it gets, but the better the rewards!
+
+
+
+
š Gym Leaders & Badges
+
+
+ š Forest Guardian
+ Location: Starter Town
+ Leader: Trainer Verde
+ Theme: Grass-type
+
+
+ š³ Nature's Haven
+ Location: Whispering Woods
+ Leader: Elder Sage
+ Theme: Grass-type
+
+
+ ā” Storm Master
+ Location: Electric Canyon
+ Leader: Captain Volt
+ Theme: Electric-type
+
+
+ š Stone Crusher
+ Location: Crystal Caves
+ Leader: Miner Magnus
+ Theme: Rock-type
+
+
+ āļø Ice Breaker
+ Location: Frozen Tundra
+ Leader: Arctic Queen
+ Theme: Ice/Water-type
+
+
+ š Dragon Slayer
+ Location: Dragon's Peak
+ Leader: Champion Drake
+ Theme: Fire-type
+
+
+
+
+
+
+
+
+
+
+
+
!team
+
View your active team of pets with their levels, HP, and status.
+
Example: !team
+
+
+
!pets
+
View your complete pet collection with detailed stats and information via web interface.
+
Example: !pets
+
+
+
!activate <pet>
+
Add a pet to your active battle team. You can have multiple active pets for different situations.
+
Example: !activate flamey
+
+
+
!deactivate <pet>
+
Remove a pet from your active team and put it in storage.
+
Example: !deactivate aqua
+
+
+
+
+
+
+
+
+
+
+
!inventory / !inv / !items
+
View all items in your inventory organized by category. Shows quantities and item descriptions.
+
Example: !inventory
+
+
+
!use <item name>
+
Use a consumable item from your inventory. Items can heal pets, boost stats, or provide other benefits.
+
Example: !use Small Potion
+
+
+
+
+
šÆ Item Categories & Rarities
+
+ ā Common (15%) - Small Potions, basic healing items
+ ā Uncommon (8-12%) - Large Potions, battle boosters, special berries
+ ā Rare (3-6%) - Super Potions, speed elixirs, location treasures
+ ā
Epic (2-3%) - Evolution stones, rare crystals, ancient artifacts
+ ⦠Legendary (1%) - Lucky charms, ancient fossils, ultimate items
+
+
+
+
+ š” Item Discovery: Find items while exploring! Each location has unique treasures. Items stack in your inventory and can be used anytime.
+
+
+
+
+
+
+
+
+
!achievements
+
View your achievement progress and see which new locations you've unlocked.
+
Example: !achievements
+
+
+
+
+
šÆ Location Unlock Requirements
+
+ Pet Collector (5 pets) ā Unlocks Whispering Woods
+ Spark Collector (2 Electric species) ā Unlocks Electric Canyon
+ Rock Hound (3 Rock species) ā Unlocks Crystal Caves
+ Ice Breaker (5 Water/Ice species) ā Unlocks Frozen Tundra
+ Dragon Tamer (15 pets + 3 Fire species) ā Unlocks Dragon's Peak
+
+
+
+
+
+
+
+
+ Access detailed information through the web dashboard at
http://petz.rdx4.com/
+
+ Player Profiles - Complete stats, pet collections, and inventories
+ Leaderboard - Top players by level and achievements
+ Locations Guide - All areas with spawn information
+ Gym Badges - Display your earned badges and progress
+
+
+
+
+
+
+ """
+
+ # Add command-specific CSS to the unified styles
+ additional_css = """
+ .section {
+ background: var(--bg-secondary);
+ border-radius: 15px;
+ margin-bottom: 30px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ border: 1px solid var(--border-color);
+ overflow: hidden;
+ }
+
+ .section-header {
+ background: var(--gradient-primary);
+ color: white;
+ padding: 20px 25px;
+ font-size: 1.3em;
+ font-weight: 700;
+ }
+
+ .section-content {
+ padding: 25px;
+ }
+
+ .command-grid {
+ display: grid;
+ gap: 20px;
+ }
+
+ .command {
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ overflow: hidden;
+ transition: all 0.3s ease;
+ background: var(--bg-tertiary);
+ }
+
+ .command:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 8px 25px rgba(102, 255, 102, 0.15);
+ border-color: var(--text-accent);
+ }
+
+ .command-name {
+ background: var(--bg-primary);
+ padding: 15px 20px;
+ font-family: 'Fira Code', 'Courier New', monospace;
+ font-weight: bold;
+ color: var(--text-accent);
+ border-bottom: 1px solid var(--border-color);
+ font-size: 1.2em;
+ text-shadow: 0 0 10px rgba(102, 255, 102, 0.3);
+ }
+
+ .command-desc {
+ padding: 20px;
+ line-height: 1.7;
+ color: var(--text-primary);
+ }
+
+ .command-example {
+ background: var(--bg-primary);
+ padding: 12px 20px;
+ font-family: 'Fira Code', 'Courier New', monospace;
+ color: var(--text-secondary);
+ border-top: 1px solid var(--border-color);
+ font-size: 0.95em;
+ }
+
+ .info-box {
+ background: var(--bg-tertiary);
+ padding: 20px;
+ border-radius: 12px;
+ margin: 20px 0;
+ border: 1px solid var(--border-color);
+ }
+
+ .info-box h4 {
+ margin: 0 0 15px 0;
+ color: var(--text-accent);
+ font-size: 1.1em;
+ font-weight: 600;
+ }
+
+ .info-box ul {
+ margin: 0;
+ padding-left: 25px;
+ }
+
+ .info-box li {
+ margin: 8px 0;
+ color: var(--text-primary);
+ }
+
+ .info-box strong {
+ color: var(--text-accent);
+ }
+
+ .footer {
+ text-align: center;
+ margin-top: 50px;
+ padding: 30px;
+ background: var(--bg-secondary);
+ border-radius: 15px;
+ color: var(--text-secondary);
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ border: 1px solid var(--border-color);
+ }
+
+ .tip {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 20px;
+ border-radius: 12px;
+ margin: 20px 0;
+ font-weight: 500;
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
+ }
+
+ .gym-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 15px;
+ margin: 15px 0;
+ }
+
+ .gym-card {
+ background: var(--bg-primary);
+ padding: 15px;
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+ }
+
+ .gym-card strong {
+ color: var(--text-accent);
+ }
+ """
+
+ # Get the unified template with additional CSS
+ html_content = self.get_page_template("Command Help", content, "help")
+ # Insert additional CSS before closing tag
+ html_content = html_content.replace("", additional_css + "")
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.end_headers()
+ self.wfile.write(html_content.encode())
def serve_players(self):
"""Serve the players page with real data"""
@@ -268,6 +1153,34 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
def serve_players_data(self, players_data):
"""Serve players page with real data"""
+ # Calculate statistics
+ total_players = len(players_data)
+ total_pets = sum(p['pet_count'] for p in players_data) if players_data else 0
+ total_achievements = sum(p['achievement_count'] for p in players_data) if players_data else 0
+ highest_level = max((p['level'] for p in players_data), default=0) if players_data else 0
+
+ # Build statistics cards
+ stats_content = f"""
+
+
+
š Total Players
+
{total_players}
+
+
+
š¾ Total Pets
+
{total_pets}
+
+
+
š Achievements
+
{total_achievements}
+
+
+
ā Highest Level
+
{highest_level}
+
+
+ """
+
# Build players table HTML
if players_data:
players_html = ""
@@ -294,175 +1207,11 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
"""
- html = f"""
-
-
-
-
-
PetBot - Players
-
-
-
-
ā Back to Game Hub
-
-
-
-
-
-
-
-
-
{len(players_data)}
-
Total Players
-
-
-
{sum(p['pet_count'] for p in players_data)}
-
Total Pets Caught
-
-
-
{sum(p['achievement_count'] for p in players_data)}
-
Total Achievements
-
-
-
{max((p['level'] for p in players_data), default=0)}
-
Highest Level
-
-
-
-
-
-
-
-
-
+ # Build table content
+ table_content = f"""
+
+
š Player Rankings
+
Rank
@@ -480,14 +1229,24 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
{players_html}
-
š” Click on any player name to view their detailed profile
-
-
-"""
+ """
+
+ # Combine all content
+ content = f"""
+
+
+ {stats_content}
+ {table_content}
+ """
+
+ html = self.get_page_template("Players", content, "players")
self.send_response(200)
self.send_header('Content-type', 'text/html')
@@ -495,76 +1254,49 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
self.wfile.write(html.encode())
def serve_error_page(self, page_name, error_msg):
- """Serve a generic error page"""
- html = f"""
-
-
-
-
- PetBot - Error
-
-
-
- ā Back to Game Hub
-
-
-
-
-
Unable to load page
-
{error_msg}
-
Please try again later or contact an administrator.
-
-
-"""
+ border: 2px solid var(--error-color);
+ margin-top: 30px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ }
+
+ .error-message h2 {
+ color: var(--error-color);
+ margin-top: 0;
+ }
+ """
+
+ html_content = self.get_page_template("Error", content, "")
+ html_content = html_content.replace("", additional_css + "")
self.send_response(500)
self.send_header('Content-type', 'text/html')
self.end_headers()
- self.wfile.write(html.encode())
+ self.wfile.write(html_content.encode())
def serve_leaderboard(self):
"""Serve the leaderboard page - redirect to players for now"""
@@ -635,7 +1367,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
return []
def serve_locations_data(self, locations_data):
- """Serve locations page with real data"""
+ """Serve locations page with real data using unified template"""
# Build locations HTML
locations_html = ""
@@ -699,119 +1431,133 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
"""
- html = f"""
-
-
-
-
- PetBot - Locations
-
-
-
- ā Back to Game Hub
-
-
-
-
-
šÆ How Locations Work
-
Travel: Use !travel <location> to move between areas
-
Explore: Use !explore to find wild pets in your current location
-
Unlock: Some locations require achievements - catch specific pet types to unlock new areas!
-
Weather: Check !weather for conditions that boost certain pet spawn rates
-
-
-
- {locations_html}
-
-
-
-
- š” Use !wild <location> in #petz to see what pets spawn in a specific area
-
-
-
-
-
-"""
+ # Get the unified template with additional CSS
+ html_content = self.get_page_template("Game Locations", content, "locations")
+ # Insert additional CSS before closing tag
+ html_content = html_content.replace("", additional_css + "")
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
- self.wfile.write(html.encode())
+ self.wfile.write(html_content.encode())
def serve_petdex(self):
"""Serve the petdex page with all pet species data"""
@@ -965,9 +1648,9 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
try:
import aiosqlite
async with aiosqlite.connect(database.db_path) as db:
- # Get all pet species with evolution information
+ # Get all pet species with evolution information (no duplicates)
cursor = await db.execute("""
- SELECT ps.*,
+ SELECT DISTINCT ps.*,
evolve_to.name as evolves_to_name,
(SELECT COUNT(*) FROM location_spawns ls WHERE ls.species_id = ps.id) as location_count
FROM pet_species ps
@@ -1018,6 +1701,40 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
rarity_names = {1: "Common", 2: "Uncommon", 3: "Rare", 4: "Epic", 5: "Legendary"}
rarity_colors = {1: "#ffffff", 2: "#1eff00", 3: "#0070dd", 4: "#a335ee", 5: "#ff8000"}
+ # Calculate statistics
+ total_species = len(petdex_data)
+ type_counts = {}
+ for pet in petdex_data:
+ if pet['type1'] not in type_counts:
+ type_counts[pet['type1']] = 0
+ type_counts[pet['type1']] += 1
+ if pet['type2'] and pet['type2'] not in type_counts:
+ type_counts[pet['type2']] = 0
+ if pet['type2']:
+ type_counts[pet['type2']] += 1
+
+ # Build statistics section
+ stats_content = f"""
+
+
+
š Total Species
+
{total_species}
+
+
+
šØ Types
+
{len(type_counts)}
+
+
+
ā Rarities
+
{len(set(pet['rarity'] for pet in petdex_data))}
+
+
+
𧬠Evolutions
+
{len([p for p in petdex_data if p['evolution_level']])}
+
+
+ """
+
pets_by_rarity = {}
for pet in petdex_data:
rarity = pet['rarity']
@@ -1100,207 +1817,24 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
The petdex appears to be empty. Contact an administrator.
"""
- html = f"""
-
-
-
-
- PetBot - Petdex
-
-
-
- ā Back to Game Hub
-
-
-
-
-
-
-
{total_species}
-
Total Species
-
-
-
{len([p for p in petdex_data if p['type1'] == 'Fire' or p['type2'] == 'Fire'])}
-
Fire Types
-
-
-
{len([p for p in petdex_data if p['type1'] == 'Water' or p['type2'] == 'Water'])}
-
Water Types
-
-
-
{len([p for p in petdex_data if p['evolution_level']])}
-
Can Evolve
-
+ # Combine all content
+ content = f"""
+
-
šÆ Pets are organized by rarity. Use !wild <location> in #petz to see what spawns where!
-
-
- {petdex_html}
-
-
-"""
+ {stats_content}
+
+
+
š Pet Collection by Rarity
+
šÆ Pets are organized by rarity. Use !wild <location> in #petz to see what spawns where!
+
+ {petdex_html}
+
+ """
+
+ html = self.get_page_template("Petdex", content, "petdex")
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
@@ -1387,7 +1921,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
'hp': row[6], 'max_hp': row[7], 'attack': row[8],
'defense': row[9], 'speed': row[10], 'happiness': row[11],
'caught_at': row[12], 'is_active': bool(row[13]), # Convert to proper boolean
- 'species_name': row[14], 'type1': row[15], 'type2': row[16]
+ 'team_order': row[14], 'species_name': row[15], 'type1': row[16], 'type2': row[17]
}
pets.append(pet_dict)
@@ -1492,148 +2026,94 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
return None
def serve_player_not_found(self, nickname):
- """Serve player not found page"""
- html = f"""
-
-
-
-
-
PetBot - Player Not Found
-
-
-
-
ā Back to Game Hub
-
-
-
-
-
Player "{nickname}" not found
-
This player hasn't started their journey yet or doesn't exist.
-
Players can use !start in #petz to begin their adventure!
-
-
-"""
+ border: 2px solid var(--error-color);
+ margin-top: 30px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ }
+
+ .error-message h2 {
+ color: var(--error-color);
+ margin-top: 0;
+ }
+ """
+
+ html_content = self.get_page_template("Player Not Found", content, "players")
+ html_content = html_content.replace("", additional_css + "")
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
- self.wfile.write(html.encode())
+ self.wfile.write(html_content.encode())
def serve_player_error(self, nickname, error_msg):
- """Serve player error page"""
- html = f"""
-
-
-
-
-
PetBot - Error
-
-
-
-
ā Back to Game Hub
-
-
-
-
-
Unable to load player data
-
{error_msg}
-
Please try again later or contact an administrator.
-
-
-"""
+ border: 2px solid var(--error-color);
+ margin-top: 30px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ }
+
+ .error-message h2 {
+ color: var(--error-color);
+ margin-top: 0;
+ }
+ """
+
+ html_content = self.get_page_template("Player Error", content, "players")
+ html_content = html_content.replace("", additional_css + "")
self.send_response(500)
self.send_header('Content-type', 'text/html')
self.end_headers()
- self.wfile.write(html.encode())
+ self.wfile.write(html_content.encode())
def serve_player_data(self, nickname, player_data):
"""Serve player profile page with real data"""
@@ -1685,15 +2165,20 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
if achievements:
for achievement in achievements:
achievements_html += f"""
-
-
š {achievement['achievement_name']}
-
{achievement['achievement_desc']}
-
Earned: {achievement['completed_at']}
+
+
š
+
+
{achievement['achievement_name']}
+
{achievement['achievement_desc']}
+
Earned: {achievement['completed_at']}
+
"""
else:
achievements_html = """
-
- No achievements yet. Keep exploring and catching pets to earn achievements!
+
+
š
+
No achievements yet
+
Keep exploring and catching pets to earn achievements!
"""
# Build inventory HTML
@@ -1720,15 +2205,19 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
quantity_str = f" x{item['quantity']}" if item['quantity'] > 1 else ""
inventory_html += f"""
-
-
{symbol} {item['name']}{quantity_str}
-
{item['description']}
-
Category: {item['category'].replace('_', ' ').title()} | Rarity: {item['rarity'].title()}
+
+
+
{item['description']}
+
Category: {item['category'].replace('_', ' ').title()} | Rarity: {item['rarity'].title()}
"""
else:
inventory_html = """
-
- No items yet. Try exploring to find useful items!
+
+
š
+
No items yet
+
Try exploring to find useful items!
"""
# Build gym badges HTML
@@ -1744,15 +2233,24 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
except (AttributeError, IndexError):
badge_date = 'Unknown'
badges_html += f"""
-
-
{badge['badge_icon']} {badge['badge_name']}
-
Earned from {badge['gym_name']} ({badge['location_name']})
-
First victory: {badge_date} | Total victories: {badge['victories']} | Highest difficulty: Level {badge['highest_difficulty']}
+
+
{badge['badge_icon']}
+
+
{badge['badge_name']}
+
Earned from {badge['gym_name']} ({badge['location_name']})
+
+ First victory: {badge_date}
+ Total victories: {badge['victories']}
+ Highest difficulty: Level {badge['highest_difficulty']}
+
+
"""
else:
badges_html = """
-
- No gym badges yet. Challenge gyms to earn badges and prove your training skills!
+
+
š
+
No gym badges yet
+
Challenge gyms to earn badges and prove your training skills!
"""
# Build encounters HTML
@@ -1775,283 +2273,495 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
encounter_date = 'Unknown'
encounters_html += f"""
-
-
{encounter['species_name']} {type_str}
-
Encountered {encounter['total_encounters']} times | Caught {encounter['caught_count']} times
-
First seen: {encounter_date}
+
+
+
+ Encountered {encounter['total_encounters']} times
+ Caught {encounter['caught_count']} times
+
+
First seen: {encounter_date}
"""
else:
encounters_html = """
-
- No pets encountered yet. Use !explore to discover wild pets!
+
+
šļø
+
No pets encountered yet
+
Use !explore to discover wild pets!
"""
- html = f"""
-
-
-
-
-
PetBot - {nickname}'s Profile
-
-
-
-
ā Back to Game Hub
-
-
-
-
-
-
-
-
-
{player['level']}
-
Level
-
-
-
{player['experience']}
-
Experience
-
-
-
${player['money']}
-
Money
-
-
-
{total_pets}
-
Pets Caught
-
-
-
{active_count}
-
Active Pets
-
-
-
{len(achievements)}
-
Achievements
-
-
-
{encounter_stats.get('species_encountered', 0)}
-
Species Seen
-
-
-
{encounter_stats.get('completion_percentage', 0)}%
-
Petdex Complete
-
-
-
-
-
-
-
-
-
-
-
- Status
- Name
- Species
- Type
- Level
- HP
- Stats
-
-
-
- {pets_html}
-
-
-
-
-
-
-
-
- {achievements_html}
-
-
-
-
-
-
- {inventory_html}
-
-
-
-
-
-
-
-
-
-
Species discovered: {encounter_stats.get('species_encountered', 0)}/{encounter_stats.get('total_species', 0)}
- ({encounter_stats.get('completion_percentage', 0)}% complete)
-
Total encounters: {encounter_stats.get('total_encounters', 0)}
-
- {encounters_html}
-
-
-
-"""
+ }
+
+ .achievements-grid, .badges-grid, .encounters-grid, .inventory-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 15px;
+ }
+
+ .achievement-card, .badge-card, .encounter-card, .inventory-item {
+ background: var(--bg-tertiary);
+ padding: 15px;
+ border-radius: 8px;
+ border-left: 4px solid var(--text-accent);
+ transition: transform 0.3s ease;
+ }
+
+ .achievement-card:hover, .badge-card:hover, .encounter-card:hover, .inventory-item:hover {
+ transform: translateY(-3px);
+ }
+
+ .achievement-card {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+ }
+
+ .achievement-icon {
+ font-size: 1.5em;
+ flex-shrink: 0;
+ }
+
+ .achievement-content h4 {
+ margin: 0 0 8px 0;
+ color: var(--text-accent);
+ }
+
+ .achievement-content p {
+ margin: 0 0 8px 0;
+ color: var(--text-primary);
+ }
+
+ .achievement-date {
+ color: var(--text-secondary);
+ font-size: 0.9em;
+ }
+
+ .badge-card {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+ border-left-color: gold;
+ }
+
+ .badge-icon {
+ font-size: 1.5em;
+ flex-shrink: 0;
+ }
+
+ .badge-content h4 {
+ margin: 0 0 8px 0;
+ color: gold;
+ }
+
+ .badge-content p {
+ margin: 0 0 10px 0;
+ color: var(--text-primary);
+ }
+
+ .badge-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ .badge-stats span {
+ color: var(--text-secondary);
+ font-size: 0.9em;
+ }
+
+ .encounter-card {
+ border-left: 4px solid var(--text-accent);
+ }
+
+ .encounter-header {
+ margin-bottom: 10px;
+ }
+
+ .encounter-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 8px;
+ }
+
+ .encounter-stats span {
+ color: var(--text-primary);
+ font-size: 0.9em;
+ }
+
+ .encounter-date {
+ color: var(--text-secondary);
+ font-size: 0.9em;
+ }
+
+ .inventory-item {
+ border-left: 4px solid var(--text-accent);
+ }
+
+ .item-header {
+ margin-bottom: 8px;
+ }
+
+ .item-description {
+ color: var(--text-primary);
+ margin-bottom: 8px;
+ }
+
+ .item-meta {
+ color: var(--text-secondary);
+ font-size: 0.9em;
+ }
+
+ .empty-state {
+ text-align: center;
+ padding: 40px;
+ color: var(--text-secondary);
+ }
+
+ .empty-icon {
+ font-size: 3em;
+ margin-bottom: 15px;
+ }
+
+ .empty-state h3 {
+ margin: 0 0 10px 0;
+ color: var(--text-primary);
+ }
+
+ .empty-state p {
+ margin: 0;
+ font-size: 1.1em;
+ }
+
+ .encounters-summary {
+ text-align: center;
+ margin-bottom: 20px;
+ padding: 15px;
+ background: var(--bg-tertiary);
+ border-radius: 8px;
+ }
+
+ .encounters-summary p {
+ margin: 5px 0;
+ color: var(--text-secondary);
+ }
+
+ .btn {
+ display: inline-block;
+ padding: 12px 24px;
+ border-radius: 6px;
+ text-decoration: none;
+ font-weight: 600;
+ text-align: center;
+ transition: all 0.3s ease;
+ border: none;
+ cursor: pointer;
+ }
+
+ .btn-primary {
+ background: var(--gradient-primary);
+ color: white;
+ }
+
+ .btn-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
+ }
+
+ /* Mobile Responsive */
+ @media (max-width: 768px) {
+ .stats-grid {
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 15px;
+ }
+
+ .stat-card {
+ padding: 15px;
+ }
+
+ .stat-value {
+ font-size: 1.5em;
+ }
+
+ .achievements-grid, .badges-grid, .encounters-grid, .inventory-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .nav-pill {
+ padding: 6px 12px;
+ font-size: 0.8em;
+ margin: 3px;
+ }
+
+ .pets-table {
+ min-width: 500px;
+ }
+
+ .pets-table th, .pets-table td {
+ padding: 8px 10px;
+ font-size: 0.9em;
+ }
+ }
+ """
+
+ # Get the unified template with the additional CSS
+ html_content = self.get_page_template(f"{nickname}'s Profile", content, "players")
+ html_content = html_content.replace("", additional_css + "")
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
- self.wfile.write(html.encode())
+ self.wfile.write(html_content.encode())
def log_message(self, format, *args):
"""Override to reduce logging noise"""
@@ -2091,31 +2801,51 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
self.serve_player_error(nickname, f"Error loading team builder: {str(e)}")
def serve_teambuilder_no_pets(self, nickname):
- """Show message when player has no pets"""
- html = f"""
-
-
-
-
-
Team Builder - {nickname}
-
-
-
-
-
š¾ No Pets Found
-
{nickname}, you need to catch some pets before using the team builder!
-
ā Back to Profile
-
-
-"""
+ """Show message when player has no pets using unified template"""
+ content = f"""
+
+
+
+
š¾ No Pets Found
+
{nickname}, you need to catch some pets before using the team builder!
+
Head to the IRC channel and use !explore to find wild pets!
+
ā Back to Profile
+
+ """
+
+ # Add no-pets-specific CSS
+ additional_css = """
+ .main-container {
+ text-align: center;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+
+ .no-pets-message {
+ background: var(--bg-secondary);
+ padding: 40px;
+ border-radius: 15px;
+ border: 2px solid var(--warning-color);
+ margin-top: 30px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ }
+
+ .no-pets-message h2 {
+ color: var(--warning-color);
+ margin-top: 0;
+ }
+ """
+
+ html_content = self.get_page_template(f"Team Builder - {nickname}", content, "players")
+ html_content = html_content.replace("", additional_css + "")
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
- self.wfile.write(html.encode())
+ self.wfile.write(html_content.encode())
def serve_teambuilder_interface(self, nickname, pets):
"""Serve the full interactive team builder interface"""
@@ -2140,14 +2870,14 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
type_str += f"/{pet['type2']}"
# Debug logging
- print(f"Making pet card for {name} (ID: {pet['id']}): is_active={pet['is_active']}, passed_is_active={is_active}, status_class={status_class}")
+ print(f"Making pet card for {name} (ID: {pet['id']}): is_active={pet['is_active']}, passed_is_active={is_active}, status_class={status_class}, team_order={pet.get('team_order', 'None')}")
# Calculate HP percentage for health bar
hp_percent = (pet['hp'] / pet['max_hp']) * 100 if pet['max_hp'] > 0 else 0
hp_color = "#4CAF50" if hp_percent > 60 else "#FF9800" if hp_percent > 25 else "#f44336"
return f"""
-
+
"""
- active_cards = ''.join(make_pet_card(pet, True) for pet in active_pets)
+ # Create 6 numbered slots and place pets in their positions
+ team_slots = [''] * 6 # Initialize 6 empty slots
+
+ # Place active pets in their team_order positions
+ for pet in active_pets:
+ team_order = pet.get('team_order')
+ if team_order and 1 <= team_order <= 6:
+ team_slots[team_order - 1] = make_pet_card(pet, True)
+
storage_cards = ''.join(make_pet_card(pet, False) for pet in inactive_pets)
html = f"""
@@ -2269,6 +3007,75 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
min-height: 200px;
}}
+ .team-slots-container {{
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 15px;
+ min-height: 400px;
+ }}
+
+ .team-slot {{
+ background: var(--bg-tertiary);
+ border: 2px dashed #666;
+ border-radius: 12px;
+ padding: 10px;
+ position: relative;
+ min-height: 120px;
+ transition: all 0.3s ease;
+ display: flex;
+ flex-direction: column;
+ }}
+
+ .team-slot:hover {{
+ border-color: var(--text-accent);
+ background: var(--drag-hover);
+ }}
+
+ .team-slot.drag-over {{
+ border-color: var(--text-accent);
+ background: var(--drag-hover);
+ border-style: solid;
+ transform: scale(1.02);
+ }}
+
+ .slot-number {{
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ background: var(--active-color);
+ color: white;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.9em;
+ font-weight: bold;
+ z-index: 10;
+ }}
+
+ .slot-content {{
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ }}
+
+ .slot-content:empty::before {{
+ content: "Empty Slot";
+ color: var(--text-secondary);
+ font-style: italic;
+ opacity: 0.7;
+ }}
+
+ .slot-content .pet-card {{
+ margin: 0;
+ width: 100%;
+ max-width: none;
+ }}
+
.pet-card {{
background: var(--bg-tertiary);
border-radius: 12px;
@@ -2565,11 +3372,31 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
-
- {active_cards}
-
-
- Drop pets here to add to your active team
+
@@ -2644,14 +3471,23 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
// Add double-click handler
card.addEventListener('dblclick', function() {{
const petId = this.dataset.petId;
- const isActive = currentTeam[petId];
+ const currentPosition = currentTeam[petId];
- console.log(`Double-click: Moving pet ${{petId}} from ${{isActive ? 'active' : 'storage'}} to ${{isActive ? 'storage' : 'active'}}`);
+ console.log(`Double-click: Moving pet ${{petId}} from ${{currentPosition ? `team slot ${{currentPosition}}` : 'storage'}}`);
- if (isActive) {{
+ if (currentPosition) {{
movePetToStorage(petId);
}} else {{
- movePetToActive(petId);
+ // Find first empty slot
+ for (let i = 1; i <= 6; i++) {{
+ const slot = document.getElementById(`slot-${{i}}`);
+ const slotContent = slot.querySelector('.slot-content');
+ if (slotContent.children.length === 0) {{
+ movePetToTeamSlot(petId, i);
+ return;
+ }}
+ }}
+ console.log('No empty slots available');
}}
}});
@@ -2668,49 +3504,42 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
}}
// Declare container variables once at the top level
- const activeContainer = document.getElementById('active-container');
+ const teamSlotsContainer = document.getElementById('team-slots-container');
const storageContainer = document.getElementById('storage-container');
- const activeDrop = document.getElementById('active-drop');
const storageDrop = document.getElementById('storage-drop');
+ const teamSlots = Array.from({{length: 6}}, (_, i) => document.getElementById(`slot-${{i + 1}}`));
// Initialize team state with detailed debugging
console.log('=== TEAM STATE INITIALIZATION ===');
const allCards = document.querySelectorAll('.pet-card');
console.log(`Found ${{allCards.length}} pet cards total`);
- console.log(`Active container has ${{activeContainer.children.length}} pets initially`);
+ console.log(`Team slots container:`, teamSlotsContainer);
console.log(`Storage container has ${{storageContainer.children.length}} pets initially`);
+ let teamPositions = {{}}; // Track pet positions in team slots
+
allCards.forEach((card, index) => {{
const petId = card.dataset.petId;
const isActive = card.dataset.active === 'true';
- const currentContainer = card.parentElement.id;
+ const teamOrder = card.dataset.teamOrder;
- console.log(`Pet ${{index}}: ID=${{petId}}, data-active=${{card.dataset.active}}, isActive=${{isActive}}, currentContainer=${{currentContainer}}`);
+ console.log(`Pet ${{index}}: ID=${{petId}}, data-active=${{card.dataset.active}}, isActive=${{isActive}}, team_order=${{teamOrder}}`);
- originalTeam[petId] = isActive;
- currentTeam[petId] = isActive;
-
- // CRITICAL: Verify container placement is correct - DO NOT MOVE unless absolutely necessary
- const expectedContainer = isActive ? activeContainer : storageContainer;
- const expectedContainerId = isActive ? 'active-container' : 'storage-container';
-
- if (currentContainer !== expectedContainerId) {{
- console.error(`MISMATCH! Pet ${{petId}} is in ${{currentContainer}} but should be in ${{expectedContainerId}} based on data-active=${{card.dataset.active}}`);
- console.log(`Moving pet ${{petId}} to correct container...`);
- expectedContainer.appendChild(card);
+ if (isActive && teamOrder) {{
+ teamPositions[petId] = parseInt(teamOrder);
+ originalTeam[petId] = parseInt(teamOrder);
+ currentTeam[petId] = parseInt(teamOrder);
}} else {{
- console.log(`Pet ${{petId}} correctly placed in ${{currentContainer}}`);
+ originalTeam[petId] = false;
+ currentTeam[petId] = false;
}}
}});
- console.log('After initialization:');
- console.log(`Active container now has ${{activeContainer.children.length}} pets`);
- console.log(`Storage container now has ${{storageContainer.children.length}} pets`);
-
+ console.log('Team positions:', teamPositions);
console.log('Original team state:', originalTeam);
console.log('Current team state:', currentTeam);
- // Completely rewritten drag and drop - simpler approach
+ // Completely rewritten drag and drop for slot system
function initializeDragAndDrop() {{
console.log('Initializing drag and drop...');
@@ -2744,43 +3573,43 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
}});
}});
- // Set up drop zones (using previously declared variables)
-
- [activeContainer, activeDrop].forEach(zone => {{
- if (zone) {{
- zone.addEventListener('dragover', function(e) {{
- e.preventDefault();
- if (e.dataTransfer) {{
- e.dataTransfer.dropEffect = 'move';
- }}
- }});
-
- zone.addEventListener('dragenter', function(e) {{
- e.preventDefault();
- this.classList.add('drag-over');
- console.log('DRAGENTER: Active zone');
- }});
-
- zone.addEventListener('dragleave', function(e) {{
- if (!this.contains(e.relatedTarget)) {{
- this.classList.remove('drag-over');
- }}
- }});
-
- zone.addEventListener('drop', function(e) {{
- e.preventDefault();
- console.log('DROP: Active zone');
+ // Set up team slot drop zones
+ teamSlots.forEach((slot, index) => {{
+ const position = index + 1;
+
+ slot.addEventListener('dragover', function(e) {{
+ e.preventDefault();
+ if (e.dataTransfer) {{
+ e.dataTransfer.dropEffect = 'move';
+ }}
+ }});
+
+ slot.addEventListener('dragenter', function(e) {{
+ e.preventDefault();
+ this.classList.add('drag-over');
+ console.log(`DRAGENTER: Team slot ${{position}}`);
+ }});
+
+ slot.addEventListener('dragleave', function(e) {{
+ if (!this.contains(e.relatedTarget)) {{
this.classList.remove('drag-over');
-
- if (draggedElement) {{
- const petId = draggedElement.dataset.petId;
- console.log('Moving pet', petId, 'to active');
- movePetToActive(petId);
- }}
- }});
- }}
+ }}
+ }});
+
+ slot.addEventListener('drop', function(e) {{
+ e.preventDefault();
+ console.log(`DROP: Team slot ${{position}}`);
+ this.classList.remove('drag-over');
+
+ if (draggedElement) {{
+ const petId = draggedElement.dataset.petId;
+ console.log(`Moving pet ${{petId}} to team slot ${{position}}`);
+ movePetToTeamSlot(petId, position);
+ }}
+ }});
}});
+ // Set up storage drop zones
[storageContainer, storageDrop].forEach(zone => {{
if (zone) {{
zone.addEventListener('dragover', function(e) {{
@@ -2819,39 +3648,48 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
console.log('Drag and drop initialization complete');
}}
- function movePetToActive(petId) {{
- console.log(`movePetToActive called for pet ${{petId}}`);
+ function movePetToTeamSlot(petId, position) {{
+ console.log(`movePetToTeamSlot called for pet ${{petId}}, position ${{position}}`);
const card = document.querySelector(`[data-pet-id="${{petId}}"]`);
if (!card) {{
console.log(`No card found for pet ${{petId}}`);
return;
}}
- const currentIsActive = currentTeam[petId];
-
- console.log(`Pet ${{petId}} current state: ${{currentIsActive ? 'active' : 'storage'}}`);
-
- if (!currentIsActive) {{
- console.log(`Moving pet ${{petId}} to active...`);
-
- // Update state
- currentTeam[petId] = true;
-
- // Move DOM element
- card.classList.remove('storage');
- card.classList.add('active');
- card.dataset.active = 'true';
- card.querySelector('.status-badge').textContent = 'Active';
- activeContainer.appendChild(card);
-
- // Update interface
- updateSaveButton();
- updateDropZoneVisibility();
-
- console.log('Pet moved to active successfully');
- }} else {{
- console.log(`Pet ${{petId}} is already active, no move needed`);
+ const slot = document.getElementById(`slot-${{position}}`);
+ if (!slot) {{
+ console.log(`No slot found for position ${{position}}`);
+ return;
}}
+
+ const slotContent = slot.querySelector('.slot-content');
+
+ // Check if slot is already occupied
+ if (slotContent.children.length > 0) {{
+ console.log(`Slot ${{position}} is already occupied, swapping pets`);
+ const existingCard = slotContent.querySelector('.pet-card');
+ if (existingCard) {{
+ const existingPetId = existingCard.dataset.petId;
+ // Move existing pet to storage
+ movePetToStorage(existingPetId);
+ }}
+ }}
+
+ // Update state
+ currentTeam[petId] = position;
+
+ // Move DOM element
+ card.classList.remove('storage');
+ card.classList.add('active');
+ card.dataset.active = 'true';
+ card.dataset.teamOrder = position;
+ card.querySelector('.status-badge').textContent = 'Active';
+ slotContent.appendChild(card);
+
+ // Update interface
+ updateSaveButton();
+
+ console.log(`Pet moved to team slot ${{position}} successfully`);
}}
function movePetToStorage(petId) {{
@@ -2862,11 +3700,11 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
return;
}}
- const currentIsActive = currentTeam[petId];
+ const currentPosition = currentTeam[petId];
- console.log(`Pet ${{petId}} current state: ${{currentIsActive ? 'active' : 'storage'}}`);
+ console.log(`Pet ${{petId}} current state: ${{currentPosition ? `team slot ${{currentPosition}}` : 'storage'}}`);
- if (currentIsActive) {{
+ if (currentPosition) {{
console.log(`Moving pet ${{petId}} to storage...`);
// Update state
@@ -2876,6 +3714,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
card.classList.remove('active');
card.classList.add('storage');
card.dataset.active = 'false';
+ card.dataset.teamOrder = '';
card.querySelector('.status-badge').textContent = 'Storage';
storageContainer.appendChild(card);
@@ -2891,16 +3730,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
function updateDropZoneVisibility() {{
- // Using previously declared container variables
-
- // CRITICAL: Only update visual indicators, never move pets
- // Use CSS classes instead of direct style manipulation
- if (activeContainer && activeContainer.children.length > 0) {{
- if (activeDrop) activeDrop.classList.add('has-pets');
- }} else {{
- if (activeDrop) activeDrop.classList.remove('has-pets');
- }}
-
+ // Update storage drop zone visibility
if (storageContainer && storageContainer.children.length > 0) {{
if (storageDrop) storageDrop.classList.add('has-pets');
}} else {{
@@ -2908,7 +3738,6 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
}}
console.log('Drop zone visibility updated:', {{
- activeContainerPets: activeContainer ? activeContainer.children.length : 0,
storageContainerPets: storageContainer ? storageContainer.children.length : 0
}});
}}
@@ -3072,10 +3901,716 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):