refactor: design-fidelity pass on players page

This commit is contained in:
Samuel Enocsson
2026-05-21 15:15:29 +02:00
parent 686d7ca00c
commit 16d375ae10
7 changed files with 463 additions and 189 deletions
+40 -28
View File
@@ -1,21 +1,27 @@
<% var body = `
<!-- Add Player Section -->
<div class="card-section">
<h3>Add Yourself to Tracked Players</h3>
<div class="card-section-form">
<input
type="number"
id="pdga-number-input"
class="input"
placeholder="Enter your PDGA number"
min="1"
style="width: 240px;"
/>
<button class="btn btn-add" onclick="searchAndAddPlayer()">
<i class="fas fa-user-plus"></i> Add Player
<!-- Add Player Card -->
<form class="add-bar" onsubmit="searchAndAddPlayer(event)">
<div class="add-bar-label">
<span class="add-bar-kicker">Track a player</span>
<span class="add-bar-hint">Add a PDGA number to start following their rating.</span>
</div>
<div class="add-bar-controls">
<div class="input-wrap">
<span class="input-prefix">#</span>
<input
type="text"
id="pdga-number-input"
inputmode="numeric"
placeholder="277890"
aria-label="PDGA number"
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
/>
</div>
<button type="submit" class="btn-primary">
<i class="fas fa-user-plus"></i> Add player
</button>
</div>
</div>
</form>
<!-- KPI Summary Tiles -->
<section class="kpi-strip" aria-label="Tracker overview">
@@ -53,19 +59,25 @@
</article>
</section>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 16px;">
<a href="#" onclick="clearCache(); return false;" style="color: var(--text-muted); font-size: 12px; text-decoration: none; opacity: 0.4;" title="Clear cache"><i class="fas fa-cog"></i></a>
<!-- Players Table Card -->
<div class="table-card">
<div class="table-toolbar">
<span class="kicker">TRACKED PLAYERS</span>
<div class="toolbar-actions">
<button id="trendchart-toggle" class="pill-toggle" type="button" aria-pressed="false">
<svg class="pill-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 17l5-6 4 4 4-7 5 6" />
</svg>
<span class="pill-label">Trend chart</span>
<span class="pill-dot"></span>
</button>
</div>
</div>
<div id="ratings-table" hx-get="/partials/ratings-table" hx-trigger="load"></div>
</div>
<div class="table-toolbar">
<button id="trendchart-toggle" class="pill-toggle" type="button" aria-pressed="false">
<svg class="pill-icon" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<polyline points="1,9 4,5 7,7 11,2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="pill-label">Trend chart</span>
<span class="pill-dot"></span>
</button>
</div>
<div id="ratings-table" hx-get="/partials/ratings-table" hx-trigger="load"></div>
<!-- Footnote -->
<p class="footnote">Unofficial PDGA rating tracker. Ratings scraped from pdga.com on each refresh.</p>
`; %>
<% var modals = `
@@ -100,4 +112,4 @@
initScript: 'setupTooltipsAfterSwap();',
body: body,
modals: modals
}) %>
}) %>
+5 -4
View File
@@ -4,7 +4,8 @@
extraClass {string} — optional additional CSS class (e.g. 'delta-predicted-pill')
*/%>
<% if (typeof value !== 'undefined' && value != null) {
const _cls = value > 0 ? 'up' : value < 0 ? 'down' : 'flat';
const _text = value > 0 ? '+' + value : value.toString();
const _xtra = (typeof extraClass !== 'undefined' && extraClass) ? ' ' + extraClass : '';
%><span class="delta-pill <%= _cls %><%= _xtra %>"><%= _text %></span><% } %>
const _cls = value > 0 ? 'up' : value < 0 ? 'down' : 'flat';
const _glyph = value > 0 ? '' : value < 0 ? '▼' : '';
const _num = value > 0 ? '+' + value : value.toString();
const _xtra = (typeof extraClass !== 'undefined' && extraClass) ? ' ' + extraClass : '';
%><span class="delta-pill <%= _cls %><%= _xtra %>"><span class="delta-glyph"><%= _glyph %></span><span class="delta-num"><%= _num %></span></span><% } %>
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300..700;1,300..700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300..800;1,300..800&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<link rel="stylesheet" href="/css/shared.css">
<% if (typeof cssFiles !== 'undefined') { %>
+39 -31
View File
@@ -21,7 +21,7 @@ function renderSparkline(values) {
var last = pts[pts.length - 1];
var areaPath = linePath + ' L ' + last.x + ' ' + h + ' L 0 ' + h + ' Z';
return '<svg width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '" style="display:block;overflow:visible">' +
return '<svg width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '" class="spark" aria-hidden="true">' +
'<path d="' + areaPath + '" style="fill:var(--accent);fill-opacity:0.10"/>' +
'<path d="' + linePath + '" style="stroke:var(--accent);stroke-width:1.5;fill:none;stroke-linejoin:round;stroke-linecap:round"/>' +
'<circle cx="' + last.x + '" cy="' + last.y + '" r="2.5" style="fill:var(--accent)"/>' +
@@ -29,16 +29,16 @@ function renderSparkline(values) {
}
%>
<% if (ratings.length === 0) { %>
<p style="text-align: center; color: var(--text-muted); padding: 40px 0;">No ratings found.</p>
<p style="text-align: center; color: var(--ink-3); padding: 40px 0;">No players tracked yet.</p>
<% } else { %>
<table>
<thead>
<tr>
<th class="mobile-hide">Rank</th>
<th>Player Name</th>
<th class="mobile-hide">PDGA #</th>
<th>Rating</th>
<th class="mobile-hide">Predicted<span class="th-hint">next official update</span></th>
<th class="col-rank">#</th>
<th class="col-player">Player</th>
<th class="col-rating">Rating<span class="th-hint">+ Δ since last update</span></th>
<th class="col-predicted">Predicted<span class="th-hint">+ gap from today</span></th>
<th class="col-actions"></th>
</tr>
</thead>
<tbody>
@@ -46,41 +46,49 @@ function renderSparkline(values) {
const sparklineSvg = renderSparkline(player.monthlyHistory || []);
%>
<tr id="row-<%= player.pdgaNumber %>" class="expandable-row" tabindex="0" onclick="togglePlayerHistory(<%= 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; margin-top: 2px;">PDGA #<%= player.pdgaNumber %></div>
<td class="col-rank">
<span class="rank-num mono"><%= index + 1 %></span>
</td>
<td class="pdga-number mobile-hide">#<%= player.pdgaNumber %></td>
<td class="rating">
<div class="refresh-section">
<span class="rating-value" data-rating="<%= player.rating || '' %>" data-stddev="<%= player.stdDev || '' %>" data-pdga="<%= player.pdgaNumber %>" style="cursor: help;"><%- player.rating || '<span style="color: var(--text-muted); font-style: italic;">Click refresh</span>' %></span>
<i class="fas fa-sync-alt refresh-icon" onclick="refreshPlayer(<%= player.pdgaNumber %>)" title="Refresh player data"></i>
<td class="col-player">
<div class="cell-name">
<span class="player-name"><a href="https://www.pdga.com/player/<%= player.pdgaNumber %>" target="_blank" onclick="event.stopPropagation()"><%= player.name %></a></span>
<span class="pdga-num mono">#<%= player.pdgaNumber %></span>
</div>
<%- include('delta-pill', { value: player.ratingChange }) %>
<% if (sparklineSvg) { %>
<span class="sparkline"><%- sparklineSvg %></span>
</td>
<td class="col-rating cell-rating">
<% if (player.rating) { %>
<div class="rating-stack">
<span class="rating-value rating-big mono" data-rating="<%= player.rating || '' %>" data-stddev="<%= player.stdDev || '' %>" data-pdga="<%= player.pdgaNumber %>"><%= player.rating %></span>
<%- include('delta-pill', { value: player.ratingChange }) %>
<% if (sparklineSvg) { %><span class="sparkline"><%- sparklineSvg %></span><% } %>
</div>
<% } else { %>
<span class="rating-pending">Click to load</span>
<% } %>
<div class="std-dev-tooltip" id="tooltip-rating-<%= player.pdgaNumber %>"></div>
</td>
<td class="predicted-rating mobile-hide" id="predicted-<%= player.pdgaNumber %>">
<div class="refresh-section">
<span class="predicted-value" data-stddev="<%= player.stdDev || '' %>" data-pdga="<%= player.pdgaNumber %>" style="cursor: help;"><%= player.predictedRating || 'N/A' %></span>
<i class="fas fa-question-circle debug-icon" onclick="showDebugInfo(<%= player.pdgaNumber %>)" title="Show calculation details" style="margin-left: 5px; color: var(--text-muted); cursor: pointer; opacity: 0.6;"></i>
<i class="fas fa-sync-alt refresh-icon" onclick="refreshRoundHistory(<%= player.pdgaNumber %>)" title="Refresh prediction data"></i>
<td class="col-predicted" id="predicted-<%= player.pdgaNumber %>">
<% if (player.predictedRating) { %>
<div class="pred-stack">
<span class="predicted-value pred-num mono" data-stddev="<%= player.stdDev || '' %>" data-pdga="<%= player.pdgaNumber %>"><%= player.predictedRating %></span>
<%- include('delta-pill', { value: player.deltaPredicted, extraClass: 'delta-predicted-pill' }) %>
</div>
<%- include('delta-pill', { value: player.deltaPredicted, extraClass: 'delta-predicted-pill' }) %>
<% } else { %>
<span class="rating-pending">—</span>
<% } %>
<div class="std-dev-tooltip" id="tooltip-stddev-<%= player.pdgaNumber %>"></div>
</td>
<td class="col-actions cell-actions" onclick="event.stopPropagation()">
<button class="icon-btn refresh-icon" onclick="refreshPlayer(<%= player.pdgaNumber %>)" title="Refresh player data" aria-label="Refresh">
<i class="fas fa-sync-alt"></i>
</button>
<button class="icon-btn icon-chev" onclick="togglePlayerHistory(<%= player.pdgaNumber %>)" title="Expand row" aria-label="Expand">
<i class="fas fa-chevron-down"></i>
</button>
</td>
</tr>
<tr id="history-<%= player.pdgaNumber %>" class="expanded-content">
<td colspan="5" class="expanded-cell">
<div class="chart-title">
<div class="refresh-section">
Rating History for <%= player.name %>
<i class="fas fa-sync-alt refresh-icon" onclick="refreshRatingHistory(<%= player.pdgaNumber %>)" title="Refresh rating history"></i>
</div>
</div>
<div id="history-content-<%= player.pdgaNumber %>">
<div class="loading-chart">Click to load rating history...</div>
</div>