···
528
+
.collapsible-section {
530
+
background: #1e293b;
531
+
border: 1px solid #334155;
532
+
border-radius: 0.75rem;
536
+
.collapsible-header {
537
+
padding: 1rem 1.5rem;
540
+
justify-content: space-between;
541
+
align-items: center;
542
+
background: #1e293b;
543
+
border-bottom: 1px solid #334155;
544
+
transition: background 0.2s;
547
+
.collapsible-header:hover {
548
+
background: #334155;
551
+
.collapsible-title {
556
+
align-items: center;
560
+
.collapsible-count {
561
+
background: rgba(239, 68, 68, 0.2);
563
+
padding: 0.25rem 0.5rem;
564
+
border-radius: 0.375rem;
565
+
font-size: 0.875rem;
568
+
.collapsible-arrow {
569
+
transition: transform 0.2s;
573
+
.collapsible-content {
576
+
transition: max-height 0.3s ease-out;
579
+
.collapsible-content.open {
580
+
max-height: 1000px;
@media (max-width: 768px) {
.subtitle { font-size: 1rem; }
···
console.error('Failed to copy:', err);
762
+
function toggleCollapsible() {
763
+
const content = document.getElementById('collapsible-content');
764
+
const arrow = document.getElementById('collapsible-arrow');
766
+
if (content.classList.contains('open')) {
767
+
content.classList.remove('open');
768
+
arrow.textContent = '▼';
770
+
content.classList.add('open');
771
+
arrow.textContent = '▲';
···
{{range $i, $e := .Entries}}
751
-
<tr{{if $e.IsPending}} class="pending"{{else if $e.IsBroken}} class="broken"{{end}}>
752
-
<td class="rank rank-{{add $i 1}}">{{if $e.IsBroken}}💥{{else if $e.IsPending}}⏳{{else if lt $i 3}}{{medal $i}}{{else}}{{add $i 1}}{{end}}</td>
753
-
<td class="player-name"><a href="/user/{{$e.Username}}" style="color: inherit; text-decoration: none;">{{$e.Username}}{{if $e.IsPending}} <span style="font-size: 0.8em;">(pending)</span>{{else if $e.IsBroken}} <span style="font-size: 0.8em; color: #ef4444;" title="{{$e.FailureMessage}}">(failed)</span>{{end}}</a></td>
754
-
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}<strong>{{$e.Rating}}</strong> <span style="color: #94a3b8; font-size: 0.85em;">±{{$e.RD}}</span>{{end}}</td>
755
-
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}{{$e.Wins}}{{end}}</td>
756
-
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}{{$e.Losses}}{{end}}</td>
757
-
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}<span class="win-rate {{winRateClass $e}}">{{winRate $e}}%</span>{{end}}</td>
758
-
<td>{{if or $e.IsPending $e.IsBroken}}-{{else}}{{printf "%.1f" $e.AvgMoves}}{{end}}</td>
759
-
<td style="color: #64748b;">{{if $e.IsPending}}Waiting...{{else if $e.IsBroken}}<span title="{{$e.FailureMessage}}">Failed</span>{{else}}{{$e.LastPlayed.Format "Jan 2, 3:04 PM"}}{{end}}</td>
819
+
<tr{{if $e.IsPending}} class="pending"{{end}}>
820
+
<td class="rank rank-{{add $i 1}}">{{if $e.IsPending}}⏳{{else if lt $i 3}}{{medal $i}}{{else}}{{add $i 1}}{{end}}</td>
821
+
<td class="player-name"><a href="/user/{{$e.Username}}" style="color: inherit; text-decoration: none;">{{$e.Username}}{{if $e.IsPending}} <span style="font-size: 0.8em;">(pending)</span>{{end}}</a></td>
822
+
<td>{{if $e.IsPending}}-{{else}}<strong>{{$e.Rating}}</strong> <span style="color: #94a3b8; font-size: 0.85em;">±{{$e.RD}}</span>{{end}}</td>
823
+
<td>{{if $e.IsPending}}-{{else}}{{$e.Wins}}{{end}}</td>
824
+
<td>{{if $e.IsPending}}-{{else}}{{$e.Losses}}{{end}}</td>
825
+
<td>{{if $e.IsPending}}-{{else}}<span class="win-rate {{winRateClass $e}}">{{winRate $e}}%</span>{{end}}</td>
826
+
<td>{{if $e.IsPending}}-{{else}}{{printf "%.1f" $e.AvgMoves}}{{end}}</td>
827
+
<td style="color: #64748b;">{{if $e.IsPending}}Waiting...{{else}}{{$e.LastPlayed.Format "Jan 2, 3:04 PM"}}{{end}}</td>
···
844
+
{{if .BrokenEntries}}
845
+
<div class="collapsible-section">
846
+
<div class="collapsible-header" onclick="toggleCollapsible()">
847
+
<div class="collapsible-title">
848
+
💥 Failed Submissions
849
+
<span class="collapsible-count">{{len .BrokenEntries}}</span>
851
+
<span class="collapsible-arrow" id="collapsible-arrow">▼</span>
853
+
<div class="collapsible-content" id="collapsible-content">
856
+
{{range $e := .BrokenEntries}}
857
+
<tr class="broken">
858
+
<td class="rank">💥</td>
859
+
<td class="player-name"><a href="/user/{{$e.Username}}" style="color: inherit; text-decoration: none;">{{$e.Username}}</a></td>
865
+
<td style="color: #64748b;"><span title="{{$e.FailureMessage}}" style="cursor: help;">{{$e.FailureMessage}}</span></td>
···
entries = []storage.LeaderboardEntry{}
977
+
// Split entries into working and broken
978
+
var workingEntries []storage.LeaderboardEntry
979
+
var brokenEntries []storage.LeaderboardEntry
980
+
for _, e := range entries {
982
+
brokenEntries = append(brokenEntries, e)
984
+
workingEntries = append(workingEntries, e)
// Get matches for bracket
matches, err := storage.GetAllMatches()
···
886
-
Entries []storage.LeaderboardEntry
887
-
Matches []storage.MatchResult
995
+
Entries []storage.LeaderboardEntry
996
+
BrokenEntries []storage.LeaderboardEntry
997
+
Matches []storage.MatchResult
894
-
TotalPlayers: len(entries),
895
-
TotalGames: calculateTotalGames(entries),
896
-
ServerURL: GetServerURL(),
1002
+
Entries: workingEntries,
1003
+
BrokenEntries: brokenEntries,
1005
+
TotalPlayers: len(workingEntries),
1006
+
TotalGames: calculateTotalGames(workingEntries),
1007
+
ServerURL: GetServerURL(),
if err := tmpl.Execute(w, data); err != nil {