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:
@@ -118,8 +118,11 @@ export function PhotoThumbnail({
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'group relative cursor-pointer overflow-hidden rounded-sm transition-all duration-200',
|
'group relative cursor-pointer overflow-hidden rounded-sm transition-all duration-200',
|
||||||
'hover:ring-2 hover:ring-primary/50',
|
// Two-tone hover ring: bright primary inner + dark offset so it
|
||||||
isSelected && 'ring-2 ring-primary shadow-lg',
|
// 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'
|
!imageLoaded && 'bg-surface animate-pulse'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
@@ -181,49 +184,52 @@ export function PhotoThumbnail({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Selection Indicator */}
|
{/* Selection Indicator — white ring around the primary fill so the
|
||||||
|
* badge stays visible against any photo (light or dark). */}
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<div className="absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full bg-primary text-white">
|
<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" />
|
<Check className="h-4 w-4" strokeWidth={3} />
|
||||||
</div>
|
</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 && (
|
{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) => (
|
{Array.from({ length: photo.rating }).map((_, i) => (
|
||||||
<Star
|
<Star
|
||||||
key={i}
|
key={i}
|
||||||
className="h-3 w-3 fill-star text-star"
|
className="h-3 w-3 fill-star text-star drop-shadow"
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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">
|
<div className="absolute bottom-1 right-1 flex items-center gap-1">
|
||||||
{isInActiveHeap && (
|
{isInActiveHeap && (
|
||||||
<div
|
<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"
|
title="In active heap"
|
||||||
>
|
>
|
||||||
<ShoppingBasket className="h-3 w-3" />
|
<ShoppingBasket className="h-3 w-3" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{photo.is_duplicate && (
|
{photo.is_duplicate && (
|
||||||
<div
|
<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)"
|
title="Duplicate (matches another photo's hash)"
|
||||||
>
|
>
|
||||||
<Copy className="h-3 w-3" />
|
<Copy className="h-3 w-3" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{photo.is_discarded && (
|
{photo.is_discarded && (
|
||||||
<div
|
<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"
|
title="Discarded"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ export function Timeline() {
|
|||||||
}}
|
}}
|
||||||
className="flex items-end pb-1"
|
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}
|
{item.label}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
--color-text: #e8e6e0;
|
--color-text: #e8e6e0;
|
||||||
--color-text-muted: #878580;
|
--color-text-muted: #878580;
|
||||||
--color-text-faint: #4a4845;
|
--color-text-faint: #4a4845;
|
||||||
--color-primary: #3b6ed8;
|
--color-primary: #3b82f6;
|
||||||
--color-pick: #4f9e5c;
|
--color-pick: #4f9e5c;
|
||||||
--color-reject: #c25a5a;
|
--color-reject: #c25a5a;
|
||||||
--color-star: #d4a340;
|
--color-star: #d4a340;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default {
|
|||||||
text: '#e8e6e0',
|
text: '#e8e6e0',
|
||||||
'text-muted': '#878580',
|
'text-muted': '#878580',
|
||||||
'text-faint': '#4a4845',
|
'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
|
pick: '#4f9e5c', // green for picked
|
||||||
reject: '#c25a5a', // red for rejected
|
reject: '#c25a5a', // red for rejected
|
||||||
star: '#d4a340', // amber for stars
|
star: '#d4a340', // amber for stars
|
||||||
|
|||||||
Reference in New Issue
Block a user