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
This commit is contained in:
Samuel Enocsson
2026-05-22 21:27:05 +02:00
parent cc9d8eb4cd
commit b51c47dc4c
7 changed files with 88 additions and 57 deletions
+32
View File
@@ -13,6 +13,7 @@
.mobile-add-bar { display: none; }
.mobile-section-head { display: none; }
.m-tab-pill { display: none; }
.m-search-input { display: none; }
/* ═══════════════════════════════════════════════════ */
@media (max-width: 880px) {
@@ -24,6 +25,37 @@
.add-bar { display: none !important; }
.footnote { display: none !important; }
/* Hide desktop search card on mobile (mobile has .m-search-input instead) */
.card-section { display: none; }
/* ── Mobile course search input ─────────────────── */
.m-search-input {
display: block;
width: 100%;
height: 38px;
padding: 0 12px;
background: var(--paper-2);
border: 1px solid var(--line-2);
border-radius: 10px;
font-family: var(--font-sans);
font-size: 14px;
color: var(--ink);
outline: none;
box-sizing: border-box;
transition: border-color 150ms ease, box-shadow 150ms ease;
}
.m-search-input::placeholder {
color: var(--ink-3);
opacity: 0.7;
}
.m-search-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 14%, transparent);
}
/* Hide desktop table but keep .table-card as wrapper */
.table-card > #ratings-table table,
#ratings-table table,
-23
View File
@@ -10,16 +10,6 @@
display: none;
}
@media (max-width: 768px) {
.mobile-only {
display: block;
}
.player-name {
font-weight: 600;
}
}
/* ── Rating values ────────────────────────────── */
.rating-value {
@@ -286,19 +276,6 @@
background: #059669;
}
/* ── Responsive ───────────────────────────────── */
@media (max-width: 768px) {
.chart-container {
height: 250px;
margin: 5px 0;
}
.chart-title {
font-size: 13px;
}
}
/* ── Target Rating Calculator ─────────────────── */
.target-rating-icon {
+11 -6
View File
@@ -31,32 +31,37 @@ function toggleCourseLayouts(courseId) {
}
// ── Mobile course card toggle ──────────────────────
var openMobileCourseId = null;
let openMobileCourseId = null;
function toggleMobileCourseLayouts(courseId) {
var card = document.getElementById('m-course-' + courseId);
const card = document.getElementById('m-course-' + courseId);
if (!card) return;
var isOpen = card.classList.contains('is-open');
const isOpen = card.classList.contains('is-open');
// Close previously open card
if (openMobileCourseId !== null && openMobileCourseId !== courseId) {
var prevCard = document.getElementById('m-course-' + openMobileCourseId);
if (prevCard) prevCard.classList.remove('is-open');
const prevCard = document.getElementById('m-course-' + openMobileCourseId);
if (prevCard) {
prevCard.classList.remove('is-open');
prevCard.setAttribute('aria-expanded', 'false');
}
openMobileCourseId = null;
}
if (isOpen) {
card.classList.remove('is-open');
card.setAttribute('aria-expanded', 'false');
openMobileCourseId = null;
return;
}
card.classList.add('is-open');
card.setAttribute('aria-expanded', 'true');
openMobileCourseId = courseId;
// Lazy-load layouts on first expand
var container = document.getElementById('m-layouts-container-' + courseId);
const container = document.getElementById('m-layouts-container-' + courseId);
if (container && container.dataset.loaded !== 'true') {
htmx.ajax('GET', '/partials/course-layouts/' + courseId, { target: '#m-layouts-container-' + courseId, swap: 'innerHTML' });
container.dataset.loaded = 'true';
+18 -13
View File
@@ -31,8 +31,8 @@ function initChartsIn(rootEl) {
if (container.dataset.charted === 'true') return;
if (!container.dataset.history) return;
try {
var history = JSON.parse(container.dataset.history);
var isMobile = container.dataset.variant === 'mobile';
const history = JSON.parse(container.dataset.history);
const isMobile = container.dataset.variant === 'mobile';
if (isMobile) {
createRatingChart(container, history, {
w: 360,
@@ -782,32 +782,37 @@ async function refreshHistoryThenCalculate(pdgaNumber) {
}
// ── Mobile player card toggle ──────────────────────
var openMobilePdgaNumber = null;
let openMobilePdgaNumber = null;
function toggleMobilePlayerCard(pdgaNumber) {
var card = document.getElementById('m-card-' + pdgaNumber);
const card = document.getElementById('m-card-' + pdgaNumber);
if (!card) return;
var isOpen = card.classList.contains('is-open');
const isOpen = card.classList.contains('is-open');
// Close previously open card
if (openMobilePdgaNumber !== null && openMobilePdgaNumber !== pdgaNumber) {
var prevCard = document.getElementById('m-card-' + openMobilePdgaNumber);
if (prevCard) prevCard.classList.remove('is-open');
const prevCard = document.getElementById('m-card-' + openMobilePdgaNumber);
if (prevCard) {
prevCard.classList.remove('is-open');
prevCard.setAttribute('aria-expanded', 'false');
}
openMobilePdgaNumber = null;
}
if (isOpen) {
card.classList.remove('is-open');
card.setAttribute('aria-expanded', 'false');
openMobilePdgaNumber = null;
return;
}
card.classList.add('is-open');
card.setAttribute('aria-expanded', 'true');
openMobilePdgaNumber = pdgaNumber;
// Init charts inside the expand panel
var expand = card.querySelector('.m-card__expand');
const expand = card.querySelector('.m-card__expand');
if (expand) {
initChartsIn(expand);
}
@@ -816,20 +821,20 @@ function toggleMobilePlayerCard(pdgaNumber) {
// ── Mobile add player ──────────────────────────────
async function searchAndAddPlayerMobile(event) {
if (event) event.preventDefault();
var input = document.getElementById('pdga-number-input-mobile');
var pdgaNumber = input ? input.value.trim() : '';
const input = document.getElementById('pdga-number-input-mobile');
const pdgaNumber = input ? input.value.trim() : '';
if (!pdgaNumber) {
alert('Please enter a PDGA number');
return;
}
var button = event && event.target ? event.target.querySelector('button[type="submit"]') : null;
const button = event && event.target ? event.target.querySelector('button[type="submit"]') : null;
if (button) { button.disabled = true; button.textContent = 'Searching...'; }
try {
var response = await fetch('/api/search-player/' + pdgaNumber);
var data = await response.json();
const response = await fetch('/api/search-player/' + pdgaNumber);
const data = await response.json();
if (!response.ok) {
showErrorModal(data.error || 'Player not found');