Files
pdga-rating/views/partials/ratings-cards.ejs
T
Samuel Enocsson b51c47dc4c fix: address mobile UI review findings (#16)
- Hide desktop .card-section on mobile, add .m-search-input with same
  HTMX attrs for mobile course search (fixes horizontal overflow)
- Remove dead layoutCount var and .m-layouts-pill block in course-cards
- Remove dead 768px breakpoints from players.css (table hidden at 880px)
- Move .mobile-section-head inside else-block for empty state in both
  ratings-cards and course-cards (fixes section head showing on empty)
- Add tabindex, role=button, aria-expanded, onkeydown to .m-card and
  .m-course-card; toggle aria-expanded in JS toggle functions
- Fix data-history attribute to use <%=  (HTML-escaped) instead of <%-
- Convert var to const/let in all new/changed JS blocks
2026-05-22 21:27:05 +02:00

123 lines
5.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<%
// 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 &middot; <%= 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">&#9660;</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>
<% } %>