feat: auto LLM feedback runner + problem link + 5xx retry

- Add SubmissionFeedbackRunner: async background queue for auto LLM feedback
- Enqueue feedback generation after each submission in submitProblem()
- Register runner in main.cc with CSP_FEEDBACK_AUTO_RUN env var
- Add problem_title to GET /api/v1/submissions/{id} response
- Frontend: clickable problem link on submission detail page
- Enhance LLM prompt with richer analysis dimensions
- Add 5xx/connection error retry (max 5 attempts) in Python LLM script

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
这个提交包含在:
cryptocommuniums-afk
2026-02-16 15:13:35 +08:00
父节点 bc2e085c70
当前提交 7860414ae5
修改 37 个文件,包含 312 行新增5343 行删除

查看文件

@@ -1,905 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1920, initial-scale=1.0">
<title>Minecraft Contest Page</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.6.0/remixicon.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
:root {
--mc-bg-dark: #1D1D1D;
--mc-obsidian: #141019;
--mc-stone: #757575;
--mc-wood: #8B6914; /* Oak wood plank approximation */
--mc-wood-dark: #5C4033;
--mc-grass: #7CB342;
--mc-grass-dark: #558B2F;
--mc-gold: #FFB300;
--mc-redstone: #E53935;
--mc-diamond: #40C4FF;
--mc-white: #FFFFFF;
--mc-text-shadow: 2px 2px 0px #000;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
font-family: 'Press Start 2P', cursive;
background-color: #121212;
background-image:
linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a),
linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a);
background-size: 40px 40px;
background-position: 0 0, 20px 20px;
color: var(--mc-white);
width: 1920px;
margin: 0 auto;
overflow-x: hidden;
font-size: 14px;
line-height: 1.5;
}
/* --- Utilities --- */
.pixel-border {
box-shadow:
-4px 0 0 0 black,
4px 0 0 0 black,
0 -4px 0 0 black,
0 4px 0 0 black,
inset -4px -4px 0 0 rgba(0,0,0,0.5),
inset 4px 4px 0 0 rgba(255,255,255,0.2);
border: 4px solid transparent;
}
.text-shadow {
text-shadow: var(--mc-text-shadow);
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
/* --- Navigation --- */
.navbar {
height: 80px;
background-color: #333;
border-bottom: 4px solid #000;
display: flex;
align-items: center;
padding: 0 40px;
justify-content: space-between;
}
.logo {
font-size: 24px;
color: var(--mc-grass);
text-transform: uppercase;
}
.nav-links a {
color: #ccc;
text-decoration: none;
margin-left: 30px;
padding: 10px;
transition: color 0.2s;
}
.nav-links a.active {
color: var(--mc-gold);
text-shadow: 2px 2px 0 #5C4033;
}
/* --- Banner --- */
.banner {
height: 350px;
width: 100%;
position: relative;
background: linear-gradient(180deg, #2a0e36 0%, #46142e 100%); /* Nether-ish */
background-image: url('https://images.unsplash.com/photo-1628151015968-3a4429e9ef04?q=80&w=2072&auto=format&fit=crop'); /* Pixel art background placeholder */
background-size: cover;
background-position: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-bottom: 6px solid #000;
overflow: hidden;
}
.banner-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
z-index: 1;
}
.banner-content {
position: relative;
z-index: 2;
text-align: center;
width: 100%;
max-width: 1200px;
}
.live-badge {
background-color: var(--mc-redstone);
color: white;
padding: 8px 16px;
display: inline-block;
font-size: 16px;
margin-bottom: 20px;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(229, 57, 53, 0.7); }
70% { transform: scale(1.05); box-shadow: 0 0 0 10px rgba(229, 57, 53, 0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(229, 57, 53, 0); }
}
.contest-title {
font-size: 42px;
color: var(--mc-gold);
margin-bottom: 20px;
-webkit-text-stroke: 2px #5C4033;
text-shadow: 4px 4px 0 #000;
}
.countdown {
font-size: 36px;
color: #fff;
margin-bottom: 30px;
letter-spacing: 4px;
text-shadow: 3px 3px 0 #000;
}
.join-btn {
background-color: var(--mc-grass);
color: white;
border: none;
padding: 20px 40px;
font-size: 20px;
cursor: pointer;
font-family: 'Press Start 2P', cursive;
transition: transform 0.1s, background-color 0.2s;
position: relative;
box-shadow: 0 6px 0 #558B2F, 0 10px 10px rgba(0,0,0,0.3);
text-transform: uppercase;
}
.join-btn:hover {
background-color: #8BC34A;
transform: translateY(-2px);
box-shadow: 0 8px 0 #558B2F, 0 12px 12px rgba(0,0,0,0.3);
}
.join-btn:active {
transform: translateY(4px);
box-shadow: 0 2px 0 #558B2F, 0 4px 4px rgba(0,0,0,0.3);
}
.participants-count {
margin-top: 20px;
font-size: 14px;
color: #ccc;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.player-head {
width: 24px;
height: 24px;
background: #555;
display: inline-block;
image-rendering: pixelated;
}
/* --- Main Layout --- */
.container {
display: flex;
width: 100%;
padding: 40px;
gap: 40px;
max-width: 1800px;
margin: 0 auto;
}
.left-col {
flex: 0 0 70%;
}
.right-col {
flex: 0 0 30%;
}
/* --- Tab Nav --- */
.tabs {
display: flex;
margin-bottom: 20px;
gap: 10px;
}
.tab {
background-color: #555;
padding: 15px 30px;
color: #aaa;
cursor: pointer;
border-bottom: none;
position: relative;
top: 4px;
transition: all 0.2s;
}
.tab.active {
background-color: var(--mc-wood);
color: #fff;
top: 0;
padding-bottom: 19px;
text-shadow: 2px 2px 0 #000;
box-shadow:
-4px 0 0 0 black,
4px 0 0 0 black,
0 -4px 0 0 black;
}
/* --- Wood Panel Style --- */
.wood-panel {
background-color: var(--mc-wood);
border: 4px solid #000;
padding: 20px;
margin-bottom: 30px;
box-shadow: inset 0 0 0 4px rgba(255,255,255,0.1), 8px 8px 0 rgba(0,0,0,0.5);
position: relative;
}
.wood-texture {
/* Simulating wood grain with linear gradients */
background-image:
linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px),
linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px);
background-size: 20px 20px;
}
/* --- Contest Cards --- */
.contest-card {
background-color: #6D4C41; /* Darker wood */
margin-bottom: 20px;
padding: 20px;
display: flex;
align-items: center;
justify-content: space-between;
transition: transform 0.2s;
position: relative;
border: 4px solid #3E2723;
}
.contest-card:hover {
transform: translateX(10px);
background-color: #795548;
}
.card-icon {
width: 60px;
height: 60px;
background-color: #3E2723;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
color: var(--mc-gold);
border: 4px solid #000;
margin-right: 20px;
}
.card-info h3 {
color: #fff;
margin-bottom: 10px;
font-size: 18px;
text-shadow: 2px 2px 0 #000;
}
.card-details {
font-size: 12px;
color: #D7CCC8;
display: flex;
gap: 15px;
}
.stars { color: var(--mc-gold); }
.card-action .btn-small {
background-color: var(--mc-stone);
border: 2px solid #000;
padding: 10px 20px;
color: white;
cursor: pointer;
font-family: inherit;
font-size: 12px;
box-shadow: 0 4px 0 #424242;
}
.card-action .btn-small:hover {
background-color: #9E9E9E;
margin-top: -2px;
box-shadow: 0 6px 0 #424242;
}
/* --- Leaderboard --- */
.leaderboard-title {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.live-dot {
width: 12px;
height: 12px;
background-color: var(--mc-grass);
border-radius: 50%; /* Minecraft has no circles, but for indicator */
display: inline-block;
margin-right: 8px;
box-shadow: 0 0 10px var(--mc-grass);
animation: blink 1s infinite;
}
@keyframes blink { 50% { opacity: 0.5; } }
.leaderboard-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
}
.leaderboard-table th {
text-align: left;
padding: 15px;
color: #3E2723;
font-size: 14px;
border-bottom: 4px solid #3E2723;
}
.leaderboard-table td {
background-color: #5D4037;
padding: 15px;
color: #fff;
border-top: 4px solid #3E2723;
border-bottom: 4px solid #3E2723;
}
.leaderboard-table tr td:first-child { border-left: 4px solid #3E2723; }
.leaderboard-table tr td:last-child { border-right: 4px solid #3E2723; }
.rank-1 td { background-color: #FFECB3; color: #5D4037; border-color: #FFB300 !important; }
.rank-2 td { background-color: #F5F5F5; color: #5D4037; border-color: #BDBDBD !important; }
.rank-3 td { background-color: #D7CCC8; color: #5D4037; border-color: #8D6E63 !important; }
.current-user td {
background-color: #DCEDC8;
color: #33691E;
border-color: #7CB342 !important;
}
.medal-icon { margin-right: 5px; }
/* --- Right Column --- */
.info-card {
background-color: #424242;
padding: 20px;
margin-bottom: 20px;
border: 4px solid #000;
box-shadow: 8px 8px 0 rgba(0,0,0,0.5);
}
.info-header {
font-size: 18px;
margin-bottom: 20px;
color: var(--mc-gold);
text-shadow: 2px 2px 0 #000;
border-bottom: 4px solid #000;
padding-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.stat-row {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
font-size: 13px;
}
.stat-val { color: var(--mc-diamond); }
.medals-display {
display: flex;
gap: 15px;
margin-top: 15px;
justify-content: center;
background: #212121;
padding: 10px;
border: 2px solid #000;
}
.timeline-item {
display: flex;
margin-bottom: 15px;
position: relative;
}
.timeline-line {
position: absolute;
left: 7px;
top: 20px;
bottom: -20px;
width: 2px;
background: #666;
z-index: 0;
}
.timeline-item:last-child .timeline-line { display: none; }
.timeline-dot {
width: 16px;
height: 16px;
background: var(--mc-gold);
border: 2px solid #000;
z-index: 1;
margin-right: 15px;
margin-top: 4px;
}
.timeline-content {
font-size: 12px;
}
.timeline-date { color: #888; font-size: 10px; margin-bottom: 4px; }
.timeline-rank { color: #fff; }
.collapsible-content {
max-height: 500px; /* arbitrary large */
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.collapsed .collapsible-content {
max-height: 0;
}
.toggle-btn {
cursor: pointer;
font-size: 20px;
}
/* --- Animations --- */
@keyframes shine {
0% { background-position: -100px; }
40%, 100% { background-position: 140px; }
}
.rank-1 {
position: relative;
overflow: hidden;
}
/* Light ray effect using psuedo elements not easy on tr, applied to td */
.rank-1 td {
background: linear-gradient(120deg, #FFECB3 0%, #FFECB3 40%, #FFF9C4 50%, #FFECB3 60%, #FFECB3 100%);
background-size: 200% 100%;
animation: shine-gold 3s infinite linear;
}
@keyframes shine-gold {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Responsive */
@media (max-width: 768px) {
.container { flex-direction: column; }
.left-col, .right-col { flex: 1 1 100%; }
}
</style>
</head>
<body>
<nav class="navbar pixel-border">
<div class="logo text-shadow"><i class="ri-sword-fill"></i> ALGO CRAFT</div>
<div class="nav-links">
<a href="#" class="active">CONTESTS</a>
<a href="#">PROBLEMS</a>
<a href="#">DISCUSS</a>
<a href="#">STORE</a>
</div>
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:32px; height:32px; background:#ddd; border:2px solid #000;">
<img src="https://api.dicebear.com/7.x/pixel-art/svg?seed=Felix" alt="User" style="width:100%; height:100%;">
</div>
<span>STEVE_DEV</span>
</div>
</nav>
<div class="banner">
<div class="banner-overlay"></div>
<div class="banner-content">
<div class="live-badge pixel-border">LIVE NOW</div>
<h1 class="contest-title">WEEKLY ALGO CHALLENGE #42</h1>
<div class="countdown" id="countdown">02:45:30</div>
<button class="join-btn pixel-border">
JOIN CONTEST
</button>
<div class="participants-count">
<div class="player-head"></div>
<div class="player-head"></div>
<div class="player-head"></div>
<span>1,234 Crafters Online</span>
</div>
</div>
</div>
<div class="container">
<!-- Left Column -->
<div class="left-col">
<div class="tabs">
<div class="tab active pixel-border" onclick="switchTab('ongoing')">ONGOING</div>
<div class="tab pixel-border" onclick="switchTab('upcoming')">UPCOMING</div>
<div class="tab pixel-border" onclick="switchTab('finished')">FINISHED</div>
</div>
<div class="wood-panel pixel-border wood-texture" id="contest-list">
<!-- Contest Cards -->
<div class="contest-card pixel-border">
<div class="flex-center">
<div class="card-icon pixel-border"><i class="ri-trophy-fill"></i></div>
<div class="card-info">
<h3>Weekly Challenge #42</h3>
<div class="card-details">
<span><i class="ri-time-line"></i> Ends in 2h</span>
<span><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i></span>
<span style="color:#4DB6AC">500 XP</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">ENTER</button>
</div>
</div>
<div class="contest-card pixel-border">
<div class="flex-center">
<div class="card-icon pixel-border"><i class="ri-sword-fill" style="color:#CFD8DC"></i></div>
<div class="card-info">
<h3>Bi-Weekly Rumble #15</h3>
<div class="card-details">
<span><i class="ri-calendar-line"></i> Sat, 14:00</span>
<span><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-line"></i></span>
<span style="color:#4DB6AC">350 XP</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">REGISTER</button>
</div>
</div>
<div class="contest-card pixel-border">
<div class="flex-center">
<div class="card-icon pixel-border"><i class="ri-vip-diamond-fill" style="color:var(--mc-diamond)"></i></div>
<div class="card-info">
<h3>Diamond League Qualifiers</h3>
<div class="card-details">
<span><i class="ri-calendar-line"></i> Sun, 10:00</span>
<span><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i><i class="ri-star-fill stars"></i></span>
<span style="color:#4DB6AC">1000 XP + BADGE</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">REGISTER</button>
</div>
</div>
</div>
<!-- Leaderboard -->
<div class="wood-panel pixel-border wood-texture">
<div class="leaderboard-title">
<h2 class="text-shadow" style="color:#3E2723">LEADERBOARD</h2>
<div style="font-size: 12px; color: #3E2723; display: flex; align-items: center;">
<span class="live-dot"></span> UPDATING LIVE
</div>
</div>
<table class="leaderboard-table">
<thead>
<tr>
<th>#</th>
<th>PLAYER</th>
<th>SCORE</th>
<th>TIME</th>
<th>STATUS</th>
</tr>
</thead>
<tbody id="leaderboard-body">
<!-- JS will populate -->
<tr class="rank-1 pixel-border">
<td><i class="ri-vip-crown-fill" style="color:#F57F17"></i> 1</td>
<td>Notch_Real</td>
<td>400</td>
<td>00:45:12</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr class="rank-2">
<td><i class="ri-medal-fill" style="color:#757575"></i> 2</td>
<td>Alex_Pro</td>
<td>380</td>
<td>00:52:30</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr class="rank-3">
<td><i class="ri-medal-fill" style="color:#8D6E63"></i> 3</td>
<td>CreeperAwMan</td>
<td>350</td>
<td>01:05:00</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr>
<td>4</td>
<td>Enderman_tp</td>
<td>320</td>
<td>01:10:22</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr>
<td>5</td>
<td>RedstoneEng</td>
<td>300</td>
<td>01:15:45</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr>
<td>6</td>
<td>Miner64</td>
<td>280</td>
<td>01:20:10</td>
<td><span style="color:green">AC</span></td>
</tr>
<tr class="current-user">
<td>7</td>
<td>STEVE_DEV (YOU)</td>
<td>250</td>
<td>01:30:00</td>
<td><span style="color:orange">WA (1)</span></td>
</tr>
<tr>
<td>8</td>
<td>ZombieBoi</td>
<td>200</td>
<td>01:35:12</td>
<td><span style="color:green">AC</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Right Column -->
<div class="right-col">
<!-- Stats Card -->
<div class="info-card pixel-border">
<div class="info-header">
<span>MY STATS</span>
<i class="ri-bar-chart-fill"></i>
</div>
<div class="stat-row">
<span>Participated:</span>
<span class="stat-val">24</span>
</div>
<div class="stat-row">
<span>Best Rank:</span>
<span class="stat-val">#3 🥉</span>
</div>
<div class="stat-row">
<span>Total Points:</span>
<span class="stat-val">1,250</span>
</div>
<div class="stat-row">
<span>Rating:</span>
<span class="stat-val" style="color:var(--mc-gold)">1650 (Diamond II)</span>
</div>
<div class="medals-display pixel-border">
<div style="text-align:center">
<i class="ri-medal-fill" style="color:var(--mc-gold); font-size:20px"></i>
<div style="font-size:10px">2</div>
</div>
<div style="text-align:center">
<i class="ri-medal-fill" style="color:#BDBDBD; font-size:20px"></i>
<div style="font-size:10px">5</div>
</div>
<div style="text-align:center">
<i class="ri-medal-fill" style="color:#8D6E63; font-size:20px"></i>
<div style="font-size:10px">8</div>
</div>
</div>
</div>
<!-- Rules Card -->
<div class="info-card pixel-border" id="rules-card">
<div class="info-header">
<span>RULES</span>
<i class="ri-book-open-fill toggle-btn" onclick="toggleRules()"></i>
</div>
<div class="collapsible-content" id="rules-content">
<ul style="padding-left: 20px; font-size: 12px; line-height: 1.8; color:#ccc;">
<li>Duration: 3 Hours</li>
<li>Penalty: +5 mins per WA</li>
<li>Languages: Java, C++, Python</li>
<li>Plagiarism check is ACTIVE</li>
<li>Do not break obsidian blocks</li>
</ul>
</div>
</div>
<!-- Past Results -->
<div class="info-card pixel-border">
<div class="info-header">
<span>HISTORY</span>
<i class="ri-history-line"></i>
</div>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="timeline-date">2023-10-15</div>
<div class="timeline-rank">Weekly #41 - Rank #15</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot" style="background:#BDBDBD"></div>
<div class="timeline-content">
<div class="timeline-date">2023-10-08</div>
<div class="timeline-rank">Weekly #40 - Rank #2 🥈</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot" style="background:#8D6E63"></div>
<div class="timeline-content">
<div class="timeline-date">2023-10-01</div>
<div class="timeline-rank">Weekly #39 - Rank #3 🥉</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-line"></div>
<div class="timeline-dot" style="background:#555"></div>
<div class="timeline-content">
<div class="timeline-date">2023-09-24</div>
<div class="timeline-rank">Weekly #38 - Rank #45</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// --- Countdown Timer ---
let totalSeconds = 2 * 3600 + 45 * 60 + 30; // 2h 45m 30s
const countdownEl = document.getElementById('countdown');
function updateCountdown() {
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
countdownEl.textContent =
`${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
if (totalSeconds > 0) {
totalSeconds--;
}
}
setInterval(updateCountdown, 1000);
updateCountdown();
// --- Tabs Functionality ---
function switchTab(tabName) {
const tabs = document.querySelectorAll('.tab');
tabs.forEach(t => t.classList.remove('active'));
// Find the clicked tab (simple logic for this demo)
event.target.classList.add('active');
// In a real app, this would filter the card list
const list = document.getElementById('contest-list');
list.style.opacity = '0.5';
setTimeout(() => {
list.style.opacity = '1';
// Mock content change
if(tabName === 'finished') {
// Just a visual cue that something changed
list.innerHTML = `<div style="text-align:center; padding:40px; color:#aaa">Loading Archive...</div>`;
setTimeout(() => {
list.innerHTML = `
<div class="contest-card pixel-border" style="opacity:0.7">
<div class="flex-center">
<div class="card-icon pixel-border" style="background:#555; color:#aaa"><i class="ri-trophy-line"></i></div>
<div class="card-info">
<h3 style="color:#aaa">Weekly Challenge #41</h3>
<div class="card-details">
<span>Ended: 2 days ago</span>
<span>Winner: Herobrine</span>
</div>
</div>
</div>
<div class="card-action">
<button class="btn-small">VIEW</button>
</div>
</div>
`;
}, 500);
} else if(tabName === 'ongoing') {
// Reset to initial HTML (simplified)
location.reload();
}
}, 200);
}
// --- Toggle Rules ---
function toggleRules() {
const card = document.getElementById('rules-card');
card.classList.toggle('collapsed');
}
// --- Real-time Leaderboard Simulation ---
function simulateLeaderboard() {
const rows = document.querySelectorAll('#leaderboard-body tr:not(.current-user)');
// Randomly swap two rows to simulate rank changes
const idx1 = Math.floor(Math.random() * 3); // Only top 3 change mostly
const idx2 = Math.floor(Math.random() * 3);
if (idx1 !== idx2) {
// Flash effect
rows[idx1].style.backgroundColor = '#fff';
setTimeout(() => {
rows[idx1].style.backgroundColor = ''; // revert to CSS class style
}, 200);
}
}
setInterval(simulateLeaderboard, 10000);
// --- LocalStorage Logic ---
// Save visit timestamp
localStorage.setItem('last_visit_mc_contest', new Date().toISOString());
// --- Firework/Hover Effects for Top 3 ---
const topRanks = document.querySelectorAll('.rank-1, .rank-2, .rank-3');
topRanks.forEach(row => {
row.addEventListener('mouseenter', () => {
// Add a subtle scale effect
row.style.transform = "scale(1.02)";
row.style.transition = "transform 0.2s";
row.style.zIndex = "10";
row.style.boxShadow = "0 0 15px rgba(255, 215, 0, 0.5)";
});
row.addEventListener('mouseleave', () => {
row.style.transform = "scale(1)";
row.style.boxShadow = "none";
row.style.zIndex = "1";
});
});
</script>
</body>
</html>