feat: mobile UI card layout for players and courses (#16)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
<%
|
||||
// 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>';
|
||||
}
|
||||
%>
|
||||
|
||||
<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>
|
||||
|
||||
<% 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-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 %>" onclick="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>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } %>
|
||||
Reference in New Issue
Block a user