diff --git a/server/routes/space-weather.js b/server/routes/space-weather.js
index aa4526fc..d1126d93 100644
--- a/server/routes/space-weather.js
+++ b/server/routes/space-weather.js
@@ -210,15 +210,20 @@ module.exports = function (app, ctx) {
}
// Kp forecast — same format change; forecast uses lowercase 'kp' field.
+ // The endpoint mixes past observations with future predictions; keep only
+ // entries whose time_tag is in the future so the chart shows predictions.
if (kForecastRes.status === 'fulfilled' && kForecastRes.value.ok) {
const data = await kForecastRes.value.json();
if (data?.length) {
const isObj = !Array.isArray(data[0]);
const rows = isObj ? data : data.slice(1);
- result.kp.forecast = rows.map((d) => ({
- time: isObj ? d.time_tag : d[0],
- value: Number.isFinite(isObj ? d.kp : parseFloat(d[1])) ? (isObj ? d.kp : parseFloat(d[1])) : 0,
- }));
+ const nowIso = new Date().toISOString();
+ result.kp.forecast = rows
+ .filter((d) => (isObj ? d.time_tag : d[0]) > nowIso)
+ .map((d) => ({
+ time: isObj ? d.time_tag : d[0],
+ value: Number.isFinite(isObj ? d.kp : parseFloat(d[1])) ? (isObj ? d.kp : parseFloat(d[1])) : 0,
+ }));
}
}
diff --git a/src/components/PropagationPanel.jsx b/src/components/PropagationPanel.jsx
index dc3b8457..50c7b84a 100644
--- a/src/components/PropagationPanel.jsx
+++ b/src/components/PropagationPanel.jsx
@@ -440,7 +440,8 @@ export const PropagationPanel = ({
{/* Geomag + Signal Noise + Source */}
- SFI {solarData?.sfi} • K {solarData?.kIndex}
+ SFI {bandConditions?.extras?.solarFlux ?? solarData?.sfi} • K{' '}
+ {bandConditions?.extras?.kIndex ?? solarData?.kIndex}
{bandConditions?.extras?.geomagField && (
diff --git a/src/hooks/useBandConditions.js b/src/hooks/useBandConditions.js
index 2ba76cec..e2a3a615 100644
--- a/src/hooks/useBandConditions.js
+++ b/src/hooks/useBandConditions.js
@@ -79,6 +79,8 @@ export const useBandConditions = () => {
// Extra solar/geomag data from N0NBH
setExtras({
+ solarFlux: n0nbh.solarData?.solarFlux,
+ kIndex: n0nbh.solarData?.kIndex,
aIndex: n0nbh.solarData?.aIndex,
xray: n0nbh.solarData?.xray,
solarWind: n0nbh.solarData?.solarWind,
diff --git a/src/plugins/layers/useLightning.js b/src/plugins/layers/useLightning.js
index 7f4c3c6a..4137fb3d 100644
--- a/src/plugins/layers/useLightning.js
+++ b/src/plugins/layers/useLightning.js
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { addMinimizeToggle } from './addMinimizeToggle.js';
import { makeDraggable } from './makeDraggable.js';
+import { getAlertSettings, playTone } from '../../utils/audioAlerts';
// Lightning Detection Plugin - Real-time lightning strike visualization
// Data source: Blitzortung.org WebSocket API
@@ -616,14 +617,12 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null, lowMemory
panel.style.boxShadow = '0 0 20px rgba(255, 0, 0, 0.8)';
panel.style.transition = 'all 0.3s ease';
- // Play alert sound if available
- try {
- const audio = new Audio(
- 'data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBTGH0fPTgjMGHm7A7+OZRAEKT6Ln77BcGAU+ltryxnMnBSp+y/HajDkHGWi77eWdTQ0MUKfj8LZjHAY4kdfy',
- );
- audio.volume = 0.3;
- audio.play().catch(() => {}); // Ignore errors if audio fails
- } catch (e) {}
+ // Play alert tone via audio alerts system (if enabled in settings)
+ const alertSettings = getAlertSettings();
+ const lightningConf = alertSettings.lightning;
+ if (lightningConf?.enabled) {
+ playTone(lightningConf.tone, alertSettings.volume ?? 0.5);
+ }
} else {
// No nearby strikes - restore normal appearance
panel.style.border = '1px solid var(--border-color)';
diff --git a/src/utils/audioAlerts.js b/src/utils/audioAlerts.js
index 653204d5..2a53d0d9 100644
--- a/src/utils/audioAlerts.js
+++ b/src/utils/audioAlerts.js
@@ -37,6 +37,7 @@ export const ALERT_FEEDS = {
dxcluster: { label: 'DX Cluster', defaultTone: 'beep' },
dxpeditions: { label: 'DXpeditions', defaultTone: 'two-tone' },
contests: { label: 'Contests', defaultTone: 'simple' },
+ lightning: { label: 'Lightning Proximity', defaultTone: 'chirp' },
};
const LS_KEY = 'ohc_audio_alerts';