From bb1d388af6b312c84e57da51cd4a70378ab15fd7 Mon Sep 17 00:00:00 2001 From: Arne Baeumler Date: Fri, 12 Jun 2026 14:51:08 +0200 Subject: [PATCH] refactor(ui): tile photo list --- src/server/request-handler.ts | 51 ++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/server/request-handler.ts b/src/server/request-handler.ts index 2828954..6344862 100644 --- a/src/server/request-handler.ts +++ b/src/server/request-handler.ts @@ -498,47 +498,47 @@ function htmlPage(): string { .list { display: grid; - gap: 6px; + grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); + gap: 8px; + align-content: start; min-height: 0; overflow: auto; padding-right: 2px; } .photo { - display: grid; - grid-template-columns: 38px 1fr; - gap: 8px; - align-items: center; - padding: 8px; + display: block; + position: relative; + aspect-ratio: 1 / 1; + padding: 0; border-radius: var(--radius-md); background: var(--timeline-surface-soft); border: 1px solid var(--timeline-border); color: var(--timeline-text); min-width: 0; + overflow: hidden; } .photo img { - width: 38px; - height: 38px; + width: 100%; + height: 100%; object-fit: cover; border-radius: 0; + display: block; } - .photo strong { - display: block; - font-size: 0.8rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + .photo::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(15, 23, 42, 0.34), transparent 42%); + opacity: 0; + transition: opacity 140ms ease; } - .photo span { - color: var(--timeline-text-muted); - font-size: 0.7rem; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + .photo:hover::after, + .photo.active::after { + opacity: 1; } .photo.active { @@ -858,6 +858,7 @@ function htmlPage(): string { .list { max-height: min(52vh, 420px); + grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); } main { @@ -1638,11 +1639,11 @@ function htmlPage(): string { const item = document.createElement("article"); item.className = "photo" + (photo.id === state.activePhotoId ? " active" : ""); + const photoTitle = photo.name + "\n" + formatDate(photo.capturedAt); + item.title = photoTitle; + item.setAttribute("aria-label", photoTitle); item.innerHTML = - '' + escapeHtml(photo.name) + '' + - '
' + escapeHtml(photo.name) + '' + - escapeHtml(formatDate(photo.capturedAt)) + - '
'; + '' + escapeHtml(photo.name) + ''; item.addEventListener("click", () => { state.activePhotoId = photo.id; renderVisiblePhotos({ fitMap: false });