Add mobile optimization and PDGA update date simulation
Mobile improvements: - Responsive table layout with hidden columns on mobile - Touch-friendly buttons and improved spacing - Consolidated information display for small screens - Mobile-specific CSS with media queries PDGA rating simulation: - Calculate next official PDGA update date (2nd Tuesday of each month) - Filter tournaments to only include rounds before next update - Simulate realistic rating predictions based on PDGA schedule - Account for rolling 12-month window and round expiration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+103
-15
@@ -7,9 +7,16 @@
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 40px;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
@@ -17,12 +24,28 @@
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 15px;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
@@ -57,12 +80,60 @@
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
table {
|
||||
font-size: 12px;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
/* Hide less important columns on mobile */
|
||||
.mobile-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show mobile-only elements only on mobile */
|
||||
.mobile-only {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Make player names more prominent on mobile */
|
||||
.player-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide mobile-only elements on desktop */
|
||||
.mobile-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Adjust colspan for mobile - fewer visible columns */
|
||||
.expanded-cell {
|
||||
/* Mobile shows: Player Name, Rating, Action = 3 columns */
|
||||
/* But we need to account for the fact that mobile-hide columns still exist in DOM */
|
||||
}
|
||||
|
||||
/* Better mobile chart sizing */
|
||||
.chart-container {
|
||||
height: 250px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold;
|
||||
@@ -148,6 +219,17 @@
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
min-height: 32px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.calc-btn {
|
||||
padding: 8px 12px;
|
||||
font-size: 11px;
|
||||
min-height: 36px;
|
||||
min-width: 70px;
|
||||
}
|
||||
}
|
||||
.calc-btn:hover {
|
||||
background-color: #138496;
|
||||
@@ -245,13 +327,13 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th class="mobile-hide">Rank</th>
|
||||
<th>Player Name</th>
|
||||
<th>PDGA #</th>
|
||||
<th>Current Rating</th>
|
||||
<th>Rating Change</th>
|
||||
<th>Predicted Rating</th>
|
||||
<th>Difference</th>
|
||||
<th class="mobile-hide">PDGA #</th>
|
||||
<th>Rating</th>
|
||||
<th class="mobile-hide">Change</th>
|
||||
<th class="mobile-hide">Predicted</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -270,21 +352,27 @@
|
||||
|
||||
tableHTML += `
|
||||
<tr id="row-${player.pdgaNumber}" class="expandable-row" onclick="togglePlayerHistory(${player.pdgaNumber})">
|
||||
<td>${index + 1}</td>
|
||||
<td><a href="https://www.pdga.com/player/${player.pdgaNumber}" target="_blank" onclick="event.stopPropagation()">${player.name}</a></td>
|
||||
<td class="pdga-number">#${player.pdgaNumber}</td>
|
||||
<td class="rating">${player.rating || 'N/A'}</td>
|
||||
<td class="rating-change ${ratingChangeClass}">${ratingChangeText}</td>
|
||||
<td class="predicted-rating" id="predicted-${player.pdgaNumber}">
|
||||
<td class="mobile-hide">${index + 1}</td>
|
||||
<td class="player-name">
|
||||
<a href="https://www.pdga.com/player/${player.pdgaNumber}" target="_blank" onclick="event.stopPropagation()">${player.name}</a>
|
||||
<div class="mobile-only pdga-number" style="font-size: 11px; color: #999; margin-top: 2px;">PDGA #${player.pdgaNumber}</div>
|
||||
</td>
|
||||
<td class="pdga-number mobile-hide">#${player.pdgaNumber}</td>
|
||||
<td class="rating">
|
||||
${player.rating || 'N/A'}
|
||||
<div class="mobile-only rating-change ${ratingChangeClass}" style="font-size: 11px; margin-top: 2px;">${ratingChangeText}</div>
|
||||
</td>
|
||||
<td class="rating-change ${ratingChangeClass} mobile-hide">${ratingChangeText}</td>
|
||||
<td class="predicted-rating mobile-hide" id="predicted-${player.pdgaNumber}">
|
||||
${player.predictedRating || 'N/A'}
|
||||
</td>
|
||||
<td class="difference ${diffClass}" id="diff-${player.pdgaNumber}">
|
||||
${difference ? diffText :
|
||||
`<button class="calc-btn" onclick="event.stopPropagation(); calculatePredictedRating(${player.pdgaNumber})">Predict Rating</button>`}
|
||||
`<button class="calc-btn" onclick="event.stopPropagation(); calculatePredictedRating(${player.pdgaNumber})">Predict</button>`}
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="history-${player.pdgaNumber}" class="expanded-content">
|
||||
<td colspan="7">
|
||||
<td colspan="7" class="expanded-cell">
|
||||
<div class="chart-title">Rating History for ${player.name}</div>
|
||||
<div class="chart-container" id="chart-${player.pdgaNumber}" style="position: relative;">
|
||||
<div class="loading-chart">Click to load rating history...</div>
|
||||
|
||||
@@ -165,7 +165,11 @@ async function getPlayerCompetitionRatings(browser, pdgaNumber) {
|
||||
const url = `https://www.pdga.com/player/${pdgaNumber}`;
|
||||
await page.goto(url, { waitUntil: 'networkidle2' });
|
||||
|
||||
const tournamentUrls = await page.evaluate(() => {
|
||||
// Calculate the next PDGA update date to filter tournaments
|
||||
const nextUpdateDate = getNextPDGAUpdateDate();
|
||||
|
||||
const tournamentUrls = await page.evaluate((nextUpdateTimestamp) => {
|
||||
const nextUpdateDate = new Date(nextUpdateTimestamp);
|
||||
const tables = document.querySelectorAll('table[id*="player-results"]');
|
||||
const urls = [];
|
||||
|
||||
@@ -185,7 +189,7 @@ async function getPlayerCompetitionRatings(browser, pdgaNumber) {
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||
|
||||
if (date > oneYearAgo) {
|
||||
if (date > oneYearAgo && date < nextUpdateDate) {
|
||||
const href = tournamentCell.getAttribute('href');
|
||||
if (href) {
|
||||
urls.push({
|
||||
@@ -200,7 +204,7 @@ async function getPlayerCompetitionRatings(browser, pdgaNumber) {
|
||||
});
|
||||
|
||||
return urls.slice(0, 8); // Reduce number of tournaments to scrape
|
||||
});
|
||||
}, nextUpdateDate.getTime());
|
||||
|
||||
console.log(`Found ${tournamentUrls.length} recent tournaments for PDGA ${pdgaNumber}`);
|
||||
|
||||
@@ -287,11 +291,55 @@ function parseDate(dateStr) {
|
||||
return new Date(dateStr);
|
||||
}
|
||||
|
||||
function getNextPDGAUpdateDate() {
|
||||
const today = new Date();
|
||||
const currentMonth = today.getMonth();
|
||||
const currentYear = today.getFullYear();
|
||||
|
||||
// Calculate 2nd Tuesday of current month
|
||||
const firstDayOfMonth = new Date(currentYear, currentMonth, 1);
|
||||
const firstTuesday = new Date(firstDayOfMonth);
|
||||
|
||||
// Find first Tuesday (day 2 = Tuesday, 0 = Sunday)
|
||||
const daysUntilTuesday = (2 - firstDayOfMonth.getDay() + 7) % 7;
|
||||
firstTuesday.setDate(1 + daysUntilTuesday);
|
||||
|
||||
// Second Tuesday is 7 days after first Tuesday
|
||||
const secondTuesday = new Date(firstTuesday);
|
||||
secondTuesday.setDate(firstTuesday.getDate() + 7);
|
||||
|
||||
// If today is before or on the 2nd Tuesday of this month, use this month's date
|
||||
// Otherwise, use next month's 2nd Tuesday
|
||||
if (today <= secondTuesday) {
|
||||
return secondTuesday;
|
||||
} else {
|
||||
// Calculate 2nd Tuesday of next month
|
||||
const nextMonth = currentMonth === 11 ? 0 : currentMonth + 1;
|
||||
const nextYear = currentMonth === 11 ? currentYear + 1 : currentYear;
|
||||
|
||||
const firstDayNextMonth = new Date(nextYear, nextMonth, 1);
|
||||
const firstTuesdayNext = new Date(firstDayNextMonth);
|
||||
|
||||
const daysUntilTuesdayNext = (2 - firstDayNextMonth.getDay() + 7) % 7;
|
||||
firstTuesdayNext.setDate(1 + daysUntilTuesdayNext);
|
||||
|
||||
const secondTuesdayNext = new Date(firstTuesdayNext);
|
||||
secondTuesdayNext.setDate(firstTuesdayNext.getDate() + 7);
|
||||
|
||||
return secondTuesdayNext;
|
||||
}
|
||||
}
|
||||
|
||||
function calculatePredictedRating(roundRatings) {
|
||||
if (!roundRatings || roundRatings.length === 0) return 0;
|
||||
|
||||
// Sort by date (most recent first) and extract ratings
|
||||
// Get the next PDGA rating update date - only include rounds before this date
|
||||
const nextUpdateDate = getNextPDGAUpdateDate();
|
||||
console.log(`Next PDGA update date: ${nextUpdateDate.toDateString()}`);
|
||||
|
||||
// Filter rounds to only include those before the next update date, then sort by date (most recent first) and extract ratings
|
||||
const sortedRatings = roundRatings
|
||||
.filter(r => r.date < nextUpdateDate) // Only include rounds before next update
|
||||
.sort((a, b) => b.date - a.date)
|
||||
.map(r => r.rating)
|
||||
.filter(r => r > 0);
|
||||
|
||||
Reference in New Issue
Block a user