kdsTeam / index.html
bakhtrv's picture
Update index.html
d7554a9 verified
Raw
History Blame Contribute Delete
37.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Team Manager</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Inter:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #0f172a;
color: white;
height: 100vh;
overflow: hidden;
touch-action: manipulation;
}
h1, h2, h3, .orbitron { font-family: 'Orbitron', sans-serif; }
/* Custom Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #1e293b; }
::-webkit-scrollbar-thumb { background: #475569; border-radius: 3px; }
.glass-panel {
background: rgba(30, 41, 59, 0.7);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Drag & Drop Styles */
.roster-slot {
min-width: 120px;
max-width: 150px;
flex-shrink: 0;
aspect-ratio: 3/4;
transition: all 0.2s;
}
.roster-slot.drag-over {
transform: scale(1.05);
box-shadow: 0 0 20px rgba(59, 130, 246, 0.6);
border-color: #60a5fa;
border-style: solid;
}
/* Member Card Styles */
.member-card {
aspect-ratio: 3/4;
cursor: grab;
transition: transform 0.2s, box-shadow 0.2s;
touch-action: none;
user-select: none;
}
.member-card:active { cursor: grabbing; transform: scale(0.98); }
.member-card:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
border-color: #3b82f6;
}
/* Spinner */
.spinner {
border: 3px solid rgba(255,255,255,0.3);
border-radius: 50%;
border-top: 3px solid #3b82f6;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Mobile optimizations */
@media (max-width: 768px) {
.roster-slot { min-width: 100px; }
#playersContainer { grid-template-columns: repeat(2, 1fr); }
}
/* Share modal */
.share-modal {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.share-modal.active {
display: flex;
}
.share-content {
background: #1e293b;
border: 1px solid #475569;
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 500px;
}
</style>
</head>
<body class="flex">
<!-- LEFT SIDEBAR (only shown when not in shared mode) -->
<div id="sidebar" class="w-1/3 h-full flex flex-col border-r border-slate-700 bg-slate-900">
<div class="p-6 border-b border-slate-700 bg-slate-900/50">
<h1 class="text-xl font-bold tracking-widest text-cyan-400 orbitron">TEAMS DASHBOARD</h1>
<button onclick="createNewTeam()" class="mt-4 w-full py-3 bg-blue-600 hover:bg-blue-500 text-white font-bold rounded uppercase tracking-wider transition-colors flex items-center justify-center gap-2">
<i class="fas fa-plus"></i>
Create New Team
</button>
</div>
<div id="teamsList" class="flex-1 overflow-y-auto p-2 space-y-1">
<div class="text-center text-gray-500 mt-10">Loading teams...</div>
</div>
</div>
<!-- RIGHT PANEL -->
<div id="mainPanel" class="w-2/3 h-full flex flex-col bg-slate-800/50 relative">
<div id="emptyState" class="absolute inset-0 flex items-center justify-center text-slate-500 pointer-events-none">
<div class="text-center">
<i class="fas fa-users text-6xl mb-4 opacity-50"></i>
<p class="text-lg orbitron">Select a team to edit</p>
</div>
</div>
<div id="editorPanel" class="hidden flex-col h-full">
<div class="p-4 md:p-6 border-b border-slate-700 bg-slate-900/80 flex justify-between items-center">
<h2 id="editorTitle" class="text-lg md:text-2xl font-bold text-white orbitron">Edit Team</h2>
<div class="flex gap-2 md:gap-3">
<button onclick="openShareModal()" id="shareBtn" class="px-4 py-2 bg-purple-600 hover:bg-purple-500 text-white font-bold rounded text-sm tracking-widest flex items-center gap-2">
<i class="fas fa-share-alt"></i>
<span class="hidden md:inline">SHARE</span>
</button>
<button onclick="saveTeam()" id="saveBtn" class="px-4 md:px-6 py-2 bg-green-600 hover:bg-green-500 text-white font-bold rounded shadow-[0_0_15px_rgba(22,163,74,0.4)] text-sm tracking-widest">
SAVE
</button>
</div>
</div>
<div class="flex-1 overflow-y-auto p-4 md:p-8 space-y-6 md:space-y-8">
<!-- Team Info & Active Roster -->
<div class="glass-panel p-4 md:p-6 rounded-xl">
<h3 class="text-sm font-bold text-cyan-400 uppercase tracking-widest mb-4">Team Info & Active Roster</h3>
<!-- Basic Info -->
<div class="flex flex-col md:flex-row gap-4 md:gap-6 mb-6 md:mb-8">
<div class="w-20 h-20 md:w-24 md:h-24 shrink-0 relative group self-center md:self-start">
<!-- Team Logo Input -->
<input type="file" id="teamLogoInput" class="hidden" accept="image/*" onchange="handleInstantUpload(this, 'teamLogoPreview', 'teamLogoLoader', 'teamLogoPlaceholder')">
<div class="w-full h-full rounded-lg cursor-pointer flex items-center justify-center relative overflow-hidden border border-slate-600 bg-slate-800" onclick="document.getElementById('teamLogoInput').click()">
<img id="teamLogoPreview" src="" class="w-full h-full object-cover hidden">
<div id="teamLogoPlaceholder" class="text-center text-xs text-gray-400">
<i class="fas fa-image text-lg mb-1 block"></i>
Logo
</div>
<!-- Loader -->
<div id="teamLogoLoader" class="hidden absolute inset-0 bg-black/70 flex items-center justify-center z-10">
<div class="spinner"></div>
</div>
</div>
</div>
<div class="flex-1">
<label class="block text-xs text-gray-400 mb-1 font-bold tracking-wider">TEAM NAME</label>
<input type="text" id="teamNameInput" class="w-full bg-slate-900 border border-slate-700 rounded p-3 text-white focus:border-cyan-500 outline-none font-bold text-lg" placeholder="e.g. Los Angeles Lakers">
</div>
</div>
<!-- ROSTER GRID (Drag Zone) -->
<div>
<div class="flex flex-col md:flex-row md:justify-between md:items-end mb-2">
<label class="block text-xs text-gray-400 uppercase tracking-widest mb-1 md:mb-0">Active Roster (Drag from Members below)</label>
<span class="text-xs text-gray-500">Limits: 1 Coach, 2 Strikers, 1 Others</span>
</div>
<!-- Horizontal scroll container -->
<div class="flex gap-3 overflow-x-auto pb-4" id="rosterScrollContainer">
<!-- Slots -->
<div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"
ondrop="drop(event)" ondragover="allowDrop(event)"
ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)"
id="slot-0"></div>
<div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"
ondrop="drop(event)" ondragover="allowDrop(event)"
ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)"
id="slot-1"></div>
<div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"
ondrop="drop(event)" ondragover="allowDrop(event)"
ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)"
id="slot-2"></div>
<div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"
ondrop="drop(event)" ondragover="allowDrop(event)"
ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)"
id="slot-3"></div>
<div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"
ondrop="drop(event)" ondragover="allowDrop(event)"
ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)"
id="slot-4"></div>
<div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"
ondrop="drop(event)" ondragover="allowDrop(event)"
ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)"
id="slot-5"></div>
</div>
</div>
</div>
<!-- MEMBERS -->
<div class="glass-panel p-4 md:p-6 rounded-xl">
<div class="flex justify-between items-center mb-4">
<h3 class="text-sm font-bold text-cyan-400 uppercase tracking-widest">MEMBERS</h3>
<button onclick="addPlayerRow()" class="text-xs bg-slate-700 hover:bg-slate-600 px-3 py-1 rounded text-white flex items-center gap-1">
<i class="fas fa-plus"></i>
Add Member
</button>
</div>
<!-- Grid container for cards -->
<div id="playersContainer" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-3 md:gap-4"></div>
</div>
</div>
</div>
</div>
<!-- SHARE MODAL -->
<div id="shareModal" class="share-modal">
<div class="share-content">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold orbitron">Share Team</h3>
<button onclick="closeShareModal()" class="text-gray-400 hover:text-white">
<i class="fas fa-times text-2xl"></i>
</button>
</div>
<p class="text-gray-300 mb-4">Share this link to allow others to edit this team:</p>
<div class="flex gap-2 mb-4">
<input type="text" id="shareUrl" readonly class="flex-1 bg-slate-900 border border-slate-700 rounded p-3 text-white text-sm" value="Loading...">
<button onclick="copyShareUrl()" id="copyBtn" class="bg-blue-600 hover:bg-blue-500 text-white px-4 rounded font-bold">
<i class="fas fa-copy"></i>
</button>
</div>
<div class="text-sm text-gray-400">
<p><i class="fas fa-info-circle mr-2"></i>Anyone with this link can edit the team</p>
</div>
</div>
</div>
<!-- NOTIFICATION -->
<div id="notification" class="fixed bottom-6 right-6 px-6 py-4 bg-slate-900 border border-slate-700 rounded-lg shadow-2xl transform translate-y-20 transition-transform duration-300 flex items-center gap-3 z-50">
<span id="notifMsg" class="font-medium text-sm"></span>
</div>
<!-- Firebase SDK & Logic -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
import { getDatabase, ref, set, push, onValue, remove, update } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-database.js";
const firebaseConfig = {
apiKey: "AIzaSyDi6znbXgOFR7aXZ9v7aVFP065yE98T7XU",
authDomain: "fida-kz.firebaseapp.com",
databaseURL: "https://fida-kz-default-rtdb.firebaseio.com",
projectId: "fida-kz",
appId: "1:728249202318:web:29ee414ef7eff47573fbdf"
};
const CLOUD_NAME = "dkagzakrx";
const API_KEY = "369873383773423";
const API_SECRET = "OWUfaIwcl0nJYfqZxd7d1lBQkJA";
const CLOUDINARY_URL = `https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload`;
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
// Global variables
window.currentTeamId = null;
window.teamsCache = {};
window.isSharedMode = false;
// Touch drag & drop variables
let touchDragData = null;
let touchStartX = 0;
let touchStartY = 0;
let touchStartTime = 0;
// Check if we're in shared mode (has teamId in URL)
const urlParams = new URLSearchParams(window.location.search);
const sharedTeamId = urlParams.get('teamId');
if (sharedTeamId) {
// Shared mode - hide sidebar, show only editor
window.isSharedMode = true;
document.getElementById('sidebar').style.display = 'none';
document.getElementById('mainPanel').style.width = '100%';
loadSharedTeam(sharedTeamId);
}
// --- CLOUDINARY LOGIC ---
async function sha1(str) {
const enc = new TextEncoder();
const hash = await crypto.subtle.digest('SHA-1', enc.encode(str));
return Array.from(new Uint8Array(hash)).map(v => v.toString(16).padStart(2, '0')).join('');
}
async function uploadToCloudinary(file) {
const timestamp = Math.floor(Date.now() / 1000);
const signature = await sha1(`timestamp=${timestamp}${API_SECRET}`);
const formData = new FormData();
formData.append("file", file);
formData.append("api_key", API_KEY);
formData.append("timestamp", timestamp);
formData.append("signature", signature);
const res = await fetch(CLOUDINARY_URL, { method: "POST", body: formData });
if (!res.ok) throw new Error("Upload failed");
const data = await res.json();
return data.secure_url;
}
// --- IMMEDIATE UPLOAD HANDLER ---
window.handleInstantUpload = async function(input, imgId, loaderId, placeholderId) {
if (!input.files || !input.files[0]) return;
const loader = document.getElementById(loaderId);
const img = document.getElementById(imgId);
const placeholder = document.getElementById(placeholderId);
if (loader) loader.classList.remove('hidden');
try {
const url = await uploadToCloudinary(input.files[0]);
img.src = url;
img.setAttribute('data-url', url);
img.classList.remove('hidden');
if (placeholder) placeholder.classList.add('hidden');
} catch (e) {
console.error(e);
showNotif("Upload failed: " + e.message, "error");
input.value = "";
} finally {
if (loader) loader.classList.add('hidden');
}
}
// --- TOUCH DRAG & DROP (Mobile Support) ---
window.touchStart = function(event) {
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
touchStartTime = Date.now();
event.preventDefault();
}
window.touchMove = function(event) {
event.preventDefault();
}
window.touchEnd = function(event) {
const touchEndX = event.changedTouches[0].clientX;
const touchEndY = event.changedTouches[0].clientY;
const touchEndTime = Date.now();
// Check if this was a tap (not swipe)
const distX = Math.abs(touchEndX - touchStartX);
const distY = Math.abs(touchEndY - touchStartY);
const timeDiff = touchEndTime - touchStartTime;
// If it's a tap (small movement, short time) and we have drag data, drop it
if (distX < 10 && distY < 10 && timeDiff < 500 && touchDragData) {
const slot = event.target.closest('.roster-slot');
if (slot) {
handleDrop(slot, touchDragData);
touchDragData = null;
}
}
event.preventDefault();
}
// --- DRAG & DROP LOGIC (Desktop & Mobile) ---
const ROLE_LIMITS = { 'coach': 1, 'striker': 2, 'keeper': 1, 'sweeper': 1, 'libero': 1, 'guide': 1 };
window.allowDrop = function(ev) {
ev.preventDefault();
const slot = ev.target.closest('.roster-slot');
if (slot) slot.classList.add('drag-over');
}
document.addEventListener('dragleave', (ev) => {
if (ev.target.classList.contains('roster-slot')) {
ev.target.classList.remove('drag-over');
}
});
window.drag = function(ev) {
const card = ev.target.closest('.member-card');
const name = card.querySelector('.name-in').value;
const role = card.querySelector('.role-in').value;
// Get Image from data-url or src
const imgObj = card.querySelector('.member-img');
let img = '';
if (imgObj.getAttribute('data-url')) {
img = imgObj.getAttribute('data-url');
} else if (imgObj.src && imgObj.src !== window.location.href && !imgObj.src.startsWith('data:')) {
img = imgObj.src;
}
const id = card.getAttribute('data-id') || 'temp_' + Date.now();
if (!name) { ev.preventDefault(); return; }
// For desktop drag
const payload = JSON.stringify({ id, name, role, img });
ev.dataTransfer.setData("text/plain", payload);
// For mobile touch
touchDragData = { id, name, role, img };
}
window.drop = function(ev) {
ev.preventDefault();
const slot = ev.target.closest('.roster-slot');
slot.classList.remove('drag-over');
try {
const data = JSON.parse(ev.dataTransfer.getData("text/plain"));
handleDrop(slot, data);
} catch (e) { console.error(e); }
}
function handleDrop(slot, data) {
const allSlots = document.querySelectorAll('.roster-slot');
// Check if already in roster
for (let s of allSlots) {
if (s.getAttribute('data-player-id') === data.id) {
showNotif("Player already in roster!", "error");
return;
}
}
// Check role limits
let currentRoleCount = 0;
allSlots.forEach(s => {
if (s.getAttribute('data-role') === data.role) currentRoleCount++;
});
if (currentRoleCount >= ROLE_LIMITS[data.role]) {
showNotif(`Limit reached: Max ${ROLE_LIMITS[data.role]} ${data.role}(s)`, "error");
return;
}
renderSlot(slot, data);
}
// --- RENDER SLOT ---
function renderSlot(slot, data) {
slot.setAttribute('data-player-id', data.id);
slot.setAttribute('data-role', data.role);
let roleColor = "bg-slate-700";
if (data.role === 'coach') roleColor = "bg-yellow-900/50 border-yellow-700";
else if (data.role === 'striker') roleColor = "bg-red-900/50 border-red-700";
else if (data.role === 'keeper') roleColor = "bg-green-900/50 border-green-700";
else if (data.role === 'guide') roleColor = "bg-blue-900/50 border-blue-700";
else if (data.role === 'sweeper') roleColor = "bg-purple-900/50 border-purple-700";
else if (data.role === 'libero') roleColor = "bg-orange-900/50 border-orange-700";
const imageUrl = data.img || data.image || "";
let imgHtml = '';
if (imageUrl && imageUrl.startsWith('http')) {
imgHtml = `<img src="${imageUrl}" class="absolute inset-0 w-full h-full object-cover opacity-80 z-0">`;
}
slot.className = `roster-slot ${roleColor} border-2 border-solid rounded-xl flex flex-col justify-end relative overflow-hidden group transition-all`;
slot.innerHTML = `
${imgHtml}
<div class="relative z-10 text-center w-full bg-gradient-to-t from-black via-black/80 to-transparent pt-6 pb-2 px-1">
<div class="text-[9px] font-bold uppercase tracking-widest text-cyan-400 mb-0.5">${data.role}</div>
<div class="text-xs font-bold text-white truncate w-full leading-tight shadow-black drop-shadow-md">${data.name}</div>
</div>
<button onclick="clearSlot(this)" class="absolute top-1 right-1 bg-black/60 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-[10px] opacity-0 group-hover:opacity-100 transition-opacity z-20">✕</button>
`;
}
window.clearSlot = function(btn) {
const slot = btn.closest('.roster-slot');
slot.removeAttribute('data-player-id');
slot.removeAttribute('data-role');
slot.className = "roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative";
slot.innerHTML = "";
event.stopPropagation();
}
// --- APP LOGIC ---
onValue(ref(db, 'teams'), (snapshot) => {
const data = snapshot.val();
window.teamsCache = data || {};
if (!window.isSharedMode) {
renderTeamsList(window.teamsCache);
}
});
function renderTeamsList(teams) {
const list = document.getElementById('teamsList');
list.innerHTML = "";
if (!teams || Object.keys(teams).length === 0) {
return list.innerHTML = `<div class="text-center text-gray-500 mt-10">No teams found.</div>`;
}
Object.entries(teams).forEach(([key, team]) => {
const isActive = window.currentTeamId === key;
list.insertAdjacentHTML('beforeend', `
<div onclick="loadTeamEditor('${key}')" class="p-4 cursor-pointer hover:bg-slate-800 transition-colors flex items-center gap-3 border-l-4 border-transparent ${isActive ? 'active-item bg-slate-800 border-blue-500' : ''}">
<img src="${team.logo || 'https://via.placeholder.com/40?text=?'}" class="w-10 h-10 rounded bg-black object-cover">
<div class="font-bold text-sm">${team.name || 'Unnamed'}</div>
</div>`);
});
}
// Load shared team
async function loadSharedTeam(teamId) {
const teamRef = ref(db, `teams/${teamId}`);
onValue(teamRef, (snapshot) => {
const team = snapshot.val();
if (team) {
window.currentTeamId = teamId;
loadTeamData(team);
document.title = `Editing: ${team.name || 'Team'}`;
} else {
showNotif("Team not found", "error");
}
});
}
window.createNewTeam = function() {
window.currentTeamId = null;
resetUI("Create New Team", true);
document.getElementById('playersContainer').innerHTML = "";
window.addPlayerRow();
renderTeamsList(window.teamsCache);
}
window.loadTeamEditor = function(teamId) {
window.currentTeamId = teamId;
const team = window.teamsCache[teamId];
if (!team) return;
loadTeamData(team);
}
function loadTeamData(team) {
resetUI("Edit Team", false);
document.getElementById('teamNameInput').value = team.name || "";
const logoImg = document.getElementById('teamLogoPreview');
const logoPlace = document.getElementById('teamLogoPlaceholder');
if (team.logo) {
logoImg.src = team.logo;
logoImg.setAttribute('data-url', team.logo);
logoImg.classList.remove('hidden');
logoPlace.classList.add('hidden');
} else {
logoImg.classList.add('hidden');
logoPlace.classList.remove('hidden');
}
const container = document.getElementById('playersContainer');
container.innerHTML = "";
if (team.members) {
Object.entries(team.members).forEach(([uid, m]) => window.addPlayerRow({ ...m, id: uid }));
} else {
window.addPlayerRow();
}
const slots = document.querySelectorAll('.roster-slot');
slots.forEach(slot => {
slot.className = "roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative";
slot.innerHTML = "";
slot.removeAttribute('data-player-id');
slot.removeAttribute('data-role');
});
if (team.active_roster) {
team.active_roster.forEach((m, i) => {
if (i < 6 && m.id) renderSlot(slots[i], m);
});
}
if (!window.isSharedMode) {
renderTeamsList(window.teamsCache);
}
}
window.resetUI = function(title, isNew) {
document.getElementById('emptyState').classList.add('hidden');
document.getElementById('editorPanel').classList.remove('hidden');
document.getElementById('editorPanel').classList.add('flex');
document.getElementById('editorTitle').innerText = title;
}
// --- SHARE FUNCTIONALITY ---
window.openShareModal = function() {
if (!window.currentTeamId) {
showNotif("No team selected", "error");
return;
}
const shareUrl = `${window.location.origin}${window.location.pathname}?teamId=${window.currentTeamId}`;
document.getElementById('shareUrl').value = shareUrl;
document.getElementById('shareModal').classList.add('active');
}
window.closeShareModal = function() {
document.getElementById('shareModal').classList.remove('active');
}
window.copyShareUrl = function() {
const shareUrlInput = document.getElementById('shareUrl');
shareUrlInput.select();
shareUrlInput.setSelectionRange(0, 99999);
navigator.clipboard.writeText(shareUrlInput.value).then(() => {
const copyBtn = document.getElementById('copyBtn');
const originalHtml = copyBtn.innerHTML;
copyBtn.innerHTML = '<i class="fas fa-check"></i>';
copyBtn.classList.add('bg-green-600');
setTimeout(() => {
copyBtn.innerHTML = originalHtml;
copyBtn.classList.remove('bg-green-600');
}, 2000);
showNotif("Link copied to clipboard", "success");
}).catch(err => {
console.error('Failed to copy: ', err);
showNotif("Failed to copy link", "error");
});
}
// --- SAVE ---
window.saveTeam = async function() {
const btn = document.getElementById('saveBtn');
const nameInput = document.getElementById('teamNameInput');
if (!nameInput.value.trim()) return showNotif("Team name required", "error");
btn.disabled = true;
btn.innerHTML = '<div class="spinner"></div>';
try {
let teamId = window.currentTeamId || push(ref(db, 'teams')).key;
// Logo
const logoImg = document.getElementById('teamLogoPreview');
let finalLogo = logoImg.getAttribute('data-url') || (logoImg.src.startsWith('http') ? logoImg.src : "");
if (finalLogo === window.location.href) finalLogo = "";
// Members
const cards = document.querySelectorAll('.member-card');
const membersObj = {};
cards.forEach(card => {
const name = card.querySelector('.name-in').value;
if (!name) return;
const role = card.querySelector('.role-in').value;
const imgObj = card.querySelector('.member-img');
let photo = imgObj.getAttribute('data-url') || "";
if (!photo && imgObj.src.startsWith('http') && imgObj.src !== window.location.href) {
photo = imgObj.src;
}
const mId = card.getAttribute('data-id') || 'mem_' + Date.now() + Math.random().toString(36).substr(2, 5);
card.setAttribute('data-id', mId);
membersObj[mId] = { name, role, image: photo };
});
// Roster
const activeRosterArr = [];
document.querySelectorAll('.roster-slot').forEach(slot => {
const pid = slot.getAttribute('data-player-id');
if (pid && membersObj[pid]) {
activeRosterArr.push({ id: pid, ...membersObj[pid] });
}
});
await update(ref(db, `teams/${teamId}`), {
name: nameInput.value,
logo: finalLogo,
members: membersObj,
active_roster: activeRosterArr
});
window.currentTeamId = teamId;
showNotif("Saved Successfully!", "success");
if (!window.isSharedMode) {
loadTeamEditor(teamId);
}
} catch (e) {
console.error(e);
showNotif("Error: " + e.message, "error");
}
finally {
btn.disabled = false;
btn.innerHTML = 'SAVE';
}
}
// --- DOM HELPER ---
window.addPlayerRow = function(p = null) {
const container = document.getElementById('playersContainer');
const uid = Math.random().toString(36).slice(2);
const pid = p ? p.id : '';
const purl = p ? p.image : '';
const roles = ['striker','guide','keeper','sweeper','libero','coach'];
const opts = roles.map(r => `<option value="${r}" ${p && p.role===r?'selected':''}>${r.toUpperCase()}</option>`).join('');
const html = `
<div id="row-${uid}" draggable="true" ondragstart="drag(event)"
ontouchstart="drag(event)"
class="member-card aspect-[3/4] bg-slate-800 border border-slate-700 rounded-xl flex flex-col relative group overflow-hidden"
data-id="${pid}">
<button onclick="document.getElementById('row-${uid}').remove()"
class="absolute top-2 right-2 z-20 bg-black/60 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity">
</button>
<div class="h-[65%] w-full relative cursor-pointer bg-slate-900 group-inner" onclick="document.getElementById('file-${uid}').click()">
<!-- IMAGE -->
<img id="img-${uid}" src="${purl}"
data-url="${purl}"
class="member-img w-full h-full object-cover ${purl?'':'hidden'}">
<!-- PLACEHOLDER -->
<div id="ph-${uid}" class="absolute inset-0 flex flex-col items-center justify-center text-slate-500 ${purl?'hidden':''}">
<div class="text-3xl mb-1">+</div>
<span class="text-[10px] uppercase">Photo</span>
</div>
<!-- LOADER -->
<div id="loader-${uid}" class="hidden absolute inset-0 bg-black/70 flex items-center justify-center z-30">
<div class="spinner"></div>
</div>
</div>
<!-- Input calls handleInstantUpload on change -->
<input type="file" id="file-${uid}" class="file-in hidden" accept="image/*"
onchange="handleInstantUpload(this, 'img-${uid}', 'loader-${uid}', 'ph-${uid}')">
<div class="h-[35%] bg-slate-950 p-2 flex flex-col justify-center gap-2 border-t border-slate-700">
<input type="text" class="name-in w-full bg-transparent text-center font-bold text-white text-sm focus:outline-none border-b border-transparent focus:border-blue-500 placeholder-slate-600 pb-1"
placeholder="Name" value="${p?p.name:''}">
<select class="role-in w-full bg-slate-900 text-[10px] text-center text-cyan-400 border border-slate-800 rounded py-1 outline-none uppercase tracking-wider">
${opts}
</select>
</div>
</div>`;
container.insertAdjacentHTML('beforeend', html);
}
window.showNotif = function(msg, type) {
const n = document.getElementById('notification');
document.getElementById('notifMsg').innerText = msg;
n.className = `fixed bottom-6 right-6 px-6 py-4 border rounded-lg shadow-2xl transform translate-y-20 transition-transform duration-300 flex items-center gap-3 z-50 ${type==='error'?'bg-red-900/90 border-red-700':'bg-green-900/90 border-green-700'}`;
n.classList.remove('translate-y-20');
setTimeout(() => n.classList.add('translate-y-20'), 3000);
}
// Close modal on ESC key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeShareModal();
});
// Close modal when clicking outside
document.getElementById('shareModal').addEventListener('click', (e) => {
if (e.target.id === 'shareModal') closeShareModal();
});
</script>
</body>
</html>