style: improve thumbnail indicator + selection contrast

- Bump primary from #3b6ed8 to #3b82f6 (Tailwind blue-500). Higher
  saturation reads better against both the dark surface and varied
  photo content. Contrast against bg-bg goes from ~5.7:1 to ~6.6:1.
- Selection ring: add ring-offset-2 ring-offset-bg so the bright blue
  has a dark gap separating it from the photo edge — pops on light
  and dark photos alike. Hover ring gets the same treatment.
- Selection check badge: white ring + shadow + thicker stroke so the
  badge is legible against any photo (was disappearing on bright
  scenes).
- Rating stars: wrap in a translucent dark pill with backdrop-blur so
  yellow stars don't vanish on yellow / sandy photos.
- Heap / duplicate / discarded badges: matching white ring + thicker
  icon stroke so they all read consistently and don't blend in.
- Timeline date headers: text-text instead of text-text-muted so the
  group labels actually pop above the grid.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 22:22:07 +02:00
parent eb16564b84
commit 9317730885
4 changed files with 26 additions and 20 deletions

View File

@@ -118,8 +118,11 @@ export function PhotoThumbnail({
<div
className={clsx(
'group relative cursor-pointer overflow-hidden rounded-sm transition-all duration-200',
'hover:ring-2 hover:ring-primary/50',
isSelected && 'ring-2 ring-primary shadow-lg',
// Two-tone hover ring: bright primary inner + dark offset so it
// 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',
!imageLoaded && 'bg-surface animate-pulse'
)}
style={{
@@ -181,49 +184,52 @@ export function PhotoThumbnail({
</div>
)}
{/* Selection Indicator */}
{/* Selection Indicator — white ring around the primary fill so the
* badge stays visible against any photo (light or dark). */}
{isSelected && (
<div className="absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-primary text-white">
<Check className="h-4 w-4" />
<div className="absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-primary text-white shadow-md ring-2 ring-white/90">
<Check className="h-4 w-4" strokeWidth={3} />
</div>
)}
{/* Rating Stars */}
{/* Rating Stars — sit in a translucent dark pill so yellow stars
* don't disappear against a yellow / sandy photo. */}
{photo.rating > 0 && (
<div className="absolute bottom-1 left-1 flex gap-0.5">
<div className="absolute bottom-1 left-1 flex items-center gap-0.5 rounded bg-black/55 px-1 py-0.5 shadow-sm backdrop-blur-[2px]">
{Array.from({ length: photo.rating }).map((_, i) => (
<Star
key={i}
className="h-3 w-3 fill-star text-star"
className="h-3 w-3 fill-star text-star drop-shadow"
/>
))}
</div>
)}
{/* Flag Indicators */}
{/* Flag Indicators — every badge gets a white ring so it survives
* any photo content. */}
<div className="absolute bottom-1 right-1 flex items-center gap-1">
{isInActiveHeap && (
<div
className="flex h-5 w-5 items-center justify-center rounded-full bg-pick text-white shadow-md"
className="flex h-5 w-5 items-center justify-center rounded-full bg-pick text-white shadow-md ring-2 ring-white/90"
title="In active heap"
>
<ShoppingBasket className="h-3 w-3" />
<ShoppingBasket className="h-3 w-3" strokeWidth={2.5} />
</div>
)}
{photo.is_duplicate && (
<div
className="flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white shadow-md"
className="flex h-5 w-5 items-center justify-center rounded-full bg-black/70 text-white shadow-md ring-2 ring-white/90"
title="Duplicate (matches another photo's hash)"
>
<Copy className="h-3 w-3" />
<Copy className="h-3 w-3" strokeWidth={2.5} />
</div>
)}
{photo.is_discarded && (
<div
className="flex h-5 w-5 items-center justify-center rounded-full bg-reject text-white shadow-md"
className="flex h-5 w-5 items-center justify-center rounded-full bg-reject text-white shadow-md ring-2 ring-white/90"
title="Discarded"
>
<Trash2 className="h-3 w-3" />
<Trash2 className="h-3 w-3" strokeWidth={2.5} />
</div>
)}
</div>

View File

@@ -482,7 +482,7 @@ export function Timeline() {
}}
className="flex items-end pb-1"
>
<h3 className="text-sm font-semibold uppercase tracking-wide text-text-muted">
<h3 className="text-sm font-semibold uppercase tracking-wide text-text">
{item.label}
</h3>
</div>

View File

@@ -12,7 +12,7 @@
--color-text: #e8e6e0;
--color-text-muted: #878580;
--color-text-faint: #4a4845;
--color-primary: #3b6ed8;
--color-primary: #3b82f6;
--color-pick: #4f9e5c;
--color-reject: #c25a5a;
--color-star: #d4a340;

View File

@@ -17,7 +17,7 @@ export default {
text: '#e8e6e0',
'text-muted': '#878580',
'text-faint': '#4a4845',
primary: '#3b6ed8', // deep royal blue
primary: '#3b82f6', // saturated blue (Tailwind blue-500) — high contrast on dark bg AND on photo content
pick: '#4f9e5c', // green for picked
reject: '#c25a5a', // red for rejected
star: '#d4a340', // amber for stars