From 8764c59a01f66eb5af864d3b99c2f72e92419ed5 Mon Sep 17 00:00:00 2001 From: itsrubberduck Date: Fri, 10 Jul 2026 16:13:27 +0200 Subject: [PATCH] feat(live-atc): add RadioPanel as the cockpit's primary frequency control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves frequency handling out of the HUD header — where it lived as two compact chips behind a dropdown — into a first-class module at the top of the main column, styled like a physical radio stack: STANDBY and ACTIVE windows flanking a circular swap button, the published-channel row below, and the readability control in the panel's own corner rather than as a separate global chip. Behaviour is unchanged: HoldSelect stays the interaction primitive for preset selection and manual free-tune entry, and swap/preset/readability all route to the same useFrequencyPresets and useRadioSpeech handlers. Tapping a channel chip stages it in standby (setStandbyFrequencyFromList), matching the old preset behaviour. The header's hud-center wrapper and the freq-chip/freq-stack/signal-chip styles it carried are removed; the freq-option/freq-manual menu styles move into the panel. The active frequency scales down via clamp() so seven monospace digits cannot overflow the window at 375px. Co-Authored-By: Claude Opus 4.8 --- .../live-atc/cockpit/RadioPanel.vue | 545 ++++++++++++++++++ app/pages/live-atc.vue | 413 +------------ 2 files changed, 570 insertions(+), 388 deletions(-) create mode 100644 app/components/live-atc/cockpit/RadioPanel.vue diff --git a/app/components/live-atc/cockpit/RadioPanel.vue b/app/components/live-atc/cockpit/RadioPanel.vue new file mode 100644 index 0000000..ea7215e --- /dev/null +++ b/app/components/live-atc/cockpit/RadioPanel.vue @@ -0,0 +1,545 @@ + + + + + diff --git a/app/pages/live-atc.vue b/app/pages/live-atc.vue index c9010c4..a2bc54a 100644 --- a/app/pages/live-atc.vue +++ b/app/pages/live-atc.vue @@ -165,130 +165,6 @@ -
-
-
- - - - - - - - - - -
- - -
- - - - -
-
+ +
+ +
+
{ box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.28); } -.signal-bar { - width: 3px; - height: 12px; - background: color-mix(in srgb, var(--text) 20%, transparent); - border-radius: 1px; - transition: all 0.3s ease; -} - -.signal-bar:nth-child(1) { height: 4px; } -.signal-bar:nth-child(2) { height: 6px; } -.signal-bar:nth-child(3) { height: 8px; } -.signal-bar:nth-child(4) { height: 10px; } -.signal-bar:nth-child(5) { height: 12px; } - -.signal-bar.signal-active { - background: #22d3ee; - box-shadow: 0 0 4px #22d3ee; -} - -.swap-animation { - transform: rotate(180deg); - transition: transform 0.5s ease; -} - @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } @@ -1946,14 +1820,6 @@ onUnmounted(() => { gap: 10px; min-width: 0; } -.hud-center { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 0 8px; -} .hud-right { display: flex; gap: 8px; @@ -2283,7 +2149,6 @@ onUnmounted(() => { justify-content: center; } .hud-right .btn-label { display: none; } - .hud-center { padding: 0; } } .pm-body { @@ -2409,207 +2274,6 @@ onUnmounted(() => { border-color: color-mix(in srgb, var(--accent) 32%, transparent); } -/* Frequency control group in the top bar: SBY (top) + ACT (bottom) + swap */ -.freq-control-group { - display: inline-flex; - align-items: stretch; - min-height: 56px; - overflow: hidden; - border-radius: 14px; - border: 1px solid rgba(34, 211, 238, 0.35); - background: rgba(34, 211, 238, 0.09); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 3%, transparent); -} -.freq-stack { - display: flex; - flex-direction: column; - min-width: 0; -} -.freq-chip { - display: flex; - align-items: baseline; - justify-content: flex-end; - gap: 8px; - min-width: 130px; - padding: 4px 12px; - background: transparent; - line-height: 1.1; - transition: background 120ms ease, transform 80ms ease; - cursor: pointer; -} -.freq-chip-sby { - border-bottom: 1px solid rgba(34, 211, 238, 0.18); - padding-top: 5px; - padding-bottom: 4px; -} -.freq-chip-act { - padding-top: 4px; - padding-bottom: 5px; -} -.freq-chip:hover { - background: rgba(34, 211, 238, 0.08); -} -.freq-chip.is-open { - background: rgba(34, 211, 238, 0.2); - transform: scale(0.99); -} -.freq-swap-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 38px; - min-height: 100%; - border-left: 1px solid rgba(34, 211, 238, 0.26); - color: #67e8f9; - background: rgba(34, 211, 238, 0.08); - transition: background 120ms ease, color 120ms ease; - -webkit-tap-highlight-color: transparent; -} -.freq-swap-btn:hover { - color: #cffafe; - background: rgba(34, 211, 238, 0.16); -} -.freq-swap-btn:active { - background: rgba(34, 211, 238, 0.22); -} -.freq-chip-tag { - font-size: 9px; - letter-spacing: 0.2em; - color: color-mix(in srgb, var(--text) 50%, transparent); -} -.freq-chip-value { - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 16px; - font-weight: 700; - color: var(--text); -} -.freq-chip-value-sm { - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 13px; - font-weight: 600; - color: color-mix(in srgb, var(--text) 78%, transparent); -} - -/* Dropdown option row with source label on the right */ -.freq-option { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - width: 100%; -} -.freq-option-main { - display: flex; - flex-direction: column; - gap: 0; - min-width: 0; -} -.freq-option-label { - font-size: 13px; - font-weight: 600; - color: var(--text); - line-height: 1.1; -} -.freq-option-sub { - font-size: 11px; - color: color-mix(in srgb, var(--text) 55%, transparent); - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - line-height: 1.1; -} -.freq-option-source { - font-size: 9px; - letter-spacing: 0.14em; - text-transform: uppercase; - color: rgba(103, 232, 249, 0.78); - padding: 2px 6px; - border-radius: 999px; - border: 1px solid rgba(34, 211, 238, 0.4); - background: rgba(34, 211, 238, 0.08); - white-space: nowrap; - 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 var(--border); -} -.freq-manual--header { - margin-top: 0; - margin-bottom: 4px; - padding-top: 0; - padding-bottom: 6px; - border-top: none; - border-bottom: 1px solid var(--border); -} -.freq-manual-input { - flex: 1 1 auto; - min-width: 0; - padding: 7px 10px; - border-radius: 10px; - border: 1px solid color-mix(in srgb, var(--text) 16%, transparent); - background: color-mix(in srgb, var(--text) 4%, transparent); - color: var(--text); - font-size: 13px; - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - outline: none; -} -.freq-manual-input::placeholder { - color: var(--t4); - 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; - align-items: center; - gap: 6px; - padding: 8px 14px; - border-radius: 999px; - border: 1px solid rgba(34, 211, 238, 0.4); - background: rgba(34, 211, 238, 0.1); - color: #67e8f9; - font-size: 13px; - font-weight: 600; - transition: border-color 120ms ease, background 120ms ease, transform 80ms ease; -} -.preset-action-chip.standby { - border-color: rgba(245, 158, 11, 0.4); - background: rgba(245, 158, 11, 0.1); - color: #fbbf24; -} -.preset-action-chip.is-open { - transform: scale(0.97); -} - /* Unified segmented control (voice / text, etc.) */ .pm-seg { display: flex; @@ -2642,33 +2306,6 @@ onUnmounted(() => { background: #22d3ee; } -/* Readability / signal trigger in the top bar */ -.signal-chip { - display: inline-flex; - align-items: center; - justify-content: center; - width: 46px; - height: 46px; - padding: 0; - border-radius: 14px; - border: 1px solid rgba(34, 211, 238, 0.35); - background: rgba(34, 211, 238, 0.09); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 3%, transparent); - transition: background 120ms ease, transform 80ms ease; -} -.signal-chip:hover { - background: rgba(34, 211, 238, 0.16); -} -.signal-chip.is-open { - background: rgba(34, 211, 238, 0.18); - transform: scale(0.97); -} -.signal-chip .signal-bars { - display: inline-flex; - align-items: flex-end; - gap: 2px; -} - /* Tablet / desktop: use the extra space ----------------------------------- */ @media (min-width: 1024px) { .pm-lograil {