diff --git a/.env.example b/.env.example index e3495bf..f23ac27 100644 --- a/.env.example +++ b/.env.example @@ -67,6 +67,13 @@ LAYOUT=modern # Only uncomment if you have your own proxy running # DXSPIDER_PROXY_URL=https://your-dxspider-proxy.com +# DX Cluster source: auto | proxy | hamqth | dxspider +# auto = tries proxy first, then HamQTH, then direct telnet +# proxy = use DX Spider Proxy (set DXSPIDER_PROXY_URL above) +# hamqth = HamQTH CSV feed (HTTP, works everywhere) +# dxspider = direct telnet to DX Spider nodes (works locally/Pi) +# DX_CLUSTER_SOURCE=auto + # OpenWeatherMap API key (for local weather display) # Get a free key at https://openweathermap.org/api # OPENWEATHER_API_KEY=your_api_key_here diff --git a/scripts/setup-pi.sh b/scripts/setup-pi.sh index d3ab463..ee79218 100644 --- a/scripts/setup-pi.sh +++ b/scripts/setup-pi.sh @@ -225,6 +225,9 @@ else CHROME_CMD="chromium-browser" fi +# Trap Ctrl+Q to exit kiosk cleanly +trap 'pkill -f "chromium.*kiosk"; exit 0' SIGTERM SIGINT + $CHROME_CMD \ --kiosk \ --noerrdialogs \ @@ -237,7 +240,17 @@ $CHROME_CMD \ --overscroll-history-navigation=0 \ --disable-pinch \ --incognito \ - http://localhost:3000 + http://localhost:3000 & + +CHROME_PID=$! + +echo "OpenHamClock kiosk running (PID: $CHROME_PID)" +echo "Exit methods:" +echo " - Alt+F4 (close Chromium)" +echo " - Ctrl+Alt+T (open terminal, then: pkill -f kiosk)" +echo " - SSH in and run: pkill -f kiosk.sh" + +wait $CHROME_PID EOF chmod +x "$INSTALL_DIR/kiosk.sh" @@ -339,7 +352,14 @@ print_summary() { if [ "$KIOSK_MODE" = true ]; then echo -e " ${GREEN}Kiosk Mode:${NC} Enabled" echo " OpenHamClock will auto-start on boot in fullscreen" - echo " To disable: rm ~/.config/autostart/openhamclock-kiosk.desktop" + echo "" + echo -e " ${YELLOW}Exit kiosk:${NC}" + echo " Alt+F4 Close Chromium" + echo " Ctrl+Alt+T Open terminal (then: pkill -f kiosk)" + echo " SSH: pkill -f kiosk.sh" + echo "" + echo -e " ${YELLOW}Disable auto-start:${NC}" + echo " rm ~/.config/autostart/openhamclock-kiosk.desktop" echo "" fi diff --git a/server.js b/server.js index e335222..c53244b 100644 --- a/server.js +++ b/server.js @@ -148,7 +148,7 @@ const CONFIG = { // DX Cluster settings spotRetentionMinutes: parseInt(process.env.SPOT_RETENTION_MINUTES) || jsonConfig.dxCluster?.spotRetentionMinutes || 30, - dxClusterSource: jsonConfig.dxCluster?.source || 'auto', + dxClusterSource: process.env.DX_CLUSTER_SOURCE || jsonConfig.dxCluster?.source || 'auto', // API keys (don't expose to frontend) _openWeatherApiKey: process.env.OPENWEATHER_API_KEY || '', @@ -983,7 +983,7 @@ let dxSpiderCache = { spots: [], timestamp: 0 }; const DXSPIDER_CACHE_TTL = 90000; // 90 seconds cache - reduces reconnection frequency app.get('/api/dxcluster/spots', async (req, res) => { - const source = (req.query.source || 'auto').toLowerCase(); + const source = (req.query.source || CONFIG.dxClusterSource || 'auto').toLowerCase(); // Helper function for HamQTH (HTTP-based, works everywhere) async function fetchHamQTH() { diff --git a/src/App.jsx b/src/App.jsx index 39f1c63..b10ab3b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -460,7 +460,7 @@ const App = () => {
Kp
-
{spaceWeather?.data?.kIndex ?? '--'}
+
{solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}
Bz
@@ -596,6 +596,7 @@ const App = () => { localDate={localDate} localWeather={localWeather} spaceWeather={spaceWeather} + solarIndices={solarIndices} use12Hour={use12Hour} onTimeFormatToggle={handleTimeFormatToggle} onSettingsClick={() => setShowSettings(true)} diff --git a/src/components/DXFilterManager.jsx b/src/components/DXFilterManager.jsx index 377da25..a9c69b6 100644 --- a/src/components/DXFilterManager.jsx +++ b/src/components/DXFilterManager.jsx @@ -279,7 +279,7 @@ export const DXFilterManager = ({ filters, onFilterChange, isOpen, onClose }) =>
- Exclude List - Hide these callsigns + Exclude List - Hide DX callsigns beginning with:
0.1 && config.callsignSize <= 2 - ? `${22 * config.callsignSize}px` + fontSize: config.headerSize > 0.1 && config.headerSize <= 2 + ? `${22 * config.headerSize}px` : "22px", fontWeight: '900', color: 'var(--accent-amber)', cursor: 'pointer', fontFamily: 'Orbitron, monospace', whiteSpace: 'nowrap' }} onClick={onSettingsClick} @@ -53,7 +54,9 @@ export const Header = ({
UTC 0.1 && config.headerSize <= 2 + ? `${24 * config.headerSize}px` + : "24px", fontWeight: '700', color: 'var(--accent-cyan)', fontFamily: 'JetBrains Mono, Consolas, monospace', @@ -70,7 +73,9 @@ export const Header = ({ > LOCAL 0.1 && config.headerSize <= 2 + ? `${24 * config.headerSize}px` + : "24px", fontWeight: '700', color: 'var(--accent-amber)', fontFamily: 'JetBrains Mono, Consolas, monospace', @@ -90,8 +95,18 @@ export const Header = ({ const windLabel = localWeather.data.windUnit || 'mph'; return (
- {localWeather.data.icon} - + 0.1 && config.headerSize <= 2 + ? `${12 * config.headerSize}px` + : "12px", + }}> + {localWeather.data.icon} + + 0.1 && config.headerSize <= 2 + ? `${12 * config.headerSize}px` + : "12px", + }}> {tempF}°F/{tempC}°C
@@ -99,17 +114,17 @@ export const Header = ({ })()}
SFI - {spaceWeather?.data?.solarFlux || '--'} + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'}
K - = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> - {spaceWeather?.data?.kIndex ?? '--'} + = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}
SSN - {spaceWeather?.data?.sunspotNumber || '--'} + {solarIndices?.data?.ssn?.current || spaceWeather?.data?.sunspotNumber || '--'}
diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index de58466..54bb296 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -9,7 +9,7 @@ import { LANGUAGES } from '../lang/i18n.js'; export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { const [callsign, setCallsign] = useState(config?.callsign || ''); - const [callsignSize, setCallsignSize] = useState(config?.callsignSize || 1.0); + const [headerSize, setheaderSize] = useState(config?.headerSize || 1.0); const [gridSquare, setGridSquare] = useState(''); const [lat, setLat] = useState(config?.location?.lat || 0); const [lon, setLon] = useState(config?.location?.lon || 0); @@ -26,7 +26,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { useEffect(() => { if (config) { setCallsign(config.callsign || ''); - setCallsignSize(config.callsignSize || 1.0) + setheaderSize(config.headerSize || 1.0) setLat(config.location?.lat || 0); setLon(config.location?.lon || 0); setTheme(config.theme || 'dark'); @@ -149,7 +149,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { onSave({ ...config, callsign: callsign.toUpperCase(), - callsignSize: callsignSize, + headerSize: headerSize, location: { lat: parseFloat(lat), lon: parseFloat(lon) }, theme, layout, @@ -309,15 +309,15 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => {
{ if (e.target.value >= 0.1 && e.target.value <= 2.0) { - setCallsignSize(e.target.value) + setheaderSize(e.target.value) }}} style={{ width: '100%', diff --git a/src/hooks/useDXCluster.js b/src/hooks/useDXCluster.js index 83a7303..60a574c 100644 --- a/src/hooks/useDXCluster.js +++ b/src/hooks/useDXCluster.js @@ -26,17 +26,15 @@ export const useDXCluster = (source = 'auto', filters = {}) => { // Watchlist only mode - must match watchlist if (filters.watchlistOnly && filters.watchlist?.length > 0) { const matchesWatchlist = filters.watchlist.some(w => - spot.call?.toUpperCase().includes(w.toUpperCase()) || - spot.spotter?.toUpperCase().includes(w.toUpperCase()) + spot.call?.toUpperCase().includes(w.toUpperCase()) ); if (!matchesWatchlist) return false; } - // Exclude list - hide matching calls + // Exclude list - hide matching calls - match the call as a prefix if (filters.excludeList?.length > 0) { const isExcluded = filters.excludeList.some(exc => - spot.call?.toUpperCase().includes(exc.toUpperCase()) || - spot.spotter?.toUpperCase().includes(exc.toUpperCase()) + spot.call?.toUpperCase().startsWith(exc.toUpperCase()) ); if (isExcluded) return false; } @@ -89,7 +87,7 @@ export const useDXCluster = (source = 'auto', filters = {}) => { useEffect(() => { const fetchData = async () => { try { - const response = await fetch('/api/dxcluster/spots'); + const response = await fetch(`/api/dxcluster/spots?source=${encodeURIComponent(source)}`); if (response.ok) { const newSpots = await response.json(); diff --git a/src/hooks/useWSJTX.js b/src/hooks/useWSJTX.js index 6523a77..eb3709a 100644 --- a/src/hooks/useWSJTX.js +++ b/src/hooks/useWSJTX.js @@ -14,20 +14,20 @@ const API_URL = '/api/wsjtx'; const DECODES_URL = '/api/wsjtx/decodes'; // Generate or retrieve persistent session ID +// NOTE: Kept short (8 chars) intentionally — long UUIDs in query strings +// trigger false positives in Bitdefender and similar security software function getSessionId() { const KEY = 'ohc-wsjtx-session'; + const generate = () => Math.random().toString(36).substring(2, 10); try { let id = localStorage.getItem(KEY); - if (id && id.length >= 16) return id; - // Generate a random ID - id = (typeof crypto !== 'undefined' && crypto.randomUUID) - ? crypto.randomUUID() - : Math.random().toString(36).substring(2) + Date.now().toString(36) + Math.random().toString(36).substring(2); + if (id && id.length >= 8) return id; + id = generate(); localStorage.setItem(KEY, id); return id; } catch { // Fallback for privacy browsers that block localStorage - return Math.random().toString(36).substring(2) + Date.now().toString(36) + Math.random().toString(36).substring(2); + return generate(); } } diff --git a/src/lang/de.json b/src/lang/de.json index 8f2d6cc..7f23a69 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -9,6 +9,7 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiano", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "Höhe (m)", "station.settings.antenna": "Antenne", "station.settings.button.save": "Einstellungen Speichern", @@ -45,5 +46,12 @@ "station.settings.useLocation": "📍 Meinen Standort verwenden", "station.settings.useLocation.error1": "Standort konnte nicht ermittelt werden. Bitte manuell eingeben.", "station.settings.useLocation.error2": "Geolokalisierung wird von deinem Browser nicht unterstützt.", - "station.settings.welcome": "👋 Willkommen bei OpenHamClock!" + "station.settings.welcome": "👋 Willkommen bei OpenHamClock!", + "plugins.layers.aurora.name": "Aurora-Vorhersage", + "plugins.layers.aurora.description": "NOAA-OVATION-Aurora-Wahrscheinlichkeitsvorhersage (30 Min.)", + "plugins.layers.earthquakes.name": "Erdbeben", + "plugins.layers.earthquakes.description": "Live-USGS-Erdbebendaten (M2,5+ der letzten 24 Stunden)", + "plugins.layers.wxradar.name": "Wetterradar", + "plugins.layers.wxradar.description": "NEXRAD-Wetterradar-Überlagerung für Nordamerika", + "plugins.layers.wxradar.attribution": "Wetterdaten © Iowa State University Mesonet" } diff --git a/src/lang/en.json b/src/lang/en.json index c1b753c..49a03bd 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -9,12 +9,13 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiano", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "Altitude (m)", "station.settings.antenna": "Antenna", "station.settings.button.save": "Save Settings", "station.settings.button.save.confirm": "Settings saved to your browser", "station.settings.callsign": "Your Callsign", - "station.settings.callsignSize": "Your Callsign's size", + "station.settings.headerSize": "Your Callsign's size", "station.settings.describe": "Enter your callsign and grid square to get started. Settings are saved in your browser.", "station.settings.dx.describe": "→ Real-time DX Spider feed via our dedicated proxy service", "station.settings.dx.option1": "⭐ DX Spider Proxy (Recommended)", @@ -46,5 +47,12 @@ "station.settings.useLocation": "📍 Use my current location", "station.settings.useLocation.error1": "Could not get location. Please enter manually.", "station.settings.useLocation.error2": "Geolocation is not supported by your browser.", - "station.settings.welcome": "👋 Welcome to OpenHamClock!" + "station.settings.welcome": "👋 Welcome to OpenHamClock!", + "plugins.layers.aurora.name": "Aurora Forecast", + "plugins.layers.aurora.description": "NOAA OVATION aurora probability forecast (30-min)", + "plugins.layers.earthquakes.name": "Earthquakes", + "plugins.layers.earthquakes.description": "Live USGS earthquake data (M2.5+ from last 24 hours)", + "plugins.layers.wxradar.name": "Weather Radar", + "plugins.layers.wxradar.description": "NEXRAD weather radar overlay for North America", + "plugins.layers.wxradar.attribution": "Weather data © Iowa State University Mesonet" } diff --git a/src/lang/es.json b/src/lang/es.json index 4454640..4bda44d 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -9,12 +9,13 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiano", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "Altitud (m)", "station.settings.antenna": "Antena", "station.settings.button.save": "Guardar Configuración", "station.settings.button.save.confirm": "La configuración se guarda en tu navegador", "station.settings.callsign": "Tu Indicativo", - "station.settings.callsignSize": "El tamaño de tu Indicativo", + "station.settings.headerSize": "El tamaño de tu Indicativo", "station.settings.describe": "Ingresa tu indicativo y cuadrícula para comenzar. Tu configuración se guardará en el navegador.", "station.settings.dx.describe": "→ Feed en tiempo real de DX Spider a través de nuestro servicio proxy dedicado", "station.settings.dx.option1": "⭐ Proxy DX Spider (Recomendado)", @@ -46,5 +47,12 @@ "station.settings.useLocation": "📍 Usar Mi Ubicación Actual", "station.settings.useLocation.error1": "No se pudo obtener la ubicación. Por favor ingrésala manualmente.", "station.settings.useLocation.error2": "La geolocalización no es compatible con tu navegador.", - "station.settings.welcome": "👋 ¡Bienvenido a OpenHamClock!" + "station.settings.welcome": "👋 ¡Bienvenido a OpenHamClock!", + "plugins.layers.aurora.name": "Pronóstico de auroras", + "plugins.layers.aurora.description": "Pronóstico de probabilidad de auroras NOAA OVATION (30 min)", + "plugins.layers.earthquakes.name": "Terremotos", + "plugins.layers.earthquakes.description": "Datos sísmicos en vivo del USGS (M2.5+ de las últimas 24 horas)", + "plugins.layers.wxradar.name": "Radar meteorológico", + "plugins.layers.wxradar.description": "Superposición del radar meteorológico NEXRAD para Norteamérica", + "plugins.layers.wxradar.attribution": "Datos meteorológicos © Iowa State University Mesonet" } diff --git a/src/lang/fr.json b/src/lang/fr.json index 89e6678..cae97fc 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -9,6 +9,7 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiano", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "Altitude (m)", "station.settings.antenna": "Antenne", "station.settings.button.save": "Enregistrer les paramètres", @@ -45,5 +46,12 @@ "station.settings.useLocation": "📍 Utiliser ma position actuelle", "station.settings.useLocation.error1": "Impossible d'obtenir la position. Veuillez entrer manuellement.", "station.settings.useLocation.error2": "La géolocalisation n'est pas prise en charge par votre navigateur.", - "station.settings.welcome": "👋 Bienvenue sur OpenHamClock !" + "station.settings.welcome": "👋 Bienvenue sur OpenHamClock !", + "plugins.layers.aurora.name": "Prévision d’aurores", + "plugins.layers.aurora.description": "Prévision de probabilité d’aurores NOAA OVATION (30 min)", + "plugins.layers.earthquakes.name": "Séismes", + "plugins.layers.earthquakes.description": "Données sismiques USGS en direct (M2,5+ sur les dernières 24 heures)", + "plugins.layers.wxradar.name": "Radar météo", + "plugins.layers.wxradar.description": "Surcouche du radar météo NEXRAD pour l’Amérique du Nord", + "plugins.layers.wxradar.attribution": "Données météo © Iowa State University Mesonet" } diff --git a/src/lang/i18n.js b/src/lang/i18n.js index 8db84f2..e7df127 100644 --- a/src/lang/i18n.js +++ b/src/lang/i18n.js @@ -10,6 +10,7 @@ import translationPT from './pt.json'; import translationJA from './ja.json'; import translationIT from './it.json'; import translationNL from './nl.json'; +import translationKO from './ko.json'; export const LANGUAGES = [ { code: 'en', name: 'English', flag: '🇬🇧' }, @@ -19,6 +20,7 @@ export const LANGUAGES = [ { code: 'nl', name: 'Nederlands', flag: '🇳🇱' }, { code: 'pt', name: 'Português', flag: '🇧🇷' }, { code: 'ja', name: '日本語', flag: '🇯🇵' }, + { code: 'ko', name: '한국어', flag: '🇰🇷' }, { code: 'it', name: 'Italiano', flag: '🇮🇹' } ]; @@ -30,6 +32,7 @@ export const resources = { nl: { translation: translationNL }, pt: { translation: translationPT }, ja: { translation: translationJA }, + ko: { translation: translationKO }, it: { translation: translationIT } }; diff --git a/src/lang/it.json b/src/lang/it.json index f623702..b18d457 100644 --- a/src/lang/it.json +++ b/src/lang/it.json @@ -9,6 +9,7 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiano", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "Altitudine (m)", "station.settings.antenna": "Antenna", "station.settings.button.save": "Salva Impostazioni", @@ -45,5 +46,12 @@ "station.settings.useLocation": "📍 Usa la Mia Posizione Attuale", "station.settings.useLocation.error1": "Impossibile ottenere la posizione. Inseriscila manualmente.", "station.settings.useLocation.error2": "La geolocalizzazione non è supportata dal tuo browser.", - "station.settings.welcome": "👋 Benvenuto su OpenHamClock!" + "station.settings.welcome": "👋 Benvenuto su OpenHamClock!", + "plugins.layers.aurora.name": "Previsione dell’aurora", + "plugins.layers.aurora.description": "Previsione di probabilità dell’aurora NOAA OVATION (30 min)", + "plugins.layers.earthquakes.name": "Terremoti", + "plugins.layers.earthquakes.description": "Dati sismici USGS in tempo reale (M2,5+ delle ultime 24 ore)", + "plugins.layers.wxradar.name": "Radar meteorologico", + "plugins.layers.wxradar.description": "Sovrapposizione del radar meteorologico NEXRAD per il Nord America", + "plugins.layers.wxradar.attribution": "Dati meteo © Iowa State University Mesonet" } diff --git a/src/lang/ja.json b/src/lang/ja.json index 474ee6d..59d4447 100644 --- a/src/lang/ja.json +++ b/src/lang/ja.json @@ -9,6 +9,7 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiano", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "標高 (m)", "station.settings.antenna": "アンテナ", "station.settings.button.save": "設定を保存", @@ -45,5 +46,12 @@ "station.settings.useLocation": "📍 現在地を使用", "station.settings.useLocation.error1": "位置情報を取得できません。手動で入力してください。", "station.settings.useLocation.error2": "お使いのブラウザはジオロケーションに対応していません。", - "station.settings.welcome": "👋 OpenHamClockへようこそ!" + "station.settings.welcome": "👋 OpenHamClockへようこそ!", + "plugins.layers.aurora.name": "オーロラ予報", + "plugins.layers.aurora.description": "NOAA OVATION オーロラ出現確率予報(30分)", + "plugins.layers.earthquakes.name": "地震", + "plugins.layers.earthquakes.description": "USGSのリアルタイム地震データ(過去24時間のM2.5以上)", + "plugins.layers.wxradar.name": "気象レーダー", + "plugins.layers.wxradar.description": "北米向けNEXRAD気象レーダーのオーバーレイ", + "plugins.layers.wxradar.attribution": "気象データ © Iowa State University Mesonet" } diff --git a/src/lang/ko.json b/src/lang/ko.json new file mode 100644 index 0000000..8d703c0 --- /dev/null +++ b/src/lang/ko.json @@ -0,0 +1,57 @@ +{ + "cancel": "취소", + "station.settings.language": "언어", + "station.settings.language.en": "English", + "station.settings.language.fr": "Français", + "station.settings.language.es": "Español", + "station.settings.language.de": "Deutsch", + "station.settings.language.pt": "Português", + "station.settings.language.ja": "日本語", + "station.settings.language.it": "Italiano", + "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", + "station.settings.altitude": "고도 (m)", + "station.settings.antenna": "안테나", + "station.settings.button.save": "설정 저장하기", + "station.settings.button.save.confirm": "설정이 브라우저에 저장되었습니다.", + "station.settings.callsign": "콜사인", + "station.settings.describe": "콜사인과 그리드 스퀘어(예: PM37mp)를 입력하여 시작하세요. 설정은 브라우저에 저장됩니다.", + "station.settings.dx.describe": "→ 자체 프록시 서버를 이용한 실시간 DX Spider 연동", + "station.settings.dx.option1": "⭐ DX Spider 프록시 (권장)", + "station.settings.dx.option2": "HamQTH Cluster", + "station.settings.dx.option3": "DXWatch", + "station.settings.dx.option4": "자동 (전부 시도하기)", + "station.settings.dx.title": "DX Cluster 데이터 서버", + "station.settings.layout": "레이아웃", + "station.settings.layout.classic": "클래식", + "station.settings.layout.classic.describe": "→ HamClock과 유사한 레이아웃", + "station.settings.layout.modern": "모던", + "station.settings.layout.modern.describe": "→ 반응형 모던 그리드 레이아웃", + "station.settings.latitude": "위도", + "station.settings.locator": "그리드 스퀘어 (또는 아래에 직접 위도/경도 입력)", + "station.settings.longitude": "경도", + "station.settings.power": "출력 (W)", + "station.settings.theme": "테마", + "station.settings.theme.dark": "다크", + "station.settings.theme.dark.describe": "→ 현대적인 다크 테마 (기본)", + "station.settings.theme.legacy": "CRT", + "station.settings.theme.legacy.describe": "→ 초록 CRT 터미널 스타일", + "station.settings.theme.light": "라이트", + "station.settings.theme.light.describe": "→ 낮 시간 사용을 위한 밝은 테마", + "station.settings.theme.retro": "레트로", + "station.settings.theme.retro.describe": "→ 90년대 Windows 레트로 스타일", + "station.settings.timezone": "시간대", + "station.settings.title": "무선국 설정", + "station.settings.tip.env": "💡 팁: 영구적인 데이터 저장을 위해 .env.example 파일을 복사해 .env 파일을 덮어쓰고 CALLSIGN 과 LOCATOR 필드를 채우세요.", + "station.settings.useLocation": "📍 현재 내 위치 사용", + "station.settings.useLocation.error1": "위치 정보를 가져올 수 없습니다. 위도와 경도를 수동으로 입력해 주세요.", + "station.settings.useLocation.error2": "브라우저가 위치 정보 기능을 지원하지 않습니다.", + "station.settings.welcome": "👋 OpenHamClock에 오신 것을 환영합니다!", + "plugins.layers.aurora.name": "오로라 예보", + "plugins.layers.aurora.description": "NOAA OVATION 오로라 확률 예보 (30분)", + "plugins.layers.earthquakes.name": "실시간 지진 현황", + "plugins.layers.earthquakes.description": "USGS 실시간 지진 데이터 (지난 24시간 동안 일어난 규모 M2.5 이상의 지진)", + "plugins.layers.wxradar.name": "기상 레이더", + "plugins.layers.wxradar.description": "북아메리카 지역 NEXRAD 기상 레이더 오버레이", + "plugins.layers.wxradar.attribution": "기상 데이터 © Iowa State University Mesonet" +} diff --git a/src/lang/nl.json b/src/lang/nl.json index 2a02524..b45fcb5 100644 --- a/src/lang/nl.json +++ b/src/lang/nl.json @@ -9,6 +9,7 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiaans", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "Hoogte (m)", "station.settings.antenna": "Antenne", "station.settings.button.save": "Bewaar Settings", @@ -45,5 +46,12 @@ "station.settings.useLocation": "📍 Gebruik mijn huidige locatie", "station.settings.useLocation.error1": "Kan niet de locatie vinden. Graag handmatig ingeven.", "station.settings.useLocation.error2": "Geolocation is niet beschikbaar op je browser.", - "station.settings.welcome": "👋 Welkom bij OpenHamClock!" + "station.settings.welcome": "👋 Welkom bij OpenHamClock!", + "plugins.layers.aurora.name": "Auroravoorspelling", + "plugins.layers.aurora.description": "NOAA OVATION-voorspelling van aurorakans (30 min)", + "plugins.layers.earthquakes.name": "Aardbevingen", + "plugins.layers.earthquakes.description": "Live USGS-aardbevingsgegevens (M2,5+ van de afgelopen 24 uur)", + "plugins.layers.wxradar.name": "Weerradar", + "plugins.layers.wxradar.description": "NEXRAD-weerradaroverlay voor Noord-Amerika", + "plugins.layers.wxradar.attribution": "Weergegevens © Iowa State University Mesonet" } diff --git a/src/lang/pt.json b/src/lang/pt.json index 92da6c1..8091927 100644 --- a/src/lang/pt.json +++ b/src/lang/pt.json @@ -9,6 +9,7 @@ "station.settings.language.ja": "日本語", "station.settings.language.it": "Italiano", "station.settings.language.nl": "Nederlands", + "station.settings.language.ko": "한국어", "station.settings.altitude": "Altitude (m)", "station.settings.antenna": "Antena", "station.settings.button.save": "Salvar Configurações", @@ -45,5 +46,12 @@ "station.settings.useLocation": "📍 Usar Minha Localização Atual", "station.settings.useLocation.error1": "Não foi possível obter a localização. Por favor, insira manualmente.", "station.settings.useLocation.error2": "Geolocalização não é suportada pelo seu navegador.", - "station.settings.welcome": "👋 Bem-vindo ao OpenHamClock!" + "station.settings.welcome": "👋 Bem-vindo ao OpenHamClock!", + "plugins.layers.aurora.name": "Previsão de aurora", + "plugins.layers.aurora.description": "Previsão de probabilidade de aurora NOAA OVATION (30 min)", + "plugins.layers.earthquakes.name": "Terremotos", + "plugins.layers.earthquakes.description": "Dados sísmicos do USGS ao vivo (M2,5+ das últimas 24 horas)", + "plugins.layers.wxradar.name": "Radar meteorológico", + "plugins.layers.wxradar.description": "Sobreposição do radar meteorológico NEXRAD para a América do Norte", + "plugins.layers.wxradar.attribution": "Dados meteorológicos © Iowa State University Mesonet" } diff --git a/src/plugins/layers/useAurora.js b/src/plugins/layers/useAurora.js index 995d5a4..200bf1e 100644 --- a/src/plugins/layers/useAurora.js +++ b/src/plugins/layers/useAurora.js @@ -1,3 +1,5 @@ +import i18n from '../../lang/i18n'; + import { useState, useEffect, useRef } from 'react'; // NOAA OVATION Aurora Forecast - JSON grid data @@ -7,8 +9,8 @@ import { useState, useEffect, useRef } from 'react'; export const metadata = { id: 'aurora', - name: 'Aurora Forecast', - description: 'NOAA OVATION aurora probability forecast (30-min)', + name: i18n.t('plugins.layers.aurora.name'), + description: i18n.t('plugins.layers.aurora.description'), icon: '🌌', category: 'space-weather', defaultEnabled: false, diff --git a/src/plugins/layers/useEarthquakes.js b/src/plugins/layers/useEarthquakes.js index 074babe..bdca710 100644 --- a/src/plugins/layers/useEarthquakes.js +++ b/src/plugins/layers/useEarthquakes.js @@ -1,3 +1,5 @@ +import i18n from '../../lang/i18n'; + import { useState, useEffect, useRef } from 'react'; //Scaled markers - Bigger circles for stronger quakes @@ -11,8 +13,8 @@ import { useState, useEffect, useRef } from 'react'; export const metadata = { id: 'earthquakes', - name: 'Earthquakes', - description: 'Live USGS earthquake data (all earthquakes from last hour) with animated detection', + name: i18n.t('plugins.layers.earthquakes.name'), + description: i18n.t('plugins.layers.earthquakes.description'), icon: '🌋', category: 'geology', defaultEnabled: false, diff --git a/src/plugins/layers/useWXRadar.js b/src/plugins/layers/useWXRadar.js index 0282402..15692af 100644 --- a/src/plugins/layers/useWXRadar.js +++ b/src/plugins/layers/useWXRadar.js @@ -1,9 +1,11 @@ +import i18n from '../../lang/i18n'; + import { useState, useEffect } from 'react'; export const metadata = { id: 'wxradar', - name: 'Weather Radar', - description: 'NEXRAD weather radar overlay for North America', + name: i18n.t('plugins.layers.wxradar.name'), + description: i18n.t('plugins.layers.wxradar.description'), icon: '☁️', category: 'weather', defaultEnabled: false, @@ -21,7 +23,7 @@ export function useLayer({ enabled = false, opacity = 0.6, map = null }) { layers: 'nexrad-n0r-900913', format: 'image/png', transparent: true, - attribution: 'Weather data © Iowa State University Mesonet', + attribution: i18n.t('plugins.layers.wxradar.attribution'), opacity: opacity, zIndex: 200 } diff --git a/src/utils/callsign.js b/src/utils/callsign.js index 2207fa2..64b829d 100644 --- a/src/utils/callsign.js +++ b/src/utils/callsign.js @@ -242,8 +242,7 @@ export const filterDXPaths = (paths, filters) => { // Exclude list - hide matching callsigns if (filters.excludeList?.length > 0) { const isExcluded = filters.excludeList.some(e => - path.dxCall?.toUpperCase().includes(e.toUpperCase()) || - path.spotter?.toUpperCase().includes(e.toUpperCase()) + path.dxCall?.toUpperCase().startsWith(e.toUpperCase()) ); if (isExcluded) return false; } diff --git a/src/utils/config.js b/src/utils/config.js index 469ecd9..f95712d 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -10,7 +10,7 @@ export const DEFAULT_CONFIG = { callsign: 'N0CALL', - callsignSize: 1.0, // Float multiplies base px size (0.1 to 2.0) + headerSize: 1.0, // Float multiplies base px size (0.1 to 2.0) locator: '', location: { lat: 40.0150, lon: -105.2705 }, // Boulder, CO (default) defaultDX: { lat: 35.6762, lon: 139.6503 }, // Tokyo diff --git a/src/utils/geo.js b/src/utils/geo.js index 8ce24e8..6e11edd 100644 --- a/src/utils/geo.js +++ b/src/utils/geo.js @@ -130,9 +130,9 @@ export const getMoonPosition = (date) => { export const getMoonPhase = (date) => { const JD = date.getTime() / 86400000 + 2440587.5; const T = (JD - 2451545.0) / 36525; - const D = (297.850 + 445267.1115 * T) % 360; // Mean elongation - // Phase angle (simplified) - const phase = ((D + 180) % 360) / 360; + const D = (297.850 + 445267.1115 * T) % 360; // Mean elongation: 0=new, 180=full + // Normalize to 0-1 range (0=new, 0.5=full) + const phase = (((D % 360) + 360) % 360) / 360; return phase; };