133 lines
6.3 KiB
Plaintext
133 lines
6.3 KiB
Plaintext
<%
|
||
// Mobile sparkline helper — parametrised, used only in this partial
|
||
function renderSparkline(values, opts) {
|
||
opts = opts || {};
|
||
var w = opts.w || 70;
|
||
var h = opts.h || 26;
|
||
if (!values || values.length < 2) return '';
|
||
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 '<svg width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '" class="m-chart-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)"/>' +
|
||
'</svg>';
|
||
}
|
||
%>
|
||
|
||
<% if (ratings.length === 0) { %>
|
||
<p style="text-align: center; color: var(--ink-3); padding: 40px 0;">No players tracked yet.</p>
|
||
<% } else { %>
|
||
<div class="mobile-section-head">
|
||
<span class="kicker">TRACKED PLAYERS · <%= ratings.length %></span>
|
||
<button id="trendchart-toggle-mobile" class="pill-button" type="button" aria-pressed="false">Trend chart</button>
|
||
</div>
|
||
<div class="mobile-list">
|
||
<% ratings.forEach(function(player, index) {
|
||
var sparkSvg = renderSparkline(player.monthlyHistory || [], { w: 70, h: 26 });
|
||
var isFirst = index === 0;
|
||
var rank = index + 1;
|
||
|
||
var ratingIsNull = (player.rating == null);
|
||
var ratingCls = ratingIsNull ? 'flat' : (player.ratingChange > 0 ? 'up' : player.ratingChange < 0 ? 'down' : 'flat');
|
||
var ratingGlyph = (ratingIsNull || player.ratingChange === 0) ? '–' : (player.ratingChange > 0 ? '▲' : '▼');
|
||
var ratingNum = ratingIsNull ? '—' : (player.ratingChange > 0 ? '+' + player.ratingChange : String(player.ratingChange));
|
||
|
||
var predIsNull = (player.predictedRating == null);
|
||
var predCls = predIsNull ? 'flat' : (player.deltaPredicted > 0 ? 'up' : player.deltaPredicted < 0 ? 'down' : 'flat');
|
||
var predGlyph = (predIsNull || player.deltaPredicted === 0) ? '–' : (player.deltaPredicted > 0 ? '▲' : '▼');
|
||
var predNum = predIsNull ? '—' : (player.deltaPredicted > 0 ? '+' + player.deltaPredicted : String(player.deltaPredicted));
|
||
%>
|
||
<div class="m-card" id="m-card-<%= player.pdgaNumber %>"
|
||
tabindex="0" role="button" aria-expanded="false" aria-label="Expand player details"
|
||
onclick="toggleMobilePlayerCard(<%= player.pdgaNumber %>)"
|
||
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();toggleMobilePlayerCard(<%= player.pdgaNumber %>);}">
|
||
<div class="m-card__head">
|
||
<div class="m-rank-chip<%= isFirst ? ' m-rank-chip--first' : '' %>"><%= rank %></div>
|
||
<div class="m-card__name-stack">
|
||
<span class="m-player-name"><%= player.name %></span>
|
||
<span class="m-pdga-num">#<%= player.pdgaNumber %></span>
|
||
</div>
|
||
<span class="m-chevron">▼</span>
|
||
</div>
|
||
|
||
<div class="m-card__body">
|
||
<div class="m-card__stats">
|
||
<div class="m-stat-row">
|
||
<span class="m-stat-label">RATING</span>
|
||
<span class="m-num"><%= player.rating || '—' %></span>
|
||
<span class="delta-pill <%= ratingCls %>"><span class="delta-glyph"><%= ratingGlyph %></span><span class="delta-num"><%= ratingNum %></span></span>
|
||
</div>
|
||
<div class="m-stat-row">
|
||
<span class="m-stat-label">PREDICTED</span>
|
||
<span class="m-num m-num--predicted"><%= player.predictedRating || '—' %></span>
|
||
<span class="delta-pill <%= predCls %> delta-predicted-pill"><span class="delta-glyph"><%= predGlyph %></span><span class="delta-num"><%= predNum %></span></span>
|
||
</div>
|
||
</div>
|
||
<% if (sparkSvg) { %>
|
||
<div class="m-card__sparkline"><span class="sparkline"><%- sparkSvg %></span></div>
|
||
<% } %>
|
||
</div>
|
||
|
||
<div class="m-card__expand">
|
||
<% if (player.ratingHistory && player.ratingHistory.length > 0) { %>
|
||
<div class="player-chart m-chart"
|
||
data-variant="mobile"
|
||
data-history="<%= JSON.stringify(player.ratingHistory) %>">
|
||
</div>
|
||
<% } %>
|
||
<dl class="m-detail-grid">
|
||
<div>
|
||
<dt>Current rating</dt>
|
||
<dd><%= player.rating || '—' %></dd>
|
||
</div>
|
||
<div>
|
||
<dt>Last month</dt>
|
||
<dd><%= player.lastMonthRating || '—' %></dd>
|
||
</div>
|
||
<div>
|
||
<dt>Change vs last month</dt>
|
||
<dd><span class="delta-pill <%= ratingCls %>"><span class="delta-glyph"><%= ratingGlyph %></span><span class="delta-num"><%= ratingNum %></span></span></dd>
|
||
</div>
|
||
<div>
|
||
<dt>Predicted next update</dt>
|
||
<dd><%= player.predictedRating || '—' %></dd>
|
||
</div>
|
||
<div>
|
||
<dt>Gap to predicted</dt>
|
||
<dd><span class="delta-pill <%= predCls %> delta-predicted-pill"><span class="delta-glyph"><%= predGlyph %></span><span class="delta-num"><%= predNum %></span></span></dd>
|
||
</div>
|
||
<% if (player.stdDev != null && player.rating) { %>
|
||
<div>
|
||
<dt>Round spread</dt>
|
||
<dd>±<%= player.stdDev %></dd>
|
||
</div>
|
||
<div>
|
||
<dt>Rating range</dt>
|
||
<dd><%= player.rating - player.stdDev %>–<%= player.rating + player.stdDev %></dd>
|
||
</div>
|
||
<% } %>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
<% }); %>
|
||
</div>
|
||
<% } %>
|