From 7d1149aabdc3f6d8246a290bf41462ceb9701930 Mon Sep 17 00:00:00 2001 From: dtoro Date: Thu, 9 Apr 2026 20:09:49 +0200 Subject: [PATCH] feat: themed scrollbars and chiseled thumbnail chips - Replace browser scrollbars with slim 6px overlay pills that tint to primary on hover (transparent track, Firefox + WebKit). - Rework the thumbnail badge family: drop the mismatched white ring halo for a 1px dark frame + inset top highlight, and swap rounded- full pills for rounded-sm chips so the ornaments match the pixel-art aesthetic used elsewhere in the app. - Soften the ActiveHeapCard desert backdrop so the fanned thumbnails read on top. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/heaps/ActiveHeapCard.tsx | 1 + .../components/timeline/PhotoThumbnail.tsx | 35 ++++++++++++------- frontend/src/index.css | 35 ++++++++++++++----- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/heaps/ActiveHeapCard.tsx b/frontend/src/components/heaps/ActiveHeapCard.tsx index 07b4887..87eb698 100644 --- a/frontend/src/components/heaps/ActiveHeapCard.tsx +++ b/frontend/src/components/heaps/ActiveHeapCard.tsx @@ -88,6 +88,7 @@ export function ActiveHeapCard() { backgroundPosition: 'center bottom', backgroundRepeat: 'no-repeat', imageRendering: 'pixelated', + opacity: 0.7, }} > {visible.length === 0 ? ( diff --git a/frontend/src/components/timeline/PhotoThumbnail.tsx b/frontend/src/components/timeline/PhotoThumbnail.tsx index be55f26..7b289ca 100644 --- a/frontend/src/components/timeline/PhotoThumbnail.tsx +++ b/frontend/src/components/timeline/PhotoThumbnail.tsx @@ -11,20 +11,29 @@ export const PHOTO_DRAG_MIME = 'application/x-mulita-photos' // ── Thumbnail badge family ─────────────────────────────────────────────── // Every ornament that overlays a thumbnail — here and in other views that // wrap PhotoThumbnail (e.g. DuplicatesView) — must compose these classes so -// the set reads as one coherent system. Shape, height, ring, typography are -// fixed; only the colour variant varies by semantics: +// the set reads as one coherent system. Shape, height, typography are +// fixed; variants set the fill + chiseled-pixel frame: // PRIMARY → user-affirmed state (selection, rating, active-heap) // NEUTRAL → informational metadata (duplicate flag, discard, file type, dims) // PICK → auto-suggested "best" in duplicate groups +// +// Frame is a single box-shadow stack rather than a Tailwind `ring` so the +// 1px outline, the subtle inset top highlight, and the drop shadow all +// live in one paint, and variants can tint the inset highlight to suit +// their fill. Corners are `rounded-sm` (2px) to echo the pixel-art theme +// used in the TopBar and ActiveHeapCard. export const THUMB_BADGE_BASE = - 'inline-flex h-5 items-center gap-1 rounded-full px-1.5 text-[10px] font-medium leading-none text-white shadow-md ring-1 ring-white/90' + 'inline-flex h-5 items-center gap-1 rounded-sm px-1.5 text-[10px] font-semibold leading-none text-white' /** Square icon-only variant — compose alongside THUMB_BADGE_BASE. */ export const THUMB_BADGE_SQUARE = 'w-5 justify-center !px-0' /** Standard icon size for any lucide glyph inside a badge. */ export const THUMB_BADGE_ICON = 'h-3 w-3' -export const THUMB_BADGE_PRIMARY = 'bg-primary' -export const THUMB_BADGE_NEUTRAL = 'bg-black/70 backdrop-blur-sm' -export const THUMB_BADGE_PICK = 'bg-pick' +export const THUMB_BADGE_PRIMARY = + 'bg-primary shadow-[0_0_0_1px_rgba(0,0,0,0.55),inset_0_1px_0_rgba(255,255,255,0.28),0_1px_2px_rgba(0,0,0,0.5)]' +export const THUMB_BADGE_NEUTRAL = + 'bg-black/75 backdrop-blur-sm shadow-[0_0_0_1px_rgba(255,255,255,0.12),inset_0_1px_0_rgba(255,255,255,0.08),0_1px_2px_rgba(0,0,0,0.55)]' +export const THUMB_BADGE_PICK = + 'bg-pick shadow-[0_0_0_1px_rgba(0,0,0,0.55),inset_0_1px_0_rgba(255,255,255,0.28),0_1px_2px_rgba(0,0,0,0.5)]' // Auto-retry schedule (ms). Backend generates thumbs on-demand via Celery, so // the first hit on a freshly-scanned library returns 404 "not ready" until @@ -135,7 +144,7 @@ export function PhotoThumbnail({ useEffect(() => { return () => clearRetryTimer() }, []) - + // Build the drag payload at fire time so multi-selection drags carry the // current selection. If the dragged photo isn't part of the selection, // drag just that one photo (matches Finder semantics). @@ -160,7 +169,7 @@ export function PhotoThumbnail({ // pops on light AND dark photos. 'hover:ring-2 hover:ring-primary/60 hover:ring-offset-1 hover:ring-offset-bg', isSelected && - 'ring-2 ring-primary ring-offset-2 ring-offset-bg shadow-lg', + 'ring-2 ring-primary ring-offset-2 ring-offset-bg shadow-lg', !imageLoaded && 'bg-surface animate-pulse' )} style={ @@ -222,7 +231,7 @@ export function PhotoThumbnail({ )} - + {/* ── Ornaments ──────────────────────────────────────────────────── * All overlays compose the THUMB_BADGE_* classes so they share one * shape/size/ring family. Colour signals semantics: @@ -297,10 +306,10 @@ export function PhotoThumbnail({ {/* TR — file-type metadata (RAW / VIDEO) */} {(photo.filepath.toLowerCase().match(/\.(raw|arw|cr2|cr3|nef|orf|rw2|dng)$/i) || photo.filepath.toLowerCase().match(/\.(mov|mp4|avi|mkv)$/i)) && ( -
- {photo.filepath.toLowerCase().match(/\.(mov|mp4|avi|mkv)$/i) ? 'VIDEO' : 'RAW'} -
- )} +
+ {photo.filepath.toLowerCase().match(/\.(mov|mp4|avi|mkv)$/i) ? 'VIDEO' : 'RAW'} +
+ )} ) } \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 7e4df1e..19d95a8 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -24,22 +24,41 @@ } } -/* Custom scrollbar styles */ -::-webkit-scrollbar { - width: 8px; - height: 8px; +/* Compact themed scrollbars. Track is fully transparent so the bar + floats over the panel surface, and the thumb is a slim translucent + pill that tints toward the primary on hover. Firefox uses the + `scrollbar-*` properties below; Chromium / WebKit uses the + `::-webkit-scrollbar*` rules. */ +* { + scrollbar-width: thin; + scrollbar-color: rgba(240, 230, 210, 0.18) transparent; } -::-webkit-scrollbar-track { - @apply bg-surface; +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track, +::-webkit-scrollbar-corner { + background: transparent; } ::-webkit-scrollbar-thumb { - @apply bg-surface-offset rounded; + background-color: rgba(240, 230, 210, 0.18); + border-radius: 9999px; + /* Inset border keeps the thumb visually slimmer than the track gutter + so it reads as a pill rather than a bar. */ + border: 1px solid transparent; + background-clip: padding-box; } ::-webkit-scrollbar-thumb:hover { - @apply bg-text-faint; + background-color: rgba(232, 150, 90, 0.55); /* primary, soft */ +} + +::-webkit-scrollbar-thumb:active { + background-color: rgba(232, 150, 90, 0.85); } /* Mule walk cycle — 3x2 sprite sheet (6 frames). Each keyframe holds