style(watchlist): a pass toward DXHunter's look

The operator preferred the original's aesthetics. Adopted: the signature pink
callsign (a fixed identity colour, like the cluster palette), counters as
coloured pills, a quieter card surface with a hover, a stronger needed bar on
spot rows, and the lightning bolt back on the DXpedition badge. Everything
else stays on theme tokens.
This commit is contained in:
2026-08-30 13:35:37 +02:00
parent 0b909a4d63
commit 25eda98612
2 changed files with 21 additions and 16 deletions
+4 -2
View File
@@ -4,11 +4,13 @@
"date": "", "date": "",
"en": [ "en": [
"Bulk operations work on any size of selection — setting a field, fixing frequencies, deleting, marking uploads and exporting the selection all failed with “too many SQL variables” past a few tens of thousands of QSOs. Statements are now issued in slices.", "Bulk operations work on any size of selection — setting a field, fixing frequencies, deleting, marking uploads and exporting the selection all failed with “too many SQL variables” past a few tens of thousands of QSOs. Statements are now issued in slices.",
"Elecraft console: the power meter reads in real watts. The K3s bargraph is relative to a range that flips at 12 W — calibrated against a real radios full table, the PC setting picks the range and the bar converts to watts." "Elecraft console: the power meter reads in real watts. The K3s bargraph is relative to a range that flips at 12 W — calibrated against a real radios full table, the PC setting picks the range and the bar converts to watts.",
"Watchlist: a visual pass toward DXHunters look — pink callsigns, counter pills, quieter cards with a hover, the ⚡ back on the DXpedition badge."
], ],
"fr": [ "fr": [
"Les opérations groupées fonctionnent quelle que soit la taille de la sélection — définir un champ, corriger des fréquences, supprimer, marquer les uploads et exporter la sélection échouaient avec « too many SQL variables » au-delà de quelques dizaines de milliers de QSO. Les requêtes sont désormais émises par tranches.", "Les opérations groupées fonctionnent quelle que soit la taille de la sélection — définir un champ, corriger des fréquences, supprimer, marquer les uploads et exporter la sélection échouaient avec « too many SQL variables » au-delà de quelques dizaines de milliers de QSO. Les requêtes sont désormais émises par tranches.",
"Console Elecraft : le wattmètre lit en vrais watts. Le bargraph du K3 est relatif à une gamme qui bascule à 12 W — calibré sur la table complète dune vraie radio, le réglage PC choisit la gamme et la barre se convertit en watts." "Console Elecraft : le wattmètre lit en vrais watts. Le bargraph du K3 est relatif à une gamme qui bascule à 12 W — calibré sur la table complète dune vraie radio, le réglage PC choisit la gamme et la barre se convertit en watts.",
"Watchlist : une passe visuelle vers le look DXHunter — indicatifs roses, compteurs en pastilles, cartes plus feutrées avec survol, le ⚡ de retour sur le badge DXpedition."
] ]
}, },
{ {
+17 -14
View File
@@ -247,15 +247,18 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
<div className="flex flex-col min-h-0 flex-1 gap-2 p-2 w-full max-w-5xl mx-auto"> <div className="flex flex-col min-h-0 flex-1 gap-2 p-2 w-full max-w-5xl mx-auto">
{/* Header: the counters alone, centred — they are the tab's headline. {/* Header: the counters alone, centred — they are the tab's headline.
Everything one INTERACTS with lives on the second row. */} Everything one INTERACTS with lives on the second row. */}
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground"> <div className="flex items-center justify-center gap-2.5 text-xs text-muted-foreground">
<Eye className="size-4 text-primary shrink-0" /> <Eye className="size-4 text-primary shrink-0" />
<span> <span className="flex items-center gap-1.5">{t('wl.cTotal')}
{t('wl.cTotal')} <b className="text-foreground">{counters.total}</b> <b className="px-2 py-0.5 rounded bg-muted text-foreground">{counters.total}</b></span>
<span className="mx-1.5 opacity-50">|</span> <span className="opacity-40">|</span>
{t('wl.cActive')} <b className="text-info">{counters.active}</b> <span className="flex items-center gap-1.5">{t('wl.cActive')}
<span className="mx-1.5 opacity-50">|</span> <b className="px-2 py-0.5 rounded text-info border border-info/30 bg-info/10">{counters.active}</b></span>
{t('wl.cNeeded')} <b className="text-warning">{counters.needed}</b> <span className="opacity-40">|</span>
</span> <span className="flex items-center gap-1.5">{t('wl.cNeeded')}
<b className={cn('px-2 py-0.5 rounded border', counters.needed > 0
? 'text-warning border-warning/40 bg-warning/10'
: 'text-muted-foreground border-border bg-muted/40')}>{counters.needed}</b></span>
</div> </div>
{/* toolbar */} {/* toolbar */}
<div className="flex items-start justify-between gap-x-3 gap-y-1.5 flex-wrap"> <div className="flex items-start justify-between gap-x-3 gap-y-1.5 flex-wrap">
@@ -328,11 +331,11 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
const list = neededOnly ? all.filter((s) => !workedFor(e, s)) : all; const list = neededOnly ? all.filter((s) => !workedFor(e, s)) : all;
return ( return (
<div key={e.callsign} <div key={e.callsign}
className={cn('rounded-lg border bg-card p-3', className={cn('rounded-lg border bg-card/70 p-3 transition-colors hover:bg-accent/20',
needed > 0 ? 'border-warning/50' : 'border-border', needed > 0 ? 'border-warning/40' : 'border-border/70',
e.isContest && 'border-l-4 border-l-warning')}> e.isContest && 'border-l-4 border-l-warning')}>
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<span className="text-lg font-bold font-mono text-primary">{e.callsign}</span> <span className="text-lg font-bold font-mono" style={{ color: '#f472b6' }}>{e.callsign}</span>
{isOnAir(e) && chip('var(--danger)', t('wl.onAir'), 'animate-pulse')} {isOnAir(e) && chip('var(--danger)', t('wl.onAir'), 'animate-pulse')}
{e.isContest && ( {e.isContest && (
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-bold bg-warning-muted text-warning-muted-foreground border border-warning-border" <span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-bold bg-warning-muted text-warning-muted-foreground border border-warning-border"
@@ -340,7 +343,7 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
<Trophy className="size-3" /> {t('wl.contest')} <Trophy className="size-3" /> {t('wl.contest')}
</span> </span>
)} )}
{e.isExpedition && chip('var(--chart-5)', t('wl.expedition'))} {e.isExpedition && chip('var(--chart-5)', '⚡ ' + t('wl.expedition'))}
{e.clubLogTotalQSOs > 0 && <span className="text-[11px] text-muted-foreground">{e.clubLogTotalQSOs.toLocaleString()} QSOs{e.clubLogQSOs24h > 0 ? ` · ${e.clubLogQSOs24h}/24h` : ''}</span>} {e.clubLogTotalQSOs > 0 && <span className="text-[11px] text-muted-foreground">{e.clubLogTotalQSOs.toLocaleString()} QSOs{e.clubLogQSOs24h > 0 ? ` · ${e.clubLogQSOs24h}/24h` : ''}</span>}
{e.clubLogHasOQRS && chip('var(--success)', 'OQRS')} {e.clubLogHasOQRS && chip('var(--success)', 'OQRS')}
{e.clubLogLiveStream && ( {e.clubLogLiveStream && (
@@ -385,8 +388,8 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
onClick={() => onSpotSelect?.(s)} onClick={() => onSpotSelect?.(s)}
onDoubleClick={() => onSpotClick?.(s)} onDoubleClick={() => onSpotClick?.(s)}
title={t('wl.spotTip')} title={t('wl.spotTip')}
className={cn('w-full flex items-center gap-2 px-2 py-1.5 rounded text-[11px] bg-muted/40 hover:bg-muted text-left', className={cn('w-full flex items-center gap-2 px-2 py-1.5 rounded text-[11px] bg-muted/25 hover:bg-muted/70 transition-colors text-left',
!done && 'border-l-2 border-warning')}> !done && 'border-l-[3px] border-warning')}>
{done && <span className='font-bold shrink-0 text-success'></span>} {done && <span className='font-bold shrink-0 text-success'></span>}
<span className="font-mono font-bold text-info shrink-0">{s.dx_call}</span> <span className="font-mono font-bold text-info shrink-0">{s.dx_call}</span>
<span className="text-muted-foreground truncate flex-1 min-w-0 max-w-56">{(s as any).country ?? ''}</span> <span className="text-muted-foreground truncate flex-1 min-w-0 max-w-56">{(s as any).country ?? ''}</span>