From 3ff1edf8bf57bd7d313c3f6af03d29bbeaa412d0 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Sat, 18 Jul 2026 16:11:06 +0200 Subject: [PATCH] I do love my keyboard --- public/s/css/f0ckm.css | 8 +++-- public/s/js/f0ckm.js | 74 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 19ca774..e4c569a 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -4072,6 +4072,7 @@ div.posts>a:not(.notif-item)::before { } div.posts>a:not(.notif-item):hover, +div.posts>a:not(.notif-item):focus-visible, div.posts>a:not(.notif-item).touch-active { box-shadow: 0 0 0 1px var(--white); } @@ -4094,6 +4095,7 @@ div.posts>a:not(.notif-item)::after { } div.posts>a:not(.notif-item):hover::after, +div.posts>a:not(.notif-item):focus-visible::after, div.posts>a:not(.notif-item).touch-active::after { visibility: visible; } @@ -10974,7 +10976,8 @@ div.posts>a.thumb.has-notif { border: 1px solid var(--accent); } -div.posts>a.thumb.has-notif:hover { +div.posts>a.thumb.has-notif:hover, +div.posts>a.thumb.has-notif:focus-visible { box-shadow: inset 0 0 0 1000px rgba(255, 0, 0, 0.4), 0 0 0 2px var(--accent) !important; } @@ -10988,7 +10991,8 @@ div.posts>a.thumb.has-notif::after { z-index: 10 !important; } -div.posts>a.thumb.has-notif:hover::after { +div.posts>a.thumb.has-notif:hover::after, +div.posts>a.thumb.has-notif:focus-visible::after { visibility: visible !important; } diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index 0272b43..ff271d3 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -9388,22 +9388,24 @@ document.addEventListener('DOMContentLoaded', () => { }; window.startHoverPreview = startPreview; - document.addEventListener('mouseover', (e) => { + const handleEnter = (e) => { const thumb = e.target.closest('.thumb'); - - // If we are hovering a thumb, and it's NOT the active one if (thumb && thumb !== activeThumb) { startPreview(thumb, 150); } - }); + }; + document.addEventListener('mouseover', handleEnter); + document.addEventListener('focusin', handleEnter); - document.addEventListener('mouseout', (e) => { + const handleLeave = (e) => { const thumb = e.target.closest('.thumb'); if (thumb) { - if (e.relatedTarget && thumb.contains(e.relatedTarget)) return; + if (e.type === 'mouseout' && e.relatedTarget && thumb.contains(e.relatedTarget)) return; clearPreview(); } - }); + }; + document.addEventListener('mouseout', handleLeave); + document.addEventListener('focusout', handleLeave); // Touch handling to support "tap to activate" behavior // 1. First tap: Activate (visuals + preview) AND prevent navigation @@ -11281,3 +11283,61 @@ document.addEventListener('click', (e) => { } }, true); })(); + +// --- Grid Keyboard Accessibility --- +document.addEventListener('keydown', (e) => { + const isTab = e.key === 'Tab'; + const isArrow = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key); + if (!isTab && !isArrow) return; + + const thumbs = Array.from(document.querySelectorAll('.posts .thumb')); + if (thumbs.length === 0) return; + + if (isTab && document.activeElement === document.body) { + e.preventDefault(); + thumbs[0].focus(); + return; + } + + const active = document.activeElement; + if (isArrow && active && active.classList.contains('thumb')) { + const currentIndex = thumbs.indexOf(active); + if (currentIndex === -1) return; + + let nextIndex = currentIndex; + + if (e.key === 'ArrowRight') { + nextIndex = currentIndex + 1; + } else if (e.key === 'ArrowLeft') { + nextIndex = currentIndex - 1; + } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + const currentRow = thumbs.filter(t => t.offsetTop === active.offsetTop); + const columns = currentRow.length; + + let maxCols = columns; + const firstRowTop = thumbs[0].offsetTop; + const firstRow = thumbs.filter(t => t.offsetTop === firstRowTop); + if (firstRow.length > columns) maxCols = firstRow.length; + + if (e.key === 'ArrowDown') { + nextIndex = currentIndex + maxCols; + if (nextIndex >= thumbs.length) { + const lastRowTop = thumbs[thumbs.length - 1].offsetTop; + if (active.offsetTop < lastRowTop) { + nextIndex = thumbs.length - 1; + } else { + nextIndex = currentIndex; + } + } + } else { + nextIndex = currentIndex - maxCols; + } + } + + if (nextIndex >= 0 && nextIndex < thumbs.length && nextIndex !== currentIndex) { + e.preventDefault(); + thumbs[nextIndex].focus(); + thumbs[nextIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + } +});