all repos — weimar @ 4725091cf8486591001604eeabcb1c4632f7ab7f

Unnamed repository; edit this file 'description' to name the repository.

feat: redesign browse grid with Chevereto-style card layout and hover overlay

- Compact grid with 2px gaps and dark image backgrounds (aspect-ratio 16:10)
- Image fills card via object-fit: cover with subtle scale-on-hover
- Gradient overlay on hover shows filename, dimensions, and truncated tags
- Removed rounded corners and info text below cards for cleaner look

💘 Generated with Crush

Assisted-by: Crush:deepseek-reasoner
Maxwell Jensen maxwelljensen@posteo.net
Wed, 13 May 2026 15:44:59 +0200
commit

4725091cf8486591001604eeabcb1c4632f7ab7f

parent

4240c5c97779567cfd5d71edbcab07b1fd3936ac

1 files changed, 84 insertions(+), 37 deletions(-)

jump to
M web/src/routes/browse/+page.svelteweb/src/routes/browse/+page.svelte

@@ -1,6 +1,6 @@

<script lang="ts"> import { onMount } from 'svelte'; - import { listImages, thumbnailUrl, type Image } from '$lib/api'; + import { listImages, thumbnailUrl, imageDownloadUrl, type Image } from '$lib/api'; import ImageViewer from '$lib/ImageViewer.svelte'; let images = $state<Image[]>([]);

@@ -45,6 +45,12 @@ }

function openViewer(index: number) { viewerIndex = index; + } + + function formatSize(bytes: number): string { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; } </script>

@@ -77,14 +83,24 @@ {:else}

<div class="grid"> {#each images as img, i (img.id)} <button class="card" onclick={() => openViewer(i)}> - {#if img.thumbnail_path} - <img src={thumbnailUrl(img.id)} alt={img.filename} loading="lazy" /> - {:else} - <div class="placeholder">{img.mime_type}</div> - {/if} - <div class="info"> - <span class="name">{img.filename}</span> - <span class="tags">{img.tags.join(', ')}</span> + <div class="card-image"> + {#if img.thumbnail_path} + <img src={thumbnailUrl(img.id)} alt={img.filename} loading="lazy" /> + {:else} + <div class="placeholder">{img.mime_type}</div> + {/if} + </div> + <div class="card-overlay"> + <div class="card-title">{img.filename}</div> + <div class="card-meta"> + {img.width}&times;{img.height} + {#if img.tags.length > 0} + &middot; {img.tags.slice(0, 3).join(', ')} + {#if img.tags.length > 3} + &hellip; + {/if} + {/if} + </div> </div> </button> {/each}

@@ -129,67 +145,98 @@ }

.controls button.clear { background: #666; } + .grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 1rem; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 2px; } + .card { - border: 1px solid #ddd; - border-radius: 8px; - overflow: hidden; - text-decoration: none; - color: inherit; + position: relative; + display: block; + width: 100%; + padding: 0; + border: none; background: none; - padding: 0; font: inherit; + color: inherit; + cursor: pointer; text-align: left; - cursor: pointer; - transition: box-shadow 0.15s; + overflow: hidden; + } + .card:focus-visible { + outline: 2px solid #1a1a2e; + outline-offset: -2px; } - .card:hover { - box-shadow: 0 2px 12px rgba(0,0,0,0.15); + .card-image { + position: relative; + overflow: hidden; + background: #222; + aspect-ratio: 16 / 10; + display: flex; + align-items: center; + justify-content: center; } - .card img, .placeholder { + .card-image img { + display: block; width: 100%; - height: 180px; + height: 100%; object-fit: cover; - display: block; + transition: transform 200ms ease; + } + .card:hover .card-image img { + transform: scale(1.05); } .placeholder { display: flex; align-items: center; justify-content: center; - background: #f0f0f0; - color: #999; - font-size: 0.9rem; + width: 100%; + height: 100%; + color: #666; + font-size: 0.85rem; } - .info { - padding: 0.5rem; - display: flex; - flex-direction: column; - gap: 0.25rem; + + .card-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(transparent, rgba(0, 0, 0, 0.8)); + padding: 2rem 0.75rem 0.6rem; + opacity: 0; + transition: opacity 200ms ease; + pointer-events: none; } - .name { - font-weight: 600; + .card:hover .card-overlay { + opacity: 1; + } + .card-title { + color: #fff; font-size: 0.85rem; + font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); } - .tags { + .card-meta { + color: rgba(255, 255, 255, 0.7); font-size: 0.75rem; - color: #666; + margin-top: 0.1rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); } + .pagination { display: flex; align-items: center; justify-content: center; gap: 1rem; - margin-top: 1.5rem; + margin-top: 2rem; } .pagination button { padding: 0.5rem 1rem;