feat(sat): the mode and the CTCSS tone where they can be seen
Both were already on screen, in a grey eleven-pixel footnote under the frequencies, which is not where either of them belongs. FM and SSB are two different evenings. One is a channel, a tone and a handheld; the other is a passband, a beam and a VFO walked as the Doppler moves. So the mode is a coloured badge — in the readout, in the transponder list where a satellite offering both makes the choice matter, and in the header so it survives hiding the readout column, which is exactly what an operator does when they want the map full width during a pass. The tone gets the weight of a frequency because that is what it is worth: a repeater called without it does not answer, and what the operator hears is an empty channel and concludes the satellite is not up. And when a bird needs no tone it now says so — a blank line could not tell "no tone" from "OpsLog does not know", and the difference is a pass. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
+4
-2
@@ -11,7 +11,8 @@
|
||||
"Hovering a satellite on the map now says what a pass is worth — elevation and azimuth, distance and whether it is closing or going away, rise and set with the countdown, and how high it will get. It no longer closes itself every five seconds while you read it.",
|
||||
"The satellite footprint is drawn for the selected bird only. A footprint is thousands of kilometres across, and a dozen of them overlapped into a wash of circles that hid the coastline, the ground track and the satellites themselves.",
|
||||
"The frequency plan goes from 25 satellites to 44, cut from Celestrak, PE0SAT and the SatNOGS transponder database instead of typed by hand — the nine Tevel-2 satellites, the Chinese space station, AO-27, AO-123, RS-44 and twenty more. Twelve that had re-entered are gone, first-generation Tevel among them. Your own file is merged rather than replaced: satellites you have never seen are added, and any frequency you corrected stands.",
|
||||
"A satellite is now found by its catalog number rather than by its name. \"RADFXSAT (FOX-1B)\" and \"AO-91\" are the same bird, and so are \"TIANYAN 01\" and \"TO-108\" — the second pair never met before, so TO-108 tracked nothing."
|
||||
"A satellite is now found by its catalog number rather than by its name. \"RADFXSAT (FOX-1B)\" and \"AO-91\" are the same bird, and so are \"TIANYAN 01\" and \"TO-108\" — the second pair never met before, so TO-108 tracked nothing.",
|
||||
"On the satellite tab, the mode is a coloured badge instead of a grey footnote, and an FM bird shows its CTCSS tone with the same weight as a frequency — a repeater called without its tone does not answer, and the operator hears an empty channel and concludes the satellite is not up. When there is no tone it says so, rather than leaving a blank that could mean either. The mode also appears in the transponder list and in the header, so it survives hiding the readout column."
|
||||
],
|
||||
"fr": [
|
||||
"Toutes les interfaces de rotor sont désormais dans Réglages ▸ Rotator, et la page satellite ne fait qu’en choisir une. EasyComm et PstRotator se configuraient dans les réglages satellite pendant que les autres se configuraient dans la liste des rotors : un même pylône était décrit deux fois. Ce que vous aviez réglé est déplacé dans la liste et sélectionné automatiquement.",
|
||||
@@ -22,7 +23,8 @@
|
||||
"Le survol d’un satellite sur la carte indique désormais ce que vaut le passage — élévation et azimut, distance et si elle diminue ou augmente, lever et coucher avec le décompte, et la hauteur qu’il atteindra. L’infobulle ne se referme plus toutes les cinq secondes pendant qu’on la lit.",
|
||||
"L’empreinte au sol n’est tracée que pour le satellite sélectionné. Une empreinte fait des milliers de kilomètres, et une douzaine se superposaient en un lavis de cercles qui masquait le trait de côte, la trace au sol et les satellites eux-mêmes.",
|
||||
"Le plan de fréquences passe de 25 à 44 satellites, généré depuis Celestrak, PE0SAT et la base de transpondeurs SatNOGS au lieu d’être saisi à la main — les neuf Tevel-2, la station spatiale chinoise, AO-27, AO-123, RS-44 et vingt autres. Douze rentrés dans l’atmosphère ont été retirés, dont les Tevel de première génération. Votre fichier est fusionné et non remplacé : les satellites inconnus sont ajoutés, et vos corrections de fréquence restent.",
|
||||
"Un satellite est désormais trouvé par son numéro de catalogue plutôt que par son nom. « RADFXSAT (FOX-1B) » et « AO-91 » sont le même oiseau, tout comme « TIANYAN 01 » et « TO-108 » — ces deux-là ne se rencontraient jamais, donc TO-108 ne suivait rien."
|
||||
"Un satellite est désormais trouvé par son numéro de catalogue plutôt que par son nom. « RADFXSAT (FOX-1B) » et « AO-91 » sont le même oiseau, tout comme « TIANYAN 01 » et « TO-108 » — ces deux-là ne se rencontraient jamais, donc TO-108 ne suivait rien.",
|
||||
"Sur l’onglet satellite, le mode est une pastille colorée au lieu d’une note grise, et un satellite FM affiche sa tonalité CTCSS avec le même poids qu’une fréquence — un relais appelé sans sa tonalité ne répond pas, et l’OM entend un canal vide et en conclut que le satellite n’est pas passé. Quand il n’y a pas de tonalité, c’est écrit, plutôt qu’un blanc qui pourrait vouloir dire l’un ou l’autre. Le mode apparaît aussi dans la liste des transpondeurs et dans l’en-tête, donc il survit au masquage de la colonne de droite."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -175,6 +175,26 @@ function satTip(p: Position, pass: Pass | undefined, t: (k: string) => string):
|
||||
return out.join('');
|
||||
}
|
||||
|
||||
// ModeBadge says FM or SSB where it cannot be missed.
|
||||
//
|
||||
// The mode used to be one word in a grey 11-pixel footnote under the
|
||||
// frequencies, and it is not a footnote: FM and SSB are two different evenings.
|
||||
// One is a channel, a tone and a handheld; the other is a passband, a beam and a
|
||||
// VFO that has to be walked as the Doppler moves. An operator who reads the
|
||||
// wrong one calls into silence.
|
||||
function ModeBadge({ mode, className }: { mode?: string; className?: string }) {
|
||||
if (!mode) return null;
|
||||
const colour = MODE_COLOUR[mode] ?? 'var(--muted-foreground)';
|
||||
return (
|
||||
<span
|
||||
className={cn('shrink-0 rounded px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide border', className)}
|
||||
style={{ color: colour, borderColor: `color-mix(in srgb, ${colour} 45%, transparent)`, background: `color-mix(in srgb, ${colour} 14%, transparent)` }}
|
||||
>
|
||||
{mode}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function ModeDot({ mode }: { mode: string }) {
|
||||
const colour = MODE_COLOUR[mode];
|
||||
if (!colour) return null;
|
||||
@@ -599,11 +619,27 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) {
|
||||
value={tpIdx}
|
||||
onChange={(e) => setTpIdx(Number(e.target.value))}
|
||||
>
|
||||
{/* The mode belongs in the choice itself. A satellite with an FM
|
||||
repeater and a linear transponder offers two labels that both
|
||||
read like a name, and picking the wrong one is a whole pass
|
||||
spent on the wrong kind of radio. */}
|
||||
{bird!.transponders!.map((x, i) => (
|
||||
<option key={i} value={i}>{x.label}</option>
|
||||
<option key={i} value={i}>
|
||||
{x.label} — {x.mode}{x.ctcss ? ` ${x.ctcss.toFixed(1)}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
{/* Also in the header, so the mode and the tone survive hiding the
|
||||
readout column — which is exactly what an operator does when they
|
||||
want the map full width during a pass. */}
|
||||
<ModeBadge mode={tp?.mode} />
|
||||
{tp?.mode === 'FM' && !!tp.ctcss && (
|
||||
<span className="shrink-0 rounded px-1.5 py-0.5 text-[10px] font-semibold tabular-nums border border-caution/45 bg-caution/10 text-caution"
|
||||
title={t('sat.toneHint')}>
|
||||
{tp.ctcss.toFixed(1)}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant={tracking?.on ? 'default' : 'outline'}
|
||||
@@ -684,7 +720,7 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) {
|
||||
inPass ? 'border-success/60' : 'border-border')}>
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="font-medium text-sm truncate">{bird?.name ?? '—'}</span>
|
||||
<span className="text-[11px] text-muted-foreground truncate">{tp?.label ?? ''}</span>
|
||||
<ModeBadge mode={tp?.mode} className="self-center" />
|
||||
</div>
|
||||
|
||||
{bird?.geostationary ? (
|
||||
@@ -783,17 +819,52 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) {
|
||||
|
||||
{/* What to tune. */}
|
||||
<div className="rounded-lg border border-border bg-card p-2">
|
||||
{/* The mode leads, because it decides everything below it. */}
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<ModeBadge mode={tp?.mode} />
|
||||
<span className="text-[11px] text-muted-foreground truncate">{tp?.label ?? '—'}</span>
|
||||
<div className="flex-1" />
|
||||
{tp?.inverting && (
|
||||
<span className="shrink-0 text-[10px] uppercase tracking-wide text-warning">{t('sat.inverting')}</span>
|
||||
)}
|
||||
{tp?.linear && (
|
||||
<span className="shrink-0 text-[10px] text-muted-foreground tabular-nums">
|
||||
{Math.round((tp.down_hi - tp.down_lo) / 1000)} kHz
|
||||
</span>
|
||||
)}
|
||||
{bird?.geostationary && (
|
||||
<span className="shrink-0 text-[10px] uppercase tracking-wide text-muted-foreground">{t('sat.geo')}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<FreqRow label={t('sat.down')} hz={tuning?.down_hz ?? 0} nominal={tuning?.nominal_down ?? 0} />
|
||||
<FreqRow label={t('sat.up')} hz={tuning?.up_hz ?? 0} nominal={tuning?.nominal_up ?? 0} />
|
||||
</div>
|
||||
<div className="mt-1.5 flex flex-wrap gap-x-3 gap-y-0.5 text-[11px] text-muted-foreground">
|
||||
{!!tp?.mode && <span>{tp.mode}</span>}
|
||||
{!!tp?.ctcss && <span>CTCSS {tp.ctcss.toFixed(1)}</span>}
|
||||
{tp?.inverting && <span>{t('sat.inverting')}</span>}
|
||||
{tp?.linear && <span>{Math.round((tp.down_hi - tp.down_lo) / 1000)} kHz</span>}
|
||||
{bird?.geostationary && <span>{t('sat.geo')}</span>}
|
||||
|
||||
{/* The tone, on the FM birds, with the same weight as a frequency.
|
||||
It IS one, as far as the outcome goes: a repeater called without
|
||||
its tone does not answer, and the operator hears an empty
|
||||
channel and concludes the satellite is not up. Said explicitly
|
||||
when there is none, too — a blank line cannot tell "no tone"
|
||||
from "OpsLog does not know". */}
|
||||
{tp?.mode === 'FM' && (
|
||||
<div className="mt-1.5 pt-1.5 border-t border-border/60 flex items-baseline gap-2">
|
||||
<span className="w-10 text-[10px] uppercase tracking-wide text-muted-foreground">
|
||||
{t('sat.tone')}
|
||||
</span>
|
||||
{tp.ctcss ? (
|
||||
<>
|
||||
<span className="text-base font-semibold tabular-nums text-caution">
|
||||
{tp.ctcss.toFixed(1)} Hz
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground">{t('sat.toneHint')}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-[11px] text-muted-foreground">{t('sat.toneNone')}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* What is coming. */}
|
||||
|
||||
@@ -604,6 +604,7 @@ const en: Dict = {
|
||||
'sat.aos': 'Rises in', 'sat.los': 'Sets in', 'sat.rise': 'Rise', 'sat.peak': 'Peak', 'sat.set': 'Set',
|
||||
'sat.range': 'Distance', 'sat.altitude': 'Altitude', 'sat.footprint': 'Footprint',
|
||||
'sat.tipEl': 'Elevation', 'sat.tipAz': 'Azimuth', 'sat.tipRange': 'Distance', 'sat.tipAlt': 'Altitude',
|
||||
'sat.tone': 'Tone', 'sat.toneHint': 'CTCSS on the uplink', 'sat.toneNone': 'no tone needed',
|
||||
'sat.tipAos': 'Rises', 'sat.tipLos': 'Sets', 'sat.tipMaxEl': 'Peak',
|
||||
'sat.tipBelow': 'below the horizon', 'sat.tipNoPass': 'no pass in the prediction window',
|
||||
'sat.approaching': 'approaching', 'sat.receding': 'receding', 'sat.below': 'below the horizon',
|
||||
@@ -1218,6 +1219,7 @@ const fr: Dict = {
|
||||
'sat.aos': 'Lever dans', 'sat.los': 'Coucher dans', 'sat.rise': 'Lever', 'sat.peak': 'Culmination', 'sat.set': 'Coucher',
|
||||
'sat.range': 'Distance', 'sat.altitude': 'Altitude', 'sat.footprint': 'Empreinte',
|
||||
'sat.tipEl': 'Élévation', 'sat.tipAz': 'Azimut', 'sat.tipRange': 'Distance', 'sat.tipAlt': 'Altitude',
|
||||
'sat.tone': 'Tonalité', 'sat.toneHint': 'CTCSS sur la montée', 'sat.toneNone': 'aucune tonalité requise',
|
||||
'sat.tipAos': 'Lever', 'sat.tipLos': 'Coucher', 'sat.tipMaxEl': 'Culmination',
|
||||
'sat.tipBelow': 'sous l’horizon', 'sat.tipNoPass': 'aucun passage dans la fenêtre de prévision',
|
||||
'sat.approaching': 'se rapproche', 'sat.receding': 's’éloigne', 'sat.below': 'sous l’horizon',
|
||||
|
||||
Reference in New Issue
Block a user