diff --git a/app/components/HoldSelect.vue b/app/components/HoldSelect.vue index 342251c..5694835 100644 --- a/app/components/HoldSelect.vue +++ b/app/components/HoldSelect.vue @@ -239,7 +239,8 @@ onBeforeUnmount(() => { {{ opt.sublabel }} -

Keine Optionen

+

Keine Optionen

+ diff --git a/app/pages/pm.vue b/app/pages/pm.vue index d9def89..a445cb8 100644 --- a/app/pages/pm.vue +++ b/app/pages/pm.vue @@ -353,6 +353,20 @@ + + @@ -3857,6 +3885,42 @@ const onPresetSelectStandby = (opt: { value: string | number }) => { if (entry) setStandbyFrequencyFromList(entry) } +// --- Manual frequency entry (free-tune any VHF airband channel) -------------- +const manualFreqActive = ref('') +const manualFreqStandby = ref('') + +// Accepts inputs like "121.5", "121,500" or "118" and normalises to a valid +// VHF airband frequency string (118.000–136.975). Returns null when invalid so +// callers/UI can disable the action. +function normalizeManualFreq(input: string): string | null { + const raw = input.trim().replace(',', '.') + if (!raw) return null + const num = Number(raw) + if (!Number.isFinite(num) || num < 118 || num >= 137) return null + return num.toFixed(3) +} + +function applyManualFrequency(target: 'active' | 'standby', close?: () => void) { + const model = target === 'active' ? manualFreqActive : manualFreqStandby + const freq = normalizeManualFreq(model.value) + if (!freq) return + + if (target === 'active') { + if (frequencies.value.active !== freq) { + // Tuning away from the current frequency — cut any in-progress ATC speech + // so the pilot no longer "hears" the controller on the old channel. + stopCurrentSpeech() + frequencies.value.standby = frequencies.value.active + frequencies.value.active = freq + } + } else { + frequencies.value.standby = freq + } + + model.value = '' + close?.() +} + // Readability / signal strength quick-select (top bar) const readabilityOptions = [ { value: 5, label: 'Excellent', sublabel: 'Readability 5', color: '#22c55e' }, @@ -4720,6 +4784,56 @@ onUnmounted(() => { flex-shrink: 0; } +/* Manual free-tune frequency entry inside the freq hold-select menu */ +.freq-manual { + display: flex; + align-items: center; + gap: 6px; + margin-top: 4px; + padding-top: 6px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} +.freq-manual-input { + flex: 1 1 auto; + min-width: 0; + padding: 7px 10px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.16); + background: rgba(255, 255, 255, 0.04); + color: #fff; + font-size: 13px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + outline: none; +} +.freq-manual-input::placeholder { + color: rgba(255, 255, 255, 0.35); + font-family: inherit; +} +.freq-manual-input:focus { + border-color: rgba(34, 211, 238, 0.6); + background: rgba(34, 211, 238, 0.06); +} +.freq-manual-btn { + flex-shrink: 0; + padding: 7px 12px; + border-radius: 10px; + border: 1px solid rgba(34, 211, 238, 0.4); + background: rgba(34, 211, 238, 0.12); + color: #67e8f9; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.08em; + cursor: pointer; + transition: background 90ms ease, opacity 90ms ease; +} +.freq-manual-btn:hover { + background: rgba(34, 211, 238, 0.2); +} +.freq-manual-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + /* Preset hold-select trigger buttons (freq tab) */ .preset-action-chip { display: inline-flex;