Enhance team builder with full drag-and-drop and detailed pet stats
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9cf2231a03
commit
7d49730a5f
1 changed files with 682 additions and 21 deletions
703
webserver.py
703
webserver.py
|
|
@ -2032,10 +2032,67 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
self.wfile.write(html.encode())
|
||||
|
||||
def serve_teambuilder_interface(self, nickname, pets):
|
||||
"""Serve the team builder interface - basic version for now"""
|
||||
"""Serve the full interactive team builder interface"""
|
||||
active_pets = [pet for pet in pets if pet['is_active']]
|
||||
inactive_pets = [pet for pet in pets if not pet['is_active']]
|
||||
|
||||
# Generate detailed pet cards
|
||||
def make_pet_card(pet, is_active):
|
||||
name = pet['nickname'] or pet['species_name']
|
||||
status = "Active" if is_active else "Storage"
|
||||
status_class = "active" if is_active else "storage"
|
||||
type_str = pet['type1']
|
||||
if pet['type2']:
|
||||
type_str += f"/{pet['type2']}"
|
||||
|
||||
# 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"""
|
||||
<div class="pet-card {status_class}" draggable="true" data-pet-id="{pet['id']}" data-active="{str(is_active).lower()}">
|
||||
<div class="pet-header">
|
||||
<h4 class="pet-name">{name}</h4>
|
||||
<div class="status-badge">{status}</div>
|
||||
</div>
|
||||
<div class="pet-species">Level {pet['level']} {pet['species_name']}</div>
|
||||
<div class="pet-type">{type_str}</div>
|
||||
|
||||
<div class="hp-section">
|
||||
<div class="hp-label">HP: {pet['hp']}/{pet['max_hp']}</div>
|
||||
<div class="hp-bar">
|
||||
<div class="hp-fill" style="width: {hp_percent}%; background: {hp_color};"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat">
|
||||
<span class="stat-label">ATK</span>
|
||||
<span class="stat-value">{pet['attack']}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">DEF</span>
|
||||
<span class="stat-value">{pet['defense']}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">SPD</span>
|
||||
<span class="stat-value">{pet['speed']}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">EXP</span>
|
||||
<span class="stat-value">{pet['experience']}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pet-happiness">
|
||||
<span class="happiness-emoji">{'😊' if pet['happiness'] > 70 else '😐' if pet['happiness'] > 40 else '😞'}</span>
|
||||
<span>Happiness: {pet['happiness']}/100</span>
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
active_cards = ''.join(make_pet_card(pet, True) for pet in active_pets)
|
||||
storage_cards = ''.join(make_pet_card(pet, False) for pet in inactive_pets)
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
@ -2043,44 +2100,648 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Team Builder - {nickname}</title>
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; background: #0f0f23; color: #cccccc; padding: 20px; }}
|
||||
.header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 10px; text-align: center; margin-bottom: 20px; }}
|
||||
.teams {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }}
|
||||
.team-section {{ background: #1e1e3f; padding: 20px; border-radius: 10px; }}
|
||||
.pet-list {{ margin-top: 15px; }}
|
||||
.pet-item {{ background: #2a2a4a; padding: 10px; margin: 8px 0; border-radius: 5px; }}
|
||||
.controls {{ text-align: center; margin-top: 20px; }}
|
||||
.btn {{ padding: 12px 24px; margin: 0 10px; border: none; border-radius: 20px; cursor: pointer; text-decoration: none; display: inline-block; }}
|
||||
.primary {{ background: #4CAF50; color: white; }}
|
||||
.secondary {{ background: #666; color: white; }}
|
||||
:root {{
|
||||
--bg-primary: #0f0f23;
|
||||
--bg-secondary: #1e1e3f;
|
||||
--bg-tertiary: #2a2a4a;
|
||||
--text-primary: #cccccc;
|
||||
--text-secondary: #999999;
|
||||
--text-accent: #66ff66;
|
||||
--active-color: #4CAF50;
|
||||
--storage-color: #FF9800;
|
||||
--drag-hover: #3a3a5a;
|
||||
--shadow: 0 4px 15px rgba(0,0,0,0.3);
|
||||
}}
|
||||
|
||||
body {{
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}}
|
||||
|
||||
.header {{
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: var(--shadow);
|
||||
}}
|
||||
|
||||
.header h1 {{ margin: 0; font-size: 2.5em; }}
|
||||
.header p {{ margin: 10px 0 0 0; opacity: 0.9; }}
|
||||
|
||||
.team-sections {{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
|
||||
@media (max-width: 768px) {{
|
||||
.team-sections {{ grid-template-columns: 1fr; }}
|
||||
}}
|
||||
|
||||
.team-section {{
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
min-height: 500px;
|
||||
box-shadow: var(--shadow);
|
||||
}}
|
||||
|
||||
.section-header {{
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
.active-header {{ background: var(--active-color); color: white; }}
|
||||
.storage-header {{ background: var(--storage-color); color: white; }}
|
||||
|
||||
.pets-container {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 15px;
|
||||
min-height: 200px;
|
||||
}}
|
||||
|
||||
.pet-card {{
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
cursor: move;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||
}}
|
||||
|
||||
.pet-card:hover {{
|
||||
transform: translateY(-3px);
|
||||
box-shadow: var(--shadow);
|
||||
}}
|
||||
|
||||
.pet-card.active {{ border-color: var(--active-color); }}
|
||||
.pet-card.storage {{ border-color: var(--storage-color); }}
|
||||
|
||||
.pet-card.dragging {{
|
||||
opacity: 0.6;
|
||||
transform: rotate(3deg) scale(0.95);
|
||||
z-index: 1000;
|
||||
}}
|
||||
|
||||
.pet-header {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}}
|
||||
|
||||
.pet-name {{
|
||||
margin: 0;
|
||||
font-size: 1.2em;
|
||||
color: var(--text-accent);
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.status-badge {{
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.active .status-badge {{ background: var(--active-color); color: white; }}
|
||||
.storage .status-badge {{ background: var(--storage-color); color: white; }}
|
||||
|
||||
.pet-species {{
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
}}
|
||||
|
||||
.pet-type {{
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.85em;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
}}
|
||||
|
||||
.hp-section {{
|
||||
margin: 10px 0;
|
||||
}}
|
||||
|
||||
.hp-label {{
|
||||
font-size: 0.9em;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 5px;
|
||||
}}
|
||||
|
||||
.hp-bar {{
|
||||
background: #333;
|
||||
border-radius: 10px;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
}}
|
||||
|
||||
.hp-fill {{
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
transition: width 0.3s ease;
|
||||
}}
|
||||
|
||||
.stats-grid {{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin: 12px 0;
|
||||
}}
|
||||
|
||||
.stat {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: rgba(255,255,255,0.05);
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
}}
|
||||
|
||||
.stat-label {{
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85em;
|
||||
}}
|
||||
|
||||
.stat-value {{
|
||||
color: var(--text-accent);
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.pet-happiness {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-secondary);
|
||||
}}
|
||||
|
||||
.happiness-emoji {{
|
||||
font-size: 1.2em;
|
||||
}}
|
||||
|
||||
.drop-zone {{
|
||||
min-height: 80px;
|
||||
border: 2px dashed #666;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 15px;
|
||||
transition: all 0.3s ease;
|
||||
font-style: italic;
|
||||
}}
|
||||
|
||||
.drop-zone.drag-over {{
|
||||
border-color: var(--text-accent);
|
||||
background: var(--drag-hover);
|
||||
color: var(--text-accent);
|
||||
border-style: solid;
|
||||
}}
|
||||
|
||||
.drop-zone.has-pets {{
|
||||
display: none;
|
||||
}}
|
||||
|
||||
.controls {{
|
||||
background: var(--bg-secondary);
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow);
|
||||
}}
|
||||
|
||||
.save-btn {{
|
||||
background: linear-gradient(135deg, #4CAF50, #45a049);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
font-size: 1.1em;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.save-btn:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
|
||||
}}
|
||||
|
||||
.save-btn:disabled {{
|
||||
background: #666;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}}
|
||||
|
||||
.back-btn {{
|
||||
background: linear-gradient(135deg, #666, #555);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 25px;
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.back-btn:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
||||
}}
|
||||
|
||||
.pin-section {{
|
||||
background: var(--bg-secondary);
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
box-shadow: var(--shadow);
|
||||
}}
|
||||
|
||||
.pin-input {{
|
||||
background: var(--bg-tertiary);
|
||||
border: 2px solid #666;
|
||||
color: var(--text-primary);
|
||||
padding: 15px;
|
||||
font-size: 1.3em;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
width: 200px;
|
||||
margin: 0 10px;
|
||||
letter-spacing: 2px;
|
||||
}}
|
||||
|
||||
.pin-input:focus {{
|
||||
border-color: var(--text-accent);
|
||||
outline: none;
|
||||
box-shadow: 0 0 10px rgba(102, 255, 102, 0.3);
|
||||
}}
|
||||
|
||||
.verify-btn {{
|
||||
background: linear-gradient(135deg, #2196F3, #1976D2);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
font-size: 1.1em;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.verify-btn:hover {{
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(33, 150, 243, 0.4);
|
||||
}}
|
||||
|
||||
.message {{
|
||||
background: var(--bg-tertiary);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 15px 0;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
.success {{ border-left: 4px solid #4CAF50; }}
|
||||
.error {{ border-left: 4px solid #f44336; }}
|
||||
.info {{ border-left: 4px solid #2196F3; }}
|
||||
|
||||
.empty-state {{
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🐾 Team Builder</h1>
|
||||
<p><strong>{nickname}</strong> | Active: {len(active_pets)} | Storage: {len(inactive_pets)}</p>
|
||||
<p>Drag pets between Active and Storage to build your perfect team</p>
|
||||
<p><strong>{nickname}</strong> | Active: {len(active_pets)} pets | Storage: {len(inactive_pets)} pets</p>
|
||||
</div>
|
||||
|
||||
<div class="teams">
|
||||
<div class="team-sections">
|
||||
<div class="team-section">
|
||||
<h3>⭐ Active Team</h3>
|
||||
<div class="pet-list">
|
||||
{''.join(f'<div class="pet-item">{pet["nickname"] or pet["species_name"]} (Lv.{pet["level"]})</div>' for pet in active_pets) or '<div class="pet-item">No active pets</div>'}
|
||||
<div class="section-header active-header">⭐ Active Team</div>
|
||||
<div class="pets-container" id="active-container">
|
||||
{active_cards}
|
||||
</div>
|
||||
<div class="drop-zone {('has-pets' if active_pets else '')}" id="active-drop">
|
||||
Drop pets here to add to your active team
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="team-section">
|
||||
<h3>📦 Storage</h3>
|
||||
<div class="pet-list">
|
||||
{''.join(f'<div class="pet-item">{pet["nickname"] or pet["species_name"]} (Lv.{pet["level"]})</div>' for pet in inactive_pets) or '<div class="pet-item">No stored pets</div>'}
|
||||
<div class="section-header storage-header">📦 Storage</div>
|
||||
<div class="pets-container" id="storage-container">
|
||||
{storage_cards}
|
||||
</div>
|
||||
<div class="drop-zone {('has-pets' if inactive_pets else '')}" id="storage-drop">
|
||||
Drop pets here to store them
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<p><em>Full drag-and-drop interface coming soon!</em></p>
|
||||
<a href="/player/{nickname}" class="btn secondary">← Back to Profile</a>
|
||||
<button class="save-btn" id="save-btn" onclick="saveTeam()">🔒 Save Team Changes</button>
|
||||
<a href="/player/{nickname}" class="back-btn">← Back to Profile</a>
|
||||
<div style="margin-top: 15px; color: var(--text-secondary); font-size: 0.9em;">
|
||||
Changes are saved securely with PIN verification via IRC
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pin-section" id="pin-section">
|
||||
<h3>🔐 PIN Verification Required</h3>
|
||||
<p>A 6-digit PIN has been sent to you via IRC private message.</p>
|
||||
<p>Enter the PIN below to confirm your team changes:</p>
|
||||
<input type="text" class="pin-input" id="pin-input" placeholder="000000" maxlength="6">
|
||||
<button class="verify-btn" onclick="verifyPin()">✅ Verify & Apply Changes</button>
|
||||
<div id="message-area"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let originalTeam = {{}};
|
||||
let currentTeam = {{}};
|
||||
let draggedElement = null;
|
||||
|
||||
// Initialize team state
|
||||
document.querySelectorAll('.pet-card').forEach(card => {{
|
||||
const petId = card.dataset.petId;
|
||||
const isActive = card.dataset.active === 'true';
|
||||
originalTeam[petId] = isActive;
|
||||
currentTeam[petId] = isActive;
|
||||
}});
|
||||
|
||||
// Enhanced drag and drop functionality
|
||||
document.querySelectorAll('.pet-card').forEach(card => {{
|
||||
card.addEventListener('dragstart', handleDragStart);
|
||||
card.addEventListener('dragend', handleDragEnd);
|
||||
}});
|
||||
|
||||
document.querySelectorAll('.drop-zone, .pets-container').forEach(zone => {{
|
||||
zone.addEventListener('dragover', handleDragOver);
|
||||
zone.addEventListener('drop', handleDrop);
|
||||
zone.addEventListener('dragenter', handleDragEnter);
|
||||
zone.addEventListener('dragleave', handleDragLeave);
|
||||
}});
|
||||
|
||||
function handleDragStart(e) {{
|
||||
draggedElement = this;
|
||||
this.classList.add('dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/html', this.outerHTML);
|
||||
|
||||
// Add visual feedback
|
||||
setTimeout(() => {{
|
||||
this.style.opacity = '0.6';
|
||||
}}, 0);
|
||||
}}
|
||||
|
||||
function handleDragEnd(e) {{
|
||||
this.classList.remove('dragging');
|
||||
this.style.opacity = '';
|
||||
|
||||
// Clear all drag-over states
|
||||
document.querySelectorAll('.drop-zone, .pets-container').forEach(zone => {{
|
||||
zone.classList.remove('drag-over');
|
||||
}});
|
||||
}}
|
||||
|
||||
function handleDragOver(e) {{
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
}}
|
||||
|
||||
function handleDragEnter(e) {{
|
||||
e.preventDefault();
|
||||
if (e.target.classList.contains('drop-zone') || e.target.classList.contains('pets-container')) {{
|
||||
e.target.classList.add('drag-over');
|
||||
}}
|
||||
}}
|
||||
|
||||
function handleDragLeave(e) {{
|
||||
if (e.target.classList.contains('drop-zone') || e.target.classList.contains('pets-container')) {{
|
||||
// Only remove if we're actually leaving the element
|
||||
if (!e.target.contains(e.relatedTarget)) {{
|
||||
e.target.classList.remove('drag-over');
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
function handleDrop(e) {{
|
||||
e.preventDefault();
|
||||
if (!draggedElement) return;
|
||||
|
||||
const petId = draggedElement.dataset.petId;
|
||||
let newActiveStatus;
|
||||
let targetContainer;
|
||||
|
||||
// Determine target based on drop zone or container
|
||||
if (e.target.id === 'active-drop' || e.target.closest('#active-container')) {{
|
||||
newActiveStatus = true;
|
||||
targetContainer = document.getElementById('active-container');
|
||||
moveToActive(draggedElement);
|
||||
}} else if (e.target.id === 'storage-drop' || e.target.closest('#storage-container')) {{
|
||||
newActiveStatus = false;
|
||||
targetContainer = document.getElementById('storage-container');
|
||||
moveToStorage(draggedElement);
|
||||
}}
|
||||
|
||||
if (newActiveStatus !== undefined && newActiveStatus !== currentTeam[petId]) {{
|
||||
currentTeam[petId] = newActiveStatus;
|
||||
updateSaveButton();
|
||||
updateDropZoneVisibility();
|
||||
|
||||
// Visual feedback
|
||||
draggedElement.style.transform = 'scale(1.05)';
|
||||
setTimeout(() => {{
|
||||
draggedElement.style.transform = '';
|
||||
}}, 200);
|
||||
}}
|
||||
|
||||
// Clear all drag states
|
||||
document.querySelectorAll('.drop-zone, .pets-container').forEach(zone => {{
|
||||
zone.classList.remove('drag-over');
|
||||
}});
|
||||
}}
|
||||
|
||||
function moveToActive(card) {{
|
||||
const container = document.getElementById('active-container');
|
||||
card.classList.remove('storage');
|
||||
card.classList.add('active');
|
||||
card.dataset.active = 'true';
|
||||
card.querySelector('.status-badge').textContent = 'Active';
|
||||
container.appendChild(card);
|
||||
}}
|
||||
|
||||
function moveToStorage(card) {{
|
||||
const container = document.getElementById('storage-container');
|
||||
card.classList.remove('active');
|
||||
card.classList.add('storage');
|
||||
card.dataset.active = 'false';
|
||||
card.querySelector('.status-badge').textContent = 'Storage';
|
||||
container.appendChild(card);
|
||||
}}
|
||||
|
||||
function updateDropZoneVisibility() {{
|
||||
const activeContainer = document.getElementById('active-container');
|
||||
const storageContainer = document.getElementById('storage-container');
|
||||
const activeDrop = document.getElementById('active-drop');
|
||||
const storageDrop = document.getElementById('storage-drop');
|
||||
|
||||
activeDrop.style.display = activeContainer.children.length > 0 ? 'none' : 'flex';
|
||||
storageDrop.style.display = storageContainer.children.length > 0 ? 'none' : 'flex';
|
||||
}}
|
||||
|
||||
function updateSaveButton() {{
|
||||
const hasChanges = JSON.stringify(originalTeam) !== JSON.stringify(currentTeam);
|
||||
const saveBtn = document.getElementById('save-btn');
|
||||
saveBtn.disabled = !hasChanges;
|
||||
|
||||
if (hasChanges) {{
|
||||
saveBtn.textContent = '🔒 Save Team Changes';
|
||||
}} else {{
|
||||
saveBtn.textContent = '✅ No Changes';
|
||||
}}
|
||||
}}
|
||||
|
||||
async function saveTeam() {{
|
||||
const changes = {{}};
|
||||
for (const petId in currentTeam) {{
|
||||
if (currentTeam[petId] !== originalTeam[petId]) {{
|
||||
changes[petId] = currentTeam[petId];
|
||||
}}
|
||||
}}
|
||||
|
||||
if (Object.keys(changes).length === 0) {{
|
||||
showMessage('No changes to save!', 'info');
|
||||
return;
|
||||
}}
|
||||
|
||||
try {{
|
||||
const response = await fetch('/teambuilder/{nickname}/save', {{
|
||||
method: 'POST',
|
||||
headers: {{ 'Content-Type': 'application/json' }},
|
||||
body: JSON.stringify(changes)
|
||||
}});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {{
|
||||
showMessage('PIN sent to IRC! Check your private messages.', 'success');
|
||||
document.getElementById('pin-section').style.display = 'block';
|
||||
document.getElementById('pin-section').scrollIntoView({{ behavior: 'smooth' }});
|
||||
}} else {{
|
||||
showMessage('Error: ' + result.error, 'error');
|
||||
}}
|
||||
}} catch (error) {{
|
||||
showMessage('Network error: ' + error.message, 'error');
|
||||
}}
|
||||
}}
|
||||
|
||||
async function verifyPin() {{
|
||||
const pin = document.getElementById('pin-input').value.trim();
|
||||
if (pin.length !== 6 || !/^\\d{{6}}$/.test(pin)) {{
|
||||
showMessage('Please enter a valid 6-digit PIN', 'error');
|
||||
return;
|
||||
}}
|
||||
|
||||
try {{
|
||||
const response = await fetch('/teambuilder/{nickname}/verify', {{
|
||||
method: 'POST',
|
||||
headers: {{ 'Content-Type': 'application/json' }},
|
||||
body: JSON.stringify({{ pin: pin }})
|
||||
}});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {{
|
||||
showMessage('Team changes applied successfully! 🎉', 'success');
|
||||
// Update original team state
|
||||
originalTeam = {{...currentTeam}};
|
||||
updateSaveButton();
|
||||
document.getElementById('pin-section').style.display = 'none';
|
||||
document.getElementById('pin-input').value = '';
|
||||
|
||||
// Visual celebration
|
||||
document.querySelectorAll('.pet-card').forEach(card => {{
|
||||
card.style.animation = 'bounce 0.6s ease-in-out';
|
||||
}});
|
||||
setTimeout(() => {{
|
||||
document.querySelectorAll('.pet-card').forEach(card => {{
|
||||
card.style.animation = '';
|
||||
}});
|
||||
}}, 600);
|
||||
}} else {{
|
||||
showMessage('Verification failed: ' + result.error, 'error');
|
||||
}}
|
||||
}} catch (error) {{
|
||||
showMessage('Network error: ' + error.message, 'error');
|
||||
}}
|
||||
}}
|
||||
|
||||
function showMessage(text, type) {{
|
||||
const messageArea = document.getElementById('message-area');
|
||||
messageArea.innerHTML = `<div class="message ${{type}}">${{text}}</div>`;
|
||||
|
||||
// Auto-hide success messages
|
||||
if (type === 'success') {{
|
||||
setTimeout(() => {{
|
||||
messageArea.innerHTML = '';
|
||||
}}, 5000);
|
||||
}}
|
||||
}}
|
||||
|
||||
// Add keyboard support for PIN input
|
||||
document.getElementById('pin-input').addEventListener('keypress', function(e) {{
|
||||
if (e.key === 'Enter') {{
|
||||
verifyPin();
|
||||
}}
|
||||
}});
|
||||
|
||||
// Initialize interface
|
||||
updateSaveButton();
|
||||
updateDropZoneVisibility();
|
||||
|
||||
// Add bounce animation
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes bounce {{
|
||||
0%, 20%, 60%, 100% {{ transform: translateY(0); }}
|
||||
40% {{ transform: translateY(-10px); }}
|
||||
80% {{ transform: translateY(-5px); }}
|
||||
}}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
console.log('🐾 Team Builder initialized successfully!');
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue