diff options
| -rw-r--r-- | public/css/style.css | 38 | ||||
| -rw-r--r-- | views/admin/person_detail.erb | 32 |
2 files changed, 41 insertions, 29 deletions
diff --git a/public/css/style.css b/public/css/style.css index a549143..b907f0e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -571,35 +571,29 @@ tr.delete-marked td { background: rgba(192,57,43,.08); } .person-detail-main, .person-page-main { flex: 1; min-width: 0; } -/* Selectable cards (photo grid and face grid) */ -.selectable-card, -.selectable-face { cursor: pointer; position: relative; } -.selectable-card .thumb-wrap, -.selectable-face .face-detail-thumb { position: relative; } +/* Selectable photo cards (public person page) */ +.selectable-card { cursor: pointer; position: relative; } +.selectable-card .thumb-wrap { position: relative; } .select-checkbox { position: absolute; top: 6px; left: 6px; width: 20px; height: 20px; border-radius: 4px; - border: 2px solid rgba(255,255,255,.7); - background: rgba(0,0,0,.35); - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity .12s; - pointer-events: none; + border: 2px solid rgba(255,255,255,.7); background: rgba(0,0,0,.35); + opacity: 0; transition: opacity .12s; pointer-events: none; } -.selectable-card:hover .select-checkbox, -.selectable-face:hover .select-checkbox { opacity: 1; } -.selectable-card.selected .select-checkbox, -.selectable-face.selected .select-checkbox { +.selectable-card:hover .select-checkbox { opacity: 1; } +.selectable-card.selected .select-checkbox { opacity: 1; background: var(--accent); border-color: var(--accent); } -.selectable-card.selected .select-checkbox::after { - content: ''; display: block; - width: 5px; height: 9px; - border: 2px solid #fff; border-top: none; border-left: none; - transform: rotate(45deg) translate(-1px, -1px); +.selectable-card.selected .thumb-wrap img { opacity: .75; } +.selectable-card.selected { outline: 2px solid var(--accent); outline-offset: -2px; } + +/* Selectable face cards (admin cluster detail) */ +.selectable-face { cursor: pointer; } +.face-cb { + display: block; margin: 5px auto 0; width: 16px; height: 16px; + cursor: pointer; accent-color: var(--accent); } -.selectable-card.selected .thumb-wrap img, -.selectable-face.selected .face-detail-thumb img { opacity: .75; } -.selectable-card.selected, +.selectable-face.selected .face-detail-thumb img { opacity: .72; } .selectable-face.selected { outline: 2px solid var(--accent); outline-offset: -2px; } /* ── Admin upload ──────────────────────────────────────────────────────── */ diff --git a/views/admin/person_detail.erb b/views/admin/person_detail.erb index fbce8af..10319cf 100644 --- a/views/admin/person_detail.erb +++ b/views/admin/person_detail.erb @@ -128,8 +128,8 @@ <img src="/face/<%= ERB::Util.html_escape(rel) %>?box=<%= ERB::Util.html_escape(box.join(',')) %>" width="100" height="100" loading="lazy"> </a> - <span class="select-checkbox" aria-hidden="true"></span> </div> + <input type="checkbox" class="face-cb" aria-label="Select face"> </div> <% end %> </div> @@ -170,21 +170,39 @@ document.getElementById('bulk-form').style.display = n ? '' : 'none'; } + function toggleCard(card, cb, force) { + var checked = (force !== undefined) ? force : !cb.checked; + cb.checked = checked; + var key = card.dataset.entry; + if (checked) { selected.add(key); card.classList.add('selected'); } + else { selected.delete(key); card.classList.remove('selected'); } + updatePanel(); + } + document.querySelectorAll('.selectable-face').forEach(function (card) { + var cb = card.querySelector('.face-cb'); + + // Checkbox change is the authoritative source of truth + cb.addEventListener('change', function () { + toggleCard(card, cb, cb.checked); + }); + + // Clicking anywhere on the card except the image link also toggles card.addEventListener('click', function (e) { - if (e.target.closest('.face-photo-link') && !e.target.closest('.select-checkbox')) return; + if (e.target.closest('.face-photo-link')) return; + if (e.target === cb) return; // let the checkbox handle it natively e.preventDefault(); - var key = card.dataset.entry; - if (selected.has(key)) { selected.delete(key); card.classList.remove('selected'); } - else { selected.add(key); card.classList.add('selected'); } - updatePanel(); + toggleCard(card, cb); }); }); document.getElementById('bulk-clear').addEventListener('click', function (e) { e.preventDefault(); selected.clear(); - document.querySelectorAll('.selectable-face.selected').forEach(function (c) { c.classList.remove('selected'); }); + document.querySelectorAll('.selectable-face').forEach(function (c) { + c.querySelector('.face-cb').checked = false; + c.classList.remove('selected'); + }); updatePanel(); }); |
