diff --git a/public/index.html b/public/index.html index d897068..1b3d106 100644 --- a/public/index.html +++ b/public/index.html @@ -265,6 +265,23 @@ font-family: 'JetBrains Mono', monospace !important; font-size: 12px !important; } + + /* Panel styling */ + .panel { + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 10px; + backdrop-filter: blur(10px); + } + + .panel-header { + font-size: 10px; + font-weight: 700; + color: var(--accent-cyan); + margin-bottom: 8px; + letter-spacing: 0.5px; + } @@ -802,9 +819,11 @@ }; // ============================================ - // PROPAGATION PANEL COMPONENT + // PROPAGATION PANEL COMPONENT (Toggleable views) // ============================================ const PropagationPanel = ({ propagation, loading }) => { + const [viewMode, setViewMode] = useState('bars'); // 'bars' or 'chart' + if (loading || !propagation) { return (
@@ -818,7 +837,25 @@ const { solarData, distance, currentBands, currentHour, hourlyPredictions } = propagation; - // Get status color + // Get reliability color for heat map (VOACAP style - red=good, green=poor) + const getHeatColor = (rel) => { + if (rel >= 80) return '#ff0000'; // Red - excellent + if (rel >= 60) return '#ff6600'; // Orange - good + if (rel >= 40) return '#ffcc00'; // Yellow - fair + if (rel >= 20) return '#88cc00'; // Yellow-green - poor + if (rel >= 10) return '#00aa00'; // Green - marginal + return '#004400'; // Dark green - closed + }; + + // Get reliability bar color (standard - green=good) + const getReliabilityColor = (rel) => { + if (rel >= 70) return '#00ff88'; + if (rel >= 50) return '#88ff00'; + if (rel >= 30) return '#ffcc00'; + if (rel >= 15) return '#ff8800'; + return '#ff4444'; + }; + const getStatusColor = (status) => { switch (status) { case 'EXCELLENT': return '#00ff88'; @@ -830,137 +867,237 @@ } }; - // Get reliability bar color - const getReliabilityColor = (rel) => { - if (rel >= 70) return '#00ff88'; - if (rel >= 50) return '#88ff00'; - if (rel >= 30) return '#ffcc00'; - if (rel >= 15) return '#ff8800'; - return '#ff4444'; - }; + const bands = ['80m', '40m', '30m', '20m', '17m', '15m', '12m', '10m']; return ( -
-
- 📡 HF Propagation to DX - - {Math.round(distance).toLocaleString()} km +
setViewMode(v => v === 'bars' ? 'chart' : 'bars')}> +
+ 📡 VOACAP DE-DX + + {viewMode === 'bars' ? '▦ bars' : '▤ chart'} • click to toggle
- {/* Solar Conditions Summary */} -
-
-
SFI
-
{solarData.sfi}
-
-
-
SSN
-
{solarData.ssn}
-
-
-
K
+ {viewMode === 'chart' ? ( + /* VOACAP Heat Map Chart View */ +
+ {/* Heat map grid */}
= 4 ? '#ff4444' : solarData.kIndex >= 3 ? '#ffcc00' : '#00ff88', - fontWeight: 'bold' - }}>{solarData.kIndex}
-
-
- - {/* Band Predictions */} -
-
- BAND - RELIABILITY - STATUS -
- - {currentBands.slice(0, 8).map((band, idx) => ( -
+ {bands.map((band, bandIdx) => ( + + {/* Band label */} +
+ {band.replace('m', '')} +
+ {/* 24 hour cells */} + {Array.from({ length: 24 }, (_, hour) => { + const bandData = hourlyPredictions?.[band]; + const hourData = bandData?.find(h => h.hour === hour); + const rel = hourData?.reliability || 0; + return ( +
+ ); + })} + + ))} +
+ + {/* Hour labels */} +
+
UTC
+ {[0, '', '', 3, '', '', 6, '', '', 9, '', '', 12, '', '', 15, '', '', 18, '', '', 21, '', ''].map((h, i) => ( +
{h}
+ ))} +
+ + {/* Legend & Info */} +
+
+ REL: +
+
+
+
+
+
+
+
+ {Math.round(distance/1000)}Kkm, SSN={solarData.ssn} +
+
+
+ ) : ( + /* Bar Chart View */ +
+ {/* Solar quick stats */} +
- = 50 ? 'var(--color-primary)' : 'var(--text-muted)' + SFI {solarData.sfi} + SSN {solarData.ssn} + K = 4 ? '#ff4444' : '#00ff88' }}>{solarData.kIndex} +
+ + {currentBands.slice(0, 8).map((band, idx) => ( +
- {band.band} - -
-
-
- = 50 ? 'var(--accent-green)' : 'var(--text-muted)' + }}> + {band.band} + +
+
+
+ 50 ? '#000' : 'var(--text-muted)' + color: getStatusColor(band.status) }}> {band.reliability}%
- - {band.status} - -
- ))} + ))} +
+ )} +
+ ); + }; + + // ============================================ + // SOLAR IMAGE COMPONENT + // ============================================ + const SolarImage = () => { + const [imageType, setImageType] = useState('0193'); // AIA 193 (corona) + + // SDO/AIA image types + const imageTypes = { + '0193': { name: 'AIA 193Ã…', desc: 'Corona' }, + '0304': { name: 'AIA 304Ã…', desc: 'Chromosphere' }, + '0171': { name: 'AIA 171Ã…', desc: 'Quiet Corona' }, + '0094': { name: 'AIA 94Ã…', desc: 'Flaring' }, + 'HMIIC': { name: 'HMI Int', desc: 'Visible' } + }; + + // SDO images update every ~15 minutes + const timestamp = Math.floor(Date.now() / 900000) * 900000; // Round to 15 min + const imageUrl = `https://sdo.gsfc.nasa.gov/assets/img/latest/latest_256_${imageType}.jpg?t=${timestamp}`; + + return ( +
+
+ ☀ SOLAR + +
+
+ Current Sun { + e.target.style.display = 'none'; + }} + />
- - {/* Footer */}
- Predictions at {String(currentHour).padStart(2, '0')}:00 UTC • Based on current solar conditions + SDO/AIA • Live from NASA
); @@ -2246,10 +2383,10 @@
{/* Band Conditions - Compact with color coding */} -
+
📊 BAND CONDITIONS
-
- {bandConditions.data.map(band => { +
+ {bandConditions.data.slice(0, 12).map(band => { const colors = { GOOD: { bg: 'rgba(0,255,136,0.2)', color: '#00ff88', border: 'rgba(0,255,136,0.4)' }, FAIR: { bg: 'rgba(255,180,50,0.2)', color: '#ffb432', border: 'rgba(255,180,50,0.4)' }, @@ -2259,41 +2396,23 @@ return (
-
{band.band}
-
{band.condition}
+
{band.band}
); })}
- {/* Propagation Compact */} -
-
📡 PROPAGATION TO DX
- {propagation.data ? ( -
-
- {Math.round(propagation.data.distance).toLocaleString()} km path -
- {propagation.data.currentBands.slice(0, 6).map(b => ( -
- = 50 ? 'var(--accent-green)' : 'var(--text-muted)' }}>{b.band} -
-
= 70 ? '#00ff88' : b.reliability >= 50 ? '#88ff00' : b.reliability >= 30 ? '#ffcc00' : '#ff4444', borderRadius: '2px' }} /> -
- = 50 ? 'var(--accent-green)' : 'var(--text-muted)' }}>{b.reliability}% -
- ))} -
- ) : ( -
Loading...
- )} -
+ {/* Solar Image */} + + + {/* VOACAP Propagation - Toggleable */} +
{/* CENTER - MAP */}