diff --git a/frontend/src/components/WatchlistTab.tsx b/frontend/src/components/WatchlistTab.tsx
index a941bed..909c23c 100644
--- a/frontend/src/components/WatchlistTab.tsx
+++ b/frontend/src/components/WatchlistTab.tsx
@@ -223,18 +223,22 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
// Capped and centred: a card is a reading surface, and callsign-to-badge
// lines stretched across a 34-inch window are not readable, they are long.
- {/* toolbar */}
-
+ {/* Header: the counters alone, centred — they are the tab's headline.
+ Everything one INTERACTS with lives on the second row. */}
+
-
+
{t('wl.cTotal')} {counters.total}
- ·
+ |
{t('wl.cActive')} {counters.active}
- ·
+ |
{t('wl.cNeeded')} {counters.needed}
- setAddCall(e.target.value)}
+
+ {/* toolbar */}
+
+
setAddCall(e.target.value.toUpperCase())}
onKeyDown={(e) => { if (e.key === 'Enter') void add(); }} />
setAddContest(!!c)} />
@@ -243,7 +247,7 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
void add()} disabled={!addCall.trim()}>
{t('wl.add')}
- setPattern(e.target.value.toUpperCase())}
diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx
index bef57c5..dd6f1ee 100644
--- a/frontend/src/lib/i18n.tsx
+++ b/frontend/src/lib/i18n.tsx
@@ -39,7 +39,7 @@ const en: Dict = {
'cwd.tipOnIdle': 'CW decoder — on, idle until CW mode · click to disable',
'cwd.tipOff': 'CW decoder · click to enable (decodes RX audio in CW mode)',
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
- 'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', 'wl.cTotal': 'Watchlist:', 'wl.cActive': 'Active:', 'wl.cNeeded': 'Needed:', 'wl.allModes': 'All modes', 'wl.modeFilter': 'Only show spots in this mode', 'wl.patternPh': 'Auto (WWA)', 'wl.patternHint': 'Auto-add as contest: any spotted callsign CONTAINING this text joins the watchlist as a contest entry by itself. Empty = off; collected entries stay.',
+ 'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', 'wl.cTotal': 'Watchlist:', 'wl.cActive': 'Active:', 'wl.cNeeded': 'Needed:', 'wl.allModes': 'All modes', 'wl.modeFilter': 'Only show spots in this mode', 'wl.patternPh': 'Auto contest (e.g. WWA)', 'wl.patternHint': 'Auto-add as contest: any spotted callsign CONTAINING this text joins the watchlist as a contest entry by itself. Empty = off; collected entries stay.',
'wl.contestHint': 'Contest station: worked/needed is judged against the current UTC day — at 00:00 UTC every slot can be worked again.',
'wl.searchPh': 'Search…', 'wl.famAll': 'All', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
'wl.activeOnly': 'Active only', 'wl.neededOnly': 'Needed only',
@@ -563,7 +563,7 @@ const fr: Dict = {
'cwd.tipOnIdle': 'Décodeur CW — actif, en veille hors mode CW · clic pour désactiver',
'cwd.tipOff': 'Décodeur CW · clic pour activer (décode l’audio RX en mode CW)',
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
- 'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', 'wl.cTotal': 'Watchlist :', 'wl.cActive': 'Actives :', 'wl.cNeeded': 'Manquantes :', 'wl.allModes': 'Tous les modes', 'wl.modeFilter': 'Ne montrer que les spots de ce mode', 'wl.patternPh': 'Auto (WWA)', 'wl.patternHint': "Ajout auto comme contest : tout indicatif spotté CONTENANT ce texte rejoint la watchlist en entrée contest tout seul. Vide = désactivé ; les entrées déjà collectées restent.",
+ 'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', 'wl.cTotal': 'Watchlist :', 'wl.cActive': 'Actives :', 'wl.cNeeded': 'Manquantes :', 'wl.allModes': 'Tous les modes', 'wl.modeFilter': 'Ne montrer que les spots de ce mode', 'wl.patternPh': 'Auto contest (ex. WWA)', 'wl.patternHint': "Ajout auto comme contest : tout indicatif spotté CONTENANT ce texte rejoint la watchlist en entrée contest tout seul. Vide = désactivé ; les entrées déjà collectées restent.",
'wl.contestHint': "Station contest : contacté/manquant est jugé sur la journée UTC courante — à 00:00 UTC chaque créneau redevient à faire.",
'wl.searchPh': 'Chercher…', 'wl.famAll': 'Tous', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
'wl.activeOnly': 'Actifs seulement', 'wl.neededOnly': 'Manquants seulement',