2.0.0-rc1 : Profile and achievements update.

This commit is contained in:
2026-08-09 00:50:45 +08:00
parent 562775e5db
commit 3874bf57a9
31 changed files with 2618 additions and 108 deletions
+66
View File
@@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block title %}战队荣誉 - YRTV{% endblock %}
{% block content %}
<div class="space-y-8 px-4 sm:px-0">
<div>
<p class="text-xs font-bold uppercase tracking-widest text-amber-600">YRTV Awards</p>
<h1 class="mt-1 text-3xl font-black text-gray-900 dark:text-white">周期最佳与荣誉榜</h1>
<p class="mt-2 text-sm text-gray-500">完全基于已导入比赛。月、季度和年度奖项设有最低场次门槛。</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ url_for('awards.index') }}" class="rounded-full px-4 py-2 text-sm font-bold {% if not award_type %}bg-amber-500 text-white{% else %}bg-white text-gray-600 dark:bg-slate-800 dark:text-gray-300{% endif %}">全部</a>
{% for key, label in award_types.items() %}
<a href="{{ url_for('awards.index', type=key) }}" class="rounded-full px-4 py-2 text-sm font-bold {% if award_type == key %}bg-amber-500 text-white{% else %}bg-white text-gray-600 dark:bg-slate-800 dark:text-gray-300{% endif %}">{{ label }}</a>
{% endfor %}
</div>
<div class="grid grid-cols-1 gap-8 xl:grid-cols-3">
<section class="rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 p-6 text-white shadow-xl">
<h2 class="text-xl font-black">荣誉榜</h2>
<div class="mt-5 space-y-3">
{% for player in leaderboard[:10] %}
<a href="{{ url_for('players.detail', steam_id=player.steam_id_64) }}" class="flex items-center justify-between rounded-xl bg-white/10 p-3 hover:bg-white/20">
<div>
<div class="font-bold">{{ loop.index }}. {{ player.identity.username or player.steam_id_64 }}</div>
<div class="text-xs text-amber-100">
年 {{ player.yearly }} · 季 {{ player.quarterly }} · 月 {{ player.monthly }} · 周 {{ player.weekly }} · 日 {{ player.daily }}
</div>
</div>
<div class="text-2xl font-black">{{ player.awards }}</div>
</a>
{% else %}
<div class="py-8 text-center text-amber-100">暂无奖项</div>
{% endfor %}
</div>
</section>
<section class="space-y-4 xl:col-span-2">
{% for award in awards %}
<article class="rounded-2xl border border-gray-100 bg-white p-5 shadow dark:border-slate-700 dark:bg-slate-800">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex items-center gap-4">
<div class="flex h-14 w-14 items-center justify-center rounded-2xl bg-amber-100 text-2xl text-amber-700">🏆</div>
<div>
<div class="text-xs font-bold uppercase tracking-wider text-amber-600">{{ award.award_label }}</div>
<a href="{{ url_for('players.detail', steam_id=award.steam_id_64) }}" class="text-xl font-black text-gray-900 hover:text-yrtv-600 dark:text-white">{{ award.identity.username or award.steam_id_64 }}</a>
<div class="text-xs text-gray-400">{{ award.period_key }} · {{ award.matches }} 场</div>
</div>
</div>
<div class="grid grid-cols-4 gap-4 text-center">
<div><div class="text-[10px] uppercase text-gray-400">Rating</div><div class="font-black">{{ '%.2f'|format(award.avg_rating) }}</div></div>
<div><div class="text-[10px] uppercase text-gray-400">K/D</div><div class="font-black">{{ '%.2f'|format(award.avg_kd) }}</div></div>
<div><div class="text-[10px] uppercase text-gray-400">ADR</div><div class="font-black">{{ '%.1f'|format(award.avg_adr) }}</div></div>
<div><div class="text-[10px] uppercase text-gray-400">Win</div><div class="font-black">{{ '%.0f%%'|format(award.win_rate * 100) }}</div></div>
</div>
</div>
</article>
{% else %}
<div class="rounded-2xl bg-white py-16 text-center text-gray-400 dark:bg-slate-800">当前筛选没有满足最低场次的奖项。</div>
{% endfor %}
</section>
</div>
</div>
{% endblock %}
+4
View File
@@ -46,6 +46,8 @@
<a href="{{ url_for('main.index') }}" class="{% if request.endpoint == 'main.index' %}border-yrtv-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">首页</a>
<a href="{{ url_for('matches.index') }}" class="{% if request.endpoint and 'matches' in request.endpoint %}border-yrtv-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">比赛</a>
<a href="{{ url_for('players.index') }}" class="{% if request.endpoint and 'players' in request.endpoint %}border-yrtv-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">玩家</a>
<a href="{{ url_for('reports.index') }}" class="{% if request.endpoint and 'reports' in request.endpoint %}border-yrtv-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">报告</a>
<a href="{{ url_for('awards.index') }}" class="{% if request.endpoint and 'awards' in request.endpoint %}border-amber-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">荣誉</a>
<a href="{{ url_for('teams.index') }}" class="{% if request.endpoint and 'teams' in request.endpoint %}border-yrtv-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">战队</a>
<a href="{{ url_for('opponents.index') }}" class="{% if request.endpoint and 'opponents' in request.endpoint %}border-yrtv-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">对手</a>
<a href="{{ url_for('tactics.index') }}" class="{% if request.endpoint and 'tactics' in request.endpoint %}border-yrtv-500 text-gray-900 dark:text-white{% else %}border-transparent text-gray-500 dark:text-gray-300 hover:border-gray-300 hover:text-gray-700 dark:hover:text-white{% endif %} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">战术</a>
@@ -84,6 +86,8 @@
<a href="{{ url_for('main.index') }}" class="{% if request.endpoint == 'main.index' %}bg-yrtv-50 border-yrtv-500 text-yrtv-700{% else %}border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700{% endif %} block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">首页</a>
<a href="{{ url_for('matches.index') }}" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">比赛</a>
<a href="{{ url_for('players.index') }}" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">玩家</a>
<a href="{{ url_for('reports.index') }}" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">报告</a>
<a href="{{ url_for('awards.index') }}" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">荣誉</a>
<a href="{{ url_for('teams.index') }}" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">战队</a>
<a href="{{ url_for('opponents.index') }}" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">对手</a>
<a href="{{ url_for('tactics.index') }}" class="border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium dark:text-white dark:hover:bg-slate-700">战术</a>
@@ -0,0 +1,68 @@
{% if post_match_report %}
<section class="rounded-2xl border border-gray-100 bg-white p-6 shadow dark:border-slate-700 dark:bg-slate-800">
<div class="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
<div>
<div class="flex items-center gap-3">
<span class="rounded-lg px-3 py-1 text-xs font-black {% if post_match_report.is_win %}bg-green-100 text-green-700{% else %}bg-red-100 text-red-600{% endif %}">
{{ 'VICTORY' if post_match_report.is_win else 'DEFEAT' }}
</span>
<h2 class="text-xl font-black text-gray-900 dark:text-white">赛后报告</h2>
</div>
<p class="mt-3 max-w-3xl text-sm text-gray-600 dark:text-gray-300">{{ post_match_report.summary_text }}</p>
</div>
<div class="grid grid-cols-3 gap-5 text-center">
<div><div class="text-2xl font-black text-yrtv-600">{{ '%.2f'|format(post_match_report.team_avg_rating) }}</div><div class="text-[10px] uppercase text-gray-400">Team Rating</div></div>
<div><div class="text-2xl font-black text-gray-900 dark:text-white">{{ '%.1f'|format(post_match_report.team_avg_adr) }}</div><div class="text-[10px] uppercase text-gray-400">Team ADR</div></div>
<div><div class="text-2xl font-black text-amber-600">{{ post_match_report.record_break_count }}</div><div class="text-[10px] uppercase text-gray-400">Records</div></div>
</div>
</div>
<div class="mt-6 grid grid-cols-1 gap-4 md:grid-cols-3">
<div class="rounded-xl bg-amber-50 p-4 dark:bg-amber-900/20">
<div class="text-xs font-bold uppercase text-amber-600">Match MVP</div>
<a href="{{ url_for('players.detail', steam_id=post_match_report.mvp_steam_id) }}" class="mt-1 block text-lg font-black text-gray-900 dark:text-white">{{ post_match_report.mvp.username or post_match_report.mvp_steam_id }}</a>
<div class="text-sm text-gray-500">Rating {{ '%.2f'|format(post_match_report.mvp_rating) }}</div>
</div>
<div class="rounded-xl bg-green-50 p-4 dark:bg-green-900/20">
<div class="text-xs font-bold uppercase text-green-600">Form Improver</div>
{% if post_match_report.improver_steam_id %}
<a href="{{ url_for('players.detail', steam_id=post_match_report.improver_steam_id) }}" class="mt-1 block text-lg font-black text-gray-900 dark:text-white">{{ post_match_report.improver.username or post_match_report.improver_steam_id }}</a>
<div class="text-sm text-green-600">Rating {{ '%+.2f'|format(post_match_report.improver_delta) }}</div>
{% else %}
<div class="mt-1 text-sm text-gray-400">近期样本不足</div>
{% endif %}
</div>
<div class="rounded-xl bg-yrtv-50 p-4 dark:bg-yrtv-900/20">
<div class="text-xs font-bold uppercase text-yrtv-600">Strongest Duo</div>
{% if post_match_report.strongest_duo %}
<div class="mt-1 text-lg font-black text-gray-900 dark:text-white">
{% for player in post_match_report.strongest_duo.players %}
{{ player.username }}{% if not loop.last %} + {% endif %}
{% endfor %}
</div>
<div class="text-sm text-gray-500">Rating {{ '%.2f'|format(post_match_report.strongest_duo.avg_rating) }}</div>
{% else %}
<div class="mt-1 text-sm text-gray-400">本场无二人组</div>
{% endif %}
</div>
</div>
<div class="mt-6 overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 text-sm dark:divide-slate-700">
<thead><tr class="text-left text-xs uppercase text-gray-400"><th class="py-2">Player</th><th>Rating</th><th>vs Prior 20</th><th>ADR Δ</th><th>Status</th><th>Record</th></tr></thead>
<tbody class="divide-y divide-gray-100 dark:divide-slate-700">
{% for player in post_match_report.players %}
<tr>
<td class="py-3 font-bold"><a href="{{ url_for('players.detail', steam_id=player.steam_id_64) }}" class="hover:text-yrtv-600">{{ player.identity.username or player.steam_id_64 }}</a></td>
<td class="font-mono">{{ '%.2f'|format(player.rating or 0) }}</td>
<td class="font-mono {% if (player.rating_delta or 0) > 0 %}text-green-600{% elif (player.rating_delta or 0) < 0 %}text-red-500{% endif %}">{{ '%+.2f'|format(player.rating_delta) if player.rating_delta is not none else 'N/A' }}</td>
<td class="font-mono">{{ '%+.1f'|format(player.adr_delta) if player.adr_delta is not none else 'N/A' }}</td>
<td>{{ player.performance_text }}</td>
<td>{% for label in player.record_labels %}<span class="mr-1 rounded bg-amber-100 px-2 py-1 text-[10px] font-bold text-amber-700">{{ label }}</span>{% else %}<span class="text-gray-300">-</span>{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endif %}
+2
View File
@@ -43,6 +43,8 @@
</div>
</div>
{% include "matches/_post_match_report.html" %}
<!-- Tab: Overview -->
<div x-show="tab === 'overview'" class="space-y-6">
<!-- Team 1 Stats -->
@@ -0,0 +1,45 @@
<section class="overflow-hidden rounded-2xl border border-gray-100 bg-gradient-to-r from-slate-900 via-slate-800 to-yrtv-900 p-6 text-white shadow-xl dark:border-slate-700">
<div class="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-xs font-bold uppercase tracking-[0.25em] text-yrtv-300">Professional Identity</div>
<div class="mt-2 flex flex-wrap items-center gap-3">
<span class="rounded-full bg-white/10 px-3 py-1 text-sm font-bold uppercase">{{ professional_identity.member_role or 'member' }}</span>
<span class="text-sm text-slate-300">{{ professional_identity.roster_version or 'Active Roster' }}</span>
</div>
<div class="mt-4 text-sm text-slate-300">
{{ professional_identity.total_matches or 0 }} 场效力 · 队内 Percentile {{ '%.0f%%'|format(professional_identity.tier_percentile or 0) }}
</div>
</div>
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
<div class="rounded-xl bg-white/10 p-3">
<div class="text-[10px] uppercase text-slate-400">代表武器</div>
<div class="mt-1 font-black">{{ professional_identity.core_top_weapon or 'N/A' }}</div>
</div>
<div class="rounded-xl bg-white/10 p-3">
<div class="text-[10px] uppercase text-slate-400">最佳地图</div>
<div class="mt-1 font-black">{{ professional_identity.meta_map_best_map or 'N/A' }}</div>
</div>
<div class="rounded-xl bg-white/10 p-3">
<div class="text-[10px] uppercase text-slate-400">荣誉</div>
<div class="mt-1 text-2xl font-black text-amber-400">{{ professional_identity.award_count or 0 }}</div>
</div>
<div class="rounded-xl bg-white/10 p-3">
<div class="text-[10px] uppercase text-slate-400">月/季/年最佳</div>
<div class="mt-1 font-black text-amber-300">
{{ professional_identity.awards_by_type.get('monthly', 0) }}/{{ professional_identity.awards_by_type.get('quarterly', 0) }}/{{ professional_identity.awards_by_type.get('yearly', 0) }}
</div>
</div>
</div>
</div>
{% if honors.awards %}
<div class="mt-5 flex gap-2 overflow-x-auto border-t border-white/10 pt-4">
{% for award in honors.awards[:8] %}
<a href="{{ url_for('awards.index', type=award.award_type) }}" class="whitespace-nowrap rounded-full bg-amber-400/20 px-3 py-1 text-xs font-bold text-amber-200">
🏆 {{ award.period_key }} {{ award.award_label }}
</a>
{% endfor %}
</div>
{% endif %}
</section>
+2
View File
@@ -148,6 +148,8 @@
</div>
</div>
{% include "players/_professional_identity.html" %}
<!-- 1.5 Lifetime Stats (Quantity) -->
<div class="bg-white dark:bg-slate-800 shadow-xl rounded-2xl overflow-hidden border border-gray-100 dark:border-slate-700 p-6">
<div class="flex flex-col lg:flex-row gap-8 items-center">
+37
View File
@@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}赛后报告 - YRTV{% endblock %}
{% block content %}
<div class="space-y-6 px-4 sm:px-0">
<div>
<p class="text-xs font-bold uppercase tracking-widest text-yrtv-600">Post Match</p>
<h1 class="mt-1 text-3xl font-black text-gray-900 dark:text-white">赛后报告中心</h1>
<p class="mt-2 text-sm text-gray-500">表现变化只与该队员比赛发生前的最近 20 场比较。</p>
</div>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
{% for report in reports %}
<a href="{{ url_for('matches.detail', match_id=report.match_id) }}" class="rounded-2xl border border-gray-100 bg-white p-5 shadow transition hover:border-yrtv-300 hover:shadow-lg dark:border-slate-700 dark:bg-slate-800">
<div class="flex items-start justify-between gap-4">
<div>
<div class="flex items-center gap-2">
<span class="rounded px-2 py-1 text-xs font-black {% if report.is_win %}bg-green-100 text-green-700{% else %}bg-red-100 text-red-600{% endif %}">{{ 'WIN' if report.is_win else 'LOSS' }}</span>
<span class="font-black text-gray-900 dark:text-white">{{ report.map_name }}</span>
</div>
<p class="mt-3 text-sm text-gray-600 dark:text-gray-300">{{ report.summary_text }}</p>
<div class="mt-3 text-xs text-gray-400">{{ report.roster_count }} 名 roster 队员 · {{ report.record_break_count }} 项纪录刷新</div>
</div>
<div class="text-right">
<div class="text-2xl font-black text-yrtv-600">{{ '%.2f'|format(report.team_avg_rating) }}</div>
<div class="text-[10px] uppercase text-gray-400">Team Rating</div>
<div class="mt-2 text-xs text-gray-500">MVP {{ report.mvp.username or report.mvp_steam_id }}</div>
</div>
</div>
</a>
{% else %}
<div class="col-span-full rounded-2xl bg-white py-16 text-center text-gray-400 dark:bg-slate-800">暂无赛后报告</div>
{% endfor %}
</div>
</div>
{% endblock %}
+3
View File
@@ -13,6 +13,9 @@
</h2>
</div>
<div class="mt-4 flex md:mt-0 md:ml-4">
<a href="{{ url_for('teams.performance') }}" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 dark:bg-slate-700 dark:border-slate-600 dark:text-white">
Team Career
</a>
{% if session.get('is_admin') %}
<button @click="showScoutModal = true" type="button" class="ml-3 inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-yrtv-600 hover:bg-yrtv-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yrtv-500">
<span class="mr-2">🔍</span> Scout Player
+156
View File
@@ -0,0 +1,156 @@
{% extends "base.html" %}
{% block title %}战队履历 - YRTV{% endblock %}
{% block content %}
<div class="space-y-8 px-4 sm:px-0">
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<p class="text-xs font-bold uppercase tracking-widest text-yrtv-600">Team Career</p>
<h1 class="mt-1 text-3xl font-black text-gray-900 dark:text-white">战队履历与阵容表现</h1>
<p class="mt-2 text-sm text-gray-500">只统计 roster 成员在同一队伍实际共同出场的比赛。</p>
</div>
<a href="{{ url_for('teams.index') }}" class="rounded-lg bg-yrtv-600 px-4 py-2 text-sm font-bold text-white hover:bg-yrtv-500">
返回 Clubhouse
</a>
</div>
<div class="grid grid-cols-2 gap-4 lg:grid-cols-5">
{% for label, value, style in [
('Matches', summary.matches, ''),
('Wins', summary.wins, 'text-green-600'),
('Losses', summary.losses, 'text-red-500'),
('Win Rate', '%.1f%%'|format(summary.win_rate * 100), 'text-yrtv-600'),
('Team Rating', '%.2f'|format(summary.avg_rating), '')
] %}
<div class="rounded-2xl border border-gray-100 bg-white p-5 shadow dark:border-slate-700 dark:bg-slate-800">
<div class="text-xs font-bold uppercase text-gray-400">{{ label }}</div>
<div class="mt-2 text-3xl font-black text-gray-900 dark:text-white {{ style }}">{{ value }}</div>
</div>
{% endfor %}
</div>
<section class="rounded-2xl border border-gray-100 bg-white p-6 shadow dark:border-slate-700 dark:bg-slate-800">
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">战队赛季</h2>
<a href="{{ url_for('awards.index') }}" class="text-sm font-bold text-amber-600">查看周期荣誉</a>
</div>
<div class="mt-5 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{% for season in seasons %}
<div class="rounded-xl bg-gradient-to-br from-slate-900 to-slate-700 p-5 text-white">
<div class="text-xs font-bold uppercase tracking-widest text-slate-300">{{ season.season_label }}</div>
<div class="mt-3 grid grid-cols-3 gap-3 text-center">
<div><div class="text-2xl font-black">{{ season.matches }}</div><div class="text-[10px] uppercase text-slate-400">Matches</div></div>
<div><div class="text-2xl font-black text-green-400">{{ '%.0f%%'|format(season.win_rate * 100) }}</div><div class="text-[10px] uppercase text-slate-400">Win</div></div>
<div><div class="text-2xl font-black">{{ '%.2f'|format(season.avg_team_rating) }}</div><div class="text-[10px] uppercase text-slate-400">Rating</div></div>
</div>
<div class="mt-4 border-t border-white/10 pt-3 text-xs text-slate-300">
最佳地图 {{ season.best_map }} ({{ '%.0f%%'|format(season.best_map_win_rate * 100) }})<br>
赛季选手 {{ season.top_player.username or season.top_player_steam_id }} ({{ '%.2f'|format(season.top_player_rating) }})
</div>
</div>
{% else %}
<div class="text-gray-400">暂无赛季数据</div>
{% endfor %}
</div>
</section>
<section class="rounded-2xl border border-gray-100 bg-white p-6 shadow dark:border-slate-700 dark:bg-slate-800">
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">当前 Roster Version</h2>
{% if versions %}
<span class="rounded-full bg-green-100 px-3 py-1 text-xs font-bold text-green-700">{{ versions[0].name }}</span>
{% endif %}
</div>
<div class="mt-5 grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
{% for member in roster %}
<div class="rounded-xl bg-gray-50 p-3 transition hover:bg-yrtv-50 dark:bg-slate-700/40">
<a href="{{ url_for('players.detail', steam_id=member.steam_id_64) }}"
class="block truncate font-bold text-gray-900 dark:text-white">{{ member.username or member.steam_id_64 }}</a>
{% if session.get('is_admin') %}
<form action="{{ url_for('teams.roster_role') }}" method="POST" class="mt-2">
<input type="hidden" name="steam_id" value="{{ member.steam_id_64 }}">
<select name="member_role" onchange="this.form.submit()"
class="w-full rounded border-gray-200 bg-white py-1 text-xs uppercase dark:border-slate-600 dark:bg-slate-700">
{% for role in ['starter', 'substitute', 'member'] %}
<option value="{{ role }}" {% if member.member_role == role %}selected{% endif %}>{{ role }}</option>
{% endfor %}
</select>
</form>
{% else %}
<div class="mt-1 text-xs uppercase text-gray-400">{{ member.member_role }}</div>
{% endif %}
</div>
{% endfor %}
</div>
</section>
<div class="grid grid-cols-1 gap-8 xl:grid-cols-2">
<section class="rounded-2xl border border-gray-100 bg-white p-6 shadow dark:border-slate-700 dark:bg-slate-800">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">二人组表现</h2>
<div class="mt-5 space-y-3">
{% for duo in duos %}
<div class="flex items-center justify-between rounded-xl bg-gray-50 p-4 dark:bg-slate-700/40">
<div>
<div class="font-bold text-gray-900 dark:text-white">
{{ duo.player_a.username or duo.steam_id_a }} + {{ duo.player_b.username or duo.steam_id_b }}
</div>
<div class="mt-1 text-xs text-gray-400">{{ duo.matches }} 场 · 平均 Rating {{ '%.2f'|format(duo.avg_combined_rating) }}</div>
</div>
<div class="text-right">
<div class="text-xl font-black {% if duo.win_rate >= 0.5 %}text-green-600{% else %}text-red-500{% endif %}">
{{ '%.0f%%'|format(duo.win_rate * 100) }}
</div>
<div class="text-[10px] text-gray-400">{{ '可靠样本' if duo.sample_reliable else '小样本' }}</div>
</div>
</div>
{% else %}
<div class="py-8 text-center text-gray-400">暂无二人组数据</div>
{% endfor %}
</div>
</section>
<section class="rounded-2xl border border-gray-100 bg-white p-6 shadow dark:border-slate-700 dark:bg-slate-800">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">实际阵容组合</h2>
<div class="mt-5 space-y-3">
{% for lineup in lineups %}
<div class="rounded-xl bg-gray-50 p-4 dark:bg-slate-700/40">
<div class="flex items-start justify-between gap-4">
<div>
<div class="flex flex-wrap gap-1">
{% for player in lineup.players %}
<span class="rounded bg-white px-2 py-1 text-xs font-bold text-gray-700 dark:bg-slate-600 dark:text-white">{{ player.username }}</span>
{% endfor %}
</div>
<div class="mt-2 text-xs text-gray-400">{{ lineup.player_count }} 人 · {{ lineup.matches }} 场 · Rating {{ '%.2f'|format(lineup.avg_team_rating) }}</div>
</div>
<div class="text-xl font-black {% if lineup.win_rate >= 0.5 %}text-green-600{% else %}text-red-500{% endif %}">
{{ '%.0f%%'|format(lineup.win_rate * 100) }}
</div>
</div>
</div>
{% else %}
<div class="py-8 text-center text-gray-400">暂无阵容组合数据</div>
{% endfor %}
</div>
</section>
</div>
<section class="rounded-2xl border border-gray-100 bg-white p-6 shadow dark:border-slate-700 dark:bg-slate-800">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Roster Version 历史</h2>
<div class="mt-5 divide-y divide-gray-100 dark:divide-slate-700">
{% for version in versions %}
<div class="flex items-center justify-between py-3">
<div>
<div class="font-bold text-gray-900 dark:text-white">{{ version.name }}</div>
<div class="text-xs text-gray-400">{{ version.member_count }} members · {{ version.effective_from }}</div>
</div>
<span class="rounded-full px-3 py-1 text-xs font-bold {% if version.is_current %}bg-green-100 text-green-700{% else %}bg-gray-100 text-gray-500{% endif %}">
{{ 'Current' if version.is_current else 'Archived' }}
</span>
</div>
{% endfor %}
</div>
</section>
</div>
{% endblock %}