-
0%
+
+
+
+
+
+
+
+
${kpis.tracked}
+
Tracked
+
${kpis.active} active
- Preparing to load ratings...
+
+
+
+
+
${kpis.avg ?? '—'}
+
Avg rating
+
across active players
+
+
+
+
+
+
${kpis.climbing}
+
Climbing
+
this month
+
+
+
+
+
+
${kpis.slipping}
+
Slipping
+
this month
+
+
+
+
+
+
-
+
+
+
`; %>
<% var modals = `
@@ -56,11 +106,10 @@
<%- include('../partials/layout', {
title: 'PDGA Ratings',
- heading: 'PDGA Player Ratings',
activePage: 'players',
cssFiles: ['players.css'],
- jsFiles: ['tooltips.js', 'chart.js', 'progress.js', 'players.js'],
+ jsFiles: ['tooltips.js', 'chart.js', 'players.js'],
initScript: 'setupTooltipsAfterSwap();',
body: body,
modals: modals
-}) %>
\ No newline at end of file
+}) %>
diff --git a/views/partials/delta-pill.ejs b/views/partials/delta-pill.ejs
new file mode 100644
index 0000000..0b1c367
--- /dev/null
+++ b/views/partials/delta-pill.ejs
@@ -0,0 +1,12 @@
+<%/* delta-pill.ejs — renders a Δ-pill span.
+ Locals:
+ value {number|null} — the delta value (null/undefined → flat pill with '—')
+ extraClass {string} — optional additional CSS class (e.g. 'delta-predicted-pill')
+*/%>
+<%
+const _isNull = (typeof value === 'undefined' || value == null);
+const _cls = _isNull ? 'flat' : value > 0 ? 'up' : value < 0 ? 'down' : 'flat';
+const _glyph = (_isNull || value === 0) ? '–' : value > 0 ? '▲' : '▼';
+const _num = _isNull ? '—' : value > 0 ? '+' + value : value.toString();
+const _xtra = (typeof extraClass !== 'undefined' && extraClass) ? ' ' + extraClass : '';
+%>
<%= _glyph %><%= _num %>
diff --git a/views/partials/layout.ejs b/views/partials/layout.ejs
index e2009a8..c759746 100644
--- a/views/partials/layout.ejs
+++ b/views/partials/layout.ejs
@@ -5,6 +5,9 @@
<%= title %>
+
+
+
<% if (typeof cssFiles !== 'undefined') { %>
@@ -14,15 +17,7 @@
<% } %>
-
+ <%- include('../partials/topbar', { activePage, lastRefresh, nextUpdate }) %>
<%- body %>
diff --git a/views/partials/player-history.ejs b/views/partials/player-history.ejs
index 9bb0edf..29af37b 100644
--- a/views/partials/player-history.ejs
+++ b/views/partials/player-history.ejs
@@ -1,12 +1,42 @@
-<% if (history && history.length > 0) { %>
-
-
-<% } else { %>
-
-
No rating history available
-
-<% } %>
+<%
+const hasPlayer = (typeof player !== 'undefined' && player);
+const chartPdgaNumber = hasPlayer ? player.pdgaNumber : pdgaNumber;
+%>
+
+ <% if (hasPlayer) { %>
+
+
+
+
- Current rating
+ - <%= player.rating ?? '—' %>
+
+
+
- Last month
+ - <%= player.lastMonthRating ?? '—' %>
+
+
+
- Change vs last month
+ - <%- include('delta-pill', { value: player.ratingChange }) %>
+
+
+
- Predicted next update
+ - <%= player.predictedRating ?? '—' %>
+
+
+
- Gap to predicted
+ - <%- include('delta-pill', { value: player.deltaPredicted, extraClass: 'delta-predicted-pill' }) %>
+
+
+
+
+ <% } %>
+
+ <% if (history && history.length > 0) { %>
+
+
+ <% } else { %>
+
No rating history available
+ <% } %>
+
+
diff --git a/views/partials/ratings-table.ejs b/views/partials/ratings-table.ejs
index 386d5bd..5fc0783 100644
--- a/views/partials/ratings-table.ejs
+++ b/views/partials/ratings-table.ejs
@@ -1,58 +1,94 @@
+<%
+function renderSparkline(values) {
+ if (!values || values.length < 2) return '';
+ var w = 96, h = 28;
+ var min = Math.min.apply(null, values);
+ var max = Math.max.apply(null, values);
+ var range = max - min || 1;
+ var xStep = w / (values.length - 1);
+
+ var pts = values.map(function(v, i) {
+ return {
+ x: (i * xStep).toFixed(1),
+ y: (((max - v) / range) * (h - 4) + 2).toFixed(1)
+ };
+ });
+
+ var linePath = pts.map(function(p, i) {
+ return (i === 0 ? 'M' : 'L') + ' ' + p.x + ' ' + p.y;
+ }).join(' ');
+
+ var last = pts[pts.length - 1];
+ var areaPath = linePath + ' L ' + last.x + ' ' + h + ' L 0 ' + h + ' Z';
+
+ return '
';
+}
+%>
<% if (ratings.length === 0) { %>
-
No ratings found.
+
No players tracked yet.
<% } else { %>
- | Rank |
- Player Name |
- PDGA # |
- Rating |
- Change |
- Predicted |
+ # |
+ Player |
+ Rating+ Δ since last update |
+ Predicted+ gap from today |
+ |
<% ratings.forEach(function(player, index) {
- var difference = player.predictedRating && player.rating ? player.predictedRating - player.rating : 0;
- var diffText = difference > 0 ? '+' + difference : difference.toString();
- var diffClass = difference > 0 ? 'positive' : difference < 0 ? 'negative' : 'neutral';
- var ratingChangeText = player.ratingChange ? (player.ratingChange > 0 ? '+' + player.ratingChange : player.ratingChange.toString()) : 'N/A';
- var ratingChangeClass = player.ratingChange > 0 ? 'positive' : player.ratingChange < 0 ? 'negative' : 'neutral';
+ const sparklineSvg = renderSparkline(player.monthlyHistory || []);
%>
-
- | <%= index + 1 %> |
-
- <%= player.name %>
- PDGA #<%= player.pdgaNumber %>
+ |
+ |
+ <%= index + 1 %>
|
- #<%= player.pdgaNumber %> |
-
-
- <%- player.rating || 'Click refresh' %>
-
+
+
- <%= ratingChangeText %>
+ |
+
+ <% if (player.rating) { %>
+
+ <%= player.rating %>
+ <%- include('delta-pill', { value: player.ratingChange }) %>
+ <% if (sparklineSvg) { %><%- sparklineSvg %><% } %>
+
+ <% } else { %>
+ Click to load
+ <% } %>
|
- <%= ratingChangeText %> |
-
-
- <%= player.predictedRating || 'N/A' %>
-
-
+
+ <% if (player.predictedRating) { %>
+
+ <%= player.predictedRating %>
+ <%- include('delta-pill', { value: player.deltaPredicted, extraClass: 'delta-predicted-pill' }) %>
+ <% } else { %>
+ —
+ <% } %>
|
+
+
+
+ |
| |
-
-
-
- Rating History for <%= player.name %>
-
-
-
+ |
Click to load rating history...
@@ -61,4 +97,4 @@
<% }); %>
|
-<% } %>
\ No newline at end of file
+<% } %>
diff --git a/views/partials/topbar.ejs b/views/partials/topbar.ejs
new file mode 100644
index 0000000..81ed594
--- /dev/null
+++ b/views/partials/topbar.ejs
@@ -0,0 +1,46 @@
+