All checks were successful
Deploy AstraOS Panel / deploy (push) Successful in 13s
- Dashboard verändert - AyranTracker hinzugefügt
288 lines
16 KiB
HTML
288 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Ayran Tracker - Professional Valorant Analytics | AstraOS</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<style>
|
|
.bg-cyberpunk {
|
|
background: radial-gradient(circle at 50% 20%, #1e1b4b 0%, #090514 100%);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-cyberpunk text-slate-100 font-sans min-h-screen flex flex-col justify-between">
|
|
|
|
{% include 'sidebar.html' %}
|
|
|
|
<div class="flex-1 ml-0 md:ml-64">
|
|
<header class="border-b border-purple-900/40 bg-slate-950/70 backdrop-blur-md sticky top-0 z-40">
|
|
<div class="px-4 py-4 flex justify-between items-center">
|
|
<div class="flex items-center space-x-3">
|
|
<span class="text-2xl">🥛</span>
|
|
<span class="text-xl font-black tracking-widest text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-violet-200 to-white">
|
|
AYRAN<span class="text-purple-400">TRACKER</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="flex-grow flex flex-col items-center justify-center px-4 py-12">
|
|
|
|
<div id="searchSection" class="w-full flex flex-col items-center justify-center text-center my-auto">
|
|
<div class="mb-10">
|
|
<span class="px-3 py-1 bg-purple-500/10 text-purple-400 border border-purple-500/20 rounded-full text-xs font-semibold uppercase tracking-wider">
|
|
Live Match Analytics
|
|
</span>
|
|
<h1 class="text-4xl md:text-5xl font-black mt-4 mb-4 tracking-tight uppercase">
|
|
Track Your <span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-violet-300">Performance</span>
|
|
</h1>
|
|
</div>
|
|
|
|
<div class="w-full max-w-xl bg-slate-950/60 p-5 rounded-xl border border-purple-900/30 shadow-2xl backdrop-blur-md">
|
|
<form id="searchForm" class="flex flex-col sm:flex-row gap-2">
|
|
<div class="flex-grow">
|
|
<input type="text" id="riotInput" placeholder="Riot ID #TAG (z.B. Drax#SWEAT)" required class="w-full bg-slate-900/90 border border-purple-900/40 rounded-lg px-4 py-3.5 text-sm text-white focus:outline-none focus:border-purple-500 text-center sm:text-left">
|
|
</div>
|
|
<button type="submit" class="bg-purple-600 hover:bg-purple-700 text-white text-sm font-bold px-6 py-3.5 rounded-lg uppercase tracking-wider transition whitespace-nowrap">
|
|
Analyse
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="profileSection" class="w-full max-w-6xl hidden">
|
|
|
|
<div class="bg-slate-950/60 border border-purple-900/30 rounded-2xl p-6 flex flex-col md:flex-row justify-between items-center gap-6 mb-6 backdrop-blur-md">
|
|
<div class="flex items-center space-x-4 text-center md:text-left flex-col md:flex-row">
|
|
<div id="playerCard" class="w-20 h-20 bg-purple-950 border border-purple-500/30 rounded-xl flex items-center justify-center text-2xl font-bold text-purple-300">VAL</div>
|
|
<div>
|
|
<div class="flex items-center justify-center md:justify-start space-x-2">
|
|
<h2 id="displayPlayerName" class="text-2xl font-black tracking-wide">Player</h2>
|
|
<span id="displayPlayerTag" class="text-slate-400 font-bold bg-slate-900 px-2 py-0.5 rounded text-xs">#TAG</span>
|
|
</div>
|
|
<p class="text-xs text-slate-400 mt-1">Account Level: <span id="displayLevel" class="text-purple-400 font-bold">--</span></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-slate-900/80 border border-purple-900/40 p-4 rounded-xl flex items-center space-x-4 min-w-[240px]">
|
|
<div class="w-10 h-10 bg-purple-900/50 rounded-lg flex items-center justify-center text-xl">🏆</div>
|
|
<div>
|
|
<p class="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Current Rank</p>
|
|
<p id="displayRank" class="text-lg font-black text-purple-300">Loading...</p>
|
|
<p id="displayRR" class="text-xs text-slate-500">-- RR</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
<div class="bg-slate-950/40 border border-purple-950 p-4 rounded-xl">
|
|
<p class="text-xs text-slate-400 font-semibold uppercase tracking-wider mb-1" id="winrateLabel">Win Rate (Competitive)</p>
|
|
<p id="calcWinrate" class="text-2xl font-black text-emerald-400">--</p>
|
|
</div>
|
|
<div class="bg-slate-950/40 border border-purple-950 p-4 rounded-xl">
|
|
<p class="text-xs text-slate-400 font-semibold uppercase tracking-wider mb-1">Average K/D Ratio</p>
|
|
<p id="calcKD" class="text-2xl font-black text-slate-100">--</p>
|
|
</div>
|
|
<div class="bg-slate-950/40 border border-purple-950 p-4 rounded-xl">
|
|
<p class="text-xs text-slate-400 font-semibold uppercase tracking-wider mb-1">Avg. Damage / Round (ADR)</p>
|
|
<p id="calcADR" class="text-2xl font-black text-purple-400">--</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-6 flex justify-between items-center">
|
|
<h3 class="text-sm font-bold uppercase tracking-wider text-slate-300">Match Filter</h3>
|
|
<select id="modeFilter" class="bg-slate-900/80 border border-purple-900/40 rounded-lg px-4 py-2 text-sm text-white focus:outline-none focus:border-purple-500">
|
|
<option value="competitive">Competitive</option>
|
|
<option value="unrated">Unrated</option>
|
|
<option value="swift">Swift</option>
|
|
<option value="team deathmatch">Team Deathmatch</option>
|
|
<option value="custom">Custom</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
|
|
<div class="lg:col-span-2 bg-slate-950/40 border border-purple-900/20 rounded-2xl p-5">
|
|
<h3 class="text-sm font-bold uppercase tracking-wider mb-4 text-slate-300" id="matchesTitle">Recent Matches (Competitive)</h3>
|
|
<div id="matchHistoryContainer" class="space-y-2.5">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-slate-950/40 border border-purple-900/20 rounded-2xl p-5">
|
|
<h3 class="text-sm font-bold uppercase tracking-wider mb-4 text-slate-300">Most Played Agents</h3>
|
|
<div id="agentContainer" class="space-y-3">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button onclick="window.location.href='/dashboard'" class="mt-8 text-xs text-purple-400 hover:text-purple-300 uppercase font-bold transition flex items-center space-x-2 mx-auto">
|
|
<span>← Zurück zum Dashboard</span>
|
|
</button>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
let currentPlayer = { name: '', tag: '' };
|
|
let allMatches = [];
|
|
|
|
const modeNames = {
|
|
'competitive': 'Competitive',
|
|
'unrated': 'Unrated',
|
|
'swift': 'Swift',
|
|
'team deathmatch': 'Team Deathmatch',
|
|
'custom': 'Custom'
|
|
};
|
|
|
|
document.getElementById('modeFilter').addEventListener('change', function() {
|
|
if (currentPlayer.name && currentPlayer.tag) {
|
|
fetchAndDisplayMatches(currentPlayer.name, currentPlayer.tag, this.value);
|
|
}
|
|
});
|
|
|
|
async function fetchAndDisplayMatches(name, tag, mode = 'competitive') {
|
|
const response = await fetch(`/api/v1/profile/${encodeURIComponent(name)}/${encodeURIComponent(tag)}?mode=${mode}`);
|
|
const data = await response.json();
|
|
|
|
if (data.status === "success") {
|
|
updateMatchDisplay(data, mode);
|
|
} else {
|
|
alert("Fehler: " + data.message);
|
|
}
|
|
}
|
|
|
|
function updateMatchDisplay(data, mode) {
|
|
document.getElementById('calcWinrate').innerText = data.calculated_stats.win_rate;
|
|
document.getElementById('calcKD').innerText = data.calculated_stats.kd_ratio;
|
|
document.getElementById('calcADR').innerText = data.calculated_stats.avg_damage_round;
|
|
|
|
const modeName = modeNames[mode] || mode.charAt(0).toUpperCase() + mode.slice(1);
|
|
document.getElementById('winrateLabel').innerText = `Win Rate (${modeName})`;
|
|
document.getElementById('matchesTitle').innerText = `Recent Matches (${modeName} - Last ${data.calculated_stats.matches_analyzed})`;
|
|
|
|
const historyContainer = document.getElementById('matchHistoryContainer');
|
|
historyContainer.innerHTML = '';
|
|
|
|
if (data.matches.length === 0) {
|
|
historyContainer.innerHTML = `<p class="text-xs text-slate-500 p-4 text-center">Keine ${modeName} Matches gefunden.</p>`;
|
|
} else {
|
|
data.matches.forEach(match => {
|
|
const isWin = match.result === "WIN";
|
|
const borderColor = isWin ? 'border-emerald-500' : 'border-rose-500';
|
|
const textColor = isWin ? 'text-emerald-400' : 'text-rose-400';
|
|
|
|
let rrDisplay = "";
|
|
let rrClass = "";
|
|
if (match.rr_change > 0) {
|
|
rrDisplay = `+${match.rr_change} RR`;
|
|
rrClass = "text-emerald-400 bg-emerald-950/40 border border-emerald-900/30";
|
|
} else if (match.rr_change < 0) {
|
|
rrDisplay = `${match.rr_change} RR`;
|
|
rrClass = "text-rose-400 bg-rose-950/40 border border-rose-900/30";
|
|
} else {
|
|
rrDisplay = "0 RR";
|
|
rrClass = "text-slate-400 bg-slate-900 border border-slate-800";
|
|
}
|
|
|
|
const matchHTML = `
|
|
<div class="bg-slate-900/60 border-l-4 ${borderColor} rounded-r-xl p-3.5 flex justify-between items-center text-xs sm:text-sm transition hover:bg-slate-900/90">
|
|
<div>
|
|
<span class="font-black ${textColor} tracking-wider mr-2">${match.result}</span>
|
|
<span class="font-bold text-slate-200">${match.rounds_won}:${match.rounds_lost}</span>
|
|
<span class="text-slate-400 text-xs block mt-0.5">${match.map} • <span class="text-purple-300">${match.agent}</span></span>
|
|
</div>
|
|
<div class="text-center">
|
|
<p class="text-[10px] text-slate-500 uppercase tracking-wider">K / D / A</p>
|
|
<p class="font-bold text-slate-200">${match.kills} / <span class="text-rose-400/80">${match.deaths}</span> / ${match.assists}</p>
|
|
<p class="text-[10px] text-purple-400 font-mono mt-0.5">Score: ${match.score}</p>
|
|
</div>
|
|
<div class="text-right flex flex-col items-end justify-center">
|
|
<span class="font-semibold text-xs px-2.5 py-1 rounded-md ${rrClass}">
|
|
${rrDisplay}
|
|
</span>
|
|
<p class="text-[9px] text-slate-500 block mt-1.5 font-medium">${match.date}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
historyContainer.insertAdjacentHTML('beforeend', matchHTML);
|
|
});
|
|
}
|
|
|
|
const agentContainer = document.getElementById('agentContainer');
|
|
agentContainer.innerHTML = '';
|
|
if (data.top_agents.length === 0) {
|
|
agentContainer.innerHTML = '<p class="text-xs text-slate-500 text-center py-2">Keine Agenten-Daten.</p>';
|
|
} else {
|
|
data.top_agents.forEach(agent => {
|
|
agentContainer.insertAdjacentHTML('beforeend', `
|
|
<div class="flex justify-between items-center bg-slate-900/50 p-3 rounded-lg border border-purple-950/40">
|
|
<span class="text-xs font-bold text-purple-300">${agent.name}</span>
|
|
<span class="text-xs font-semibold text-slate-400 bg-slate-950 px-2 py-0.5 rounded">${agent.count}x gespielt</span>
|
|
</div>
|
|
`);
|
|
});
|
|
}
|
|
}
|
|
|
|
document.getElementById('searchForm').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
|
|
const rawInput = document.getElementById('riotInput').value.trim();
|
|
|
|
if (!rawInput.includes('#')) {
|
|
alert("Bitte gib deine ID im Format 'Name#Tag' an (z.B. Drax#SWEAT).");
|
|
return;
|
|
}
|
|
|
|
const parts = rawInput.split('#');
|
|
const name = parts[0].trim();
|
|
const tag = parts[1].trim();
|
|
|
|
if (!name || !tag) {
|
|
alert("Name oder Tag unvollständig. Beispiel: Drax#SWEAT");
|
|
return;
|
|
}
|
|
|
|
currentPlayer = { name, tag };
|
|
const btn = this.querySelector('button[type="submit"]');
|
|
btn.innerText = "Analyzing...";
|
|
btn.disabled = true;
|
|
|
|
try {
|
|
const response = await fetch(`/api/v1/profile/${encodeURIComponent(name)}/${encodeURIComponent(tag)}?mode=competitive`);
|
|
const data = await response.json();
|
|
|
|
if (data.status === "success") {
|
|
document.getElementById('displayPlayerName').innerText = data.account.name;
|
|
document.getElementById('displayPlayerTag').innerText = `#${data.account.tag.toUpperCase()}`;
|
|
document.getElementById('displayLevel').innerText = data.account.level;
|
|
document.getElementById('displayRank').innerText = data.rank.current_tier_name;
|
|
document.getElementById('displayRR').innerText = `${data.rank.ranking_in_tier} RR`;
|
|
|
|
if (data.account.card_url) {
|
|
document.getElementById('playerCard').style.backgroundImage = `url('${data.account.card_url}')`;
|
|
document.getElementById('playerCard').style.backgroundSize = 'cover';
|
|
document.getElementById('playerCard').innerText = '';
|
|
}
|
|
|
|
document.getElementById('modeFilter').value = 'competitive';
|
|
updateMatchDisplay(data, 'competitive');
|
|
|
|
document.getElementById('searchSection').classList.add('hidden');
|
|
document.getElementById('profileSection').classList.remove('hidden');
|
|
} else {
|
|
alert("Fehler: " + data.message);
|
|
}
|
|
} catch (error) {
|
|
alert("API-Fehler oder Verbindungsabbruch: " + error.message);
|
|
} finally {
|
|
btn.innerText = "Analyse";
|
|
btn.disabled = false;
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|