|
|
|
@ -572,6 +572,439 @@ const App = () => {
|
|
|
|
<span>35</span>
|
|
|
|
<span>35</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
) : config.layout === 'tablet' ? (
|
|
|
|
|
|
|
|
/* TABLET LAYOUT - Optimized for 7-10" widescreen displays (16:9) */
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
width: '100vw',
|
|
|
|
|
|
|
|
height: '100vh',
|
|
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
|
|
background: 'var(--bg-primary)',
|
|
|
|
|
|
|
|
fontFamily: 'JetBrains Mono, monospace',
|
|
|
|
|
|
|
|
overflow: 'hidden'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
{/* COMPACT TOP BAR */}
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
|
|
background: 'var(--bg-panel)',
|
|
|
|
|
|
|
|
borderBottom: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
padding: '4px 12px',
|
|
|
|
|
|
|
|
height: '44px',
|
|
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
|
|
gap: '8px'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
{/* Callsign */}
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
fontSize: '20px',
|
|
|
|
|
|
|
|
fontWeight: '900',
|
|
|
|
|
|
|
|
color: 'var(--accent-amber)',
|
|
|
|
|
|
|
|
fontFamily: 'Orbitron, monospace',
|
|
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
|
|
whiteSpace: 'nowrap'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
onClick={() => setShowSettings(true)}
|
|
|
|
|
|
|
|
title="Settings"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{config.callsign}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* UTC */}
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', whiteSpace: 'nowrap' }}>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>UTC</span>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '18px', fontWeight: '700', color: 'var(--accent-cyan)' }}>{utcTime}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Local */}
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
style={{ display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer', whiteSpace: 'nowrap' }}
|
|
|
|
|
|
|
|
onClick={handleTimeFormatToggle}
|
|
|
|
|
|
|
|
title={`Click for ${use12Hour ? '24h' : '12h'} format`}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '11px', color: 'var(--text-muted)' }}>LOC</span>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '18px', fontWeight: '700', color: 'var(--accent-amber)' }}>{localTime}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Solar Quick Stats */}
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', fontSize: '12px', whiteSpace: 'nowrap' }}>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>SFI </span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--accent-amber)', fontWeight: '700' }}>{solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'}</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>K </span>
|
|
|
|
|
|
|
|
<span style={{ color: parseInt(solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex) >= 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}>
|
|
|
|
|
|
|
|
{solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>SSN </span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--accent-cyan)', fontWeight: '700' }}>{solarIndices?.data?.ssn?.current || '--'}</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Controls */}
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '4px' }}>
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
|
|
onClick={() => setShowSettings(true)}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
background: 'var(--bg-tertiary)',
|
|
|
|
|
|
|
|
border: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
|
|
color: 'var(--text-secondary)',
|
|
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>⚙</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
|
|
onClick={handleFullscreenToggle}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
background: 'var(--bg-tertiary)',
|
|
|
|
|
|
|
|
border: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
|
|
color: 'var(--text-secondary)',
|
|
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>{isFullscreen ? '⛶' : '⛶'}</button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* MAIN AREA: Map + Data Sidebar */}
|
|
|
|
|
|
|
|
<div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
|
|
|
|
|
|
|
|
{/* MAP */}
|
|
|
|
|
|
|
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
|
|
|
|
|
|
<WorldMap
|
|
|
|
|
|
|
|
deLocation={config.location}
|
|
|
|
|
|
|
|
dxLocation={dxLocation}
|
|
|
|
|
|
|
|
onDXChange={handleDXChange}
|
|
|
|
|
|
|
|
potaSpots={potaSpots.data}
|
|
|
|
|
|
|
|
mySpots={mySpots.data}
|
|
|
|
|
|
|
|
dxPaths={dxPaths.data}
|
|
|
|
|
|
|
|
dxFilters={dxFilters}
|
|
|
|
|
|
|
|
satellites={satellites.data}
|
|
|
|
|
|
|
|
pskReporterSpots={filteredPskSpots}
|
|
|
|
|
|
|
|
showDXPaths={mapLayers.showDXPaths}
|
|
|
|
|
|
|
|
showDXLabels={mapLayers.showDXLabels}
|
|
|
|
|
|
|
|
onToggleDXLabels={toggleDXLabels}
|
|
|
|
|
|
|
|
showPOTA={mapLayers.showPOTA}
|
|
|
|
|
|
|
|
showSatellites={mapLayers.showSatellites}
|
|
|
|
|
|
|
|
showPSKReporter={mapLayers.showPSKReporter}
|
|
|
|
|
|
|
|
wsjtxSpots={wsjtxMapSpots}
|
|
|
|
|
|
|
|
showWSJTX={mapLayers.showWSJTX}
|
|
|
|
|
|
|
|
onToggleSatellites={toggleSatellites}
|
|
|
|
|
|
|
|
hoveredSpot={hoveredSpot}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* DATA SIDEBAR */}
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
width: '220px',
|
|
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
|
|
borderLeft: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
background: 'var(--bg-secondary)',
|
|
|
|
|
|
|
|
overflow: 'hidden'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
{/* Band Conditions Grid */}
|
|
|
|
|
|
|
|
<div style={{ padding: '6px', borderBottom: '1px solid var(--border-color)' }}>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '10px', color: 'var(--accent-amber)', fontWeight: '700', marginBottom: '4px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Band Conditions</div>
|
|
|
|
|
|
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '3px' }}>
|
|
|
|
|
|
|
|
{(bandConditions?.data || []).slice(0, 13).map((band, idx) => {
|
|
|
|
|
|
|
|
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)' },
|
|
|
|
|
|
|
|
POOR: { bg: 'rgba(255,68,102,0.2)', color: '#ff4466', border: 'rgba(255,68,102,0.4)' }
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const s = colors[band.condition] || colors.FAIR;
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<div key={idx} style={{
|
|
|
|
|
|
|
|
background: s.bg,
|
|
|
|
|
|
|
|
border: `1px solid ${s.border}`,
|
|
|
|
|
|
|
|
borderRadius: '3px',
|
|
|
|
|
|
|
|
padding: '3px 1px',
|
|
|
|
|
|
|
|
textAlign: 'center'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
<div style={{ fontFamily: 'Orbitron, monospace', fontSize: '11px', fontWeight: '700', color: s.color }}>{band.band}</div>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '8px', fontWeight: '600', color: s.color, opacity: 0.8 }}>{band.condition}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* MUF/LUF */}
|
|
|
|
|
|
|
|
{propagation.data && (
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '4px', fontSize: '10px', justifyContent: 'center' }}>
|
|
|
|
|
|
|
|
<span><span style={{ color: 'var(--text-muted)' }}>MUF </span><span style={{ color: '#ff8800', fontWeight: '600' }}>{propagation.data.muf || '?'}</span></span>
|
|
|
|
|
|
|
|
<span><span style={{ color: 'var(--text-muted)' }}>LUF </span><span style={{ color: '#00aaff', fontWeight: '600' }}>{propagation.data.luf || '?'}</span></span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Compact DX Cluster */}
|
|
|
|
|
|
|
|
<div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
|
|
|
|
|
|
|
<div style={{ padding: '4px 6px', borderBottom: '1px solid var(--border-color)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '10px', color: 'var(--accent-red)', fontWeight: '700', textTransform: 'uppercase' }}>DX Cluster</span>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '9px', color: 'var(--text-muted)' }}>{dxCluster.data?.length || 0} spots</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ flex: 1, overflowY: 'auto', fontSize: '11px' }}>
|
|
|
|
|
|
|
|
{dxCluster.data?.slice(0, 30).map((spot, i) => (
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
key={i}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
padding: '2px 6px',
|
|
|
|
|
|
|
|
display: 'grid',
|
|
|
|
|
|
|
|
gridTemplateColumns: '55px 1fr 30px',
|
|
|
|
|
|
|
|
gap: '3px',
|
|
|
|
|
|
|
|
borderBottom: '1px solid rgba(255,255,255,0.04)',
|
|
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
|
|
background: hoveredSpot?.call === spot.call ? 'var(--bg-tertiary)' : 'transparent',
|
|
|
|
|
|
|
|
fontSize: '10px'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
onMouseEnter={() => setHoveredSpot(spot)}
|
|
|
|
|
|
|
|
onMouseLeave={() => setHoveredSpot(null)}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<span style={{ color: '#ffff00' }}>{parseFloat(spot.freq).toFixed(1)}</span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--accent-cyan)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{spot.call}</span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)', textAlign: 'right' }}>{spot.time || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
) : config.layout === 'compact' ? (
|
|
|
|
|
|
|
|
/* COMPACT LAYOUT - Optimized for 4:3 screens and data-first display */
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
width: '100vw',
|
|
|
|
|
|
|
|
height: '100vh',
|
|
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
|
|
background: 'var(--bg-primary)',
|
|
|
|
|
|
|
|
fontFamily: 'JetBrains Mono, monospace',
|
|
|
|
|
|
|
|
overflow: 'hidden'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
{/* TOP: Callsign + Times + Solar */}
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
background: 'var(--bg-panel)',
|
|
|
|
|
|
|
|
borderBottom: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
|
|
|
flexShrink: 0
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
{/* Row 1: Callsign + Times */}
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '4px' }}>
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
fontSize: '26px',
|
|
|
|
|
|
|
|
fontWeight: '900',
|
|
|
|
|
|
|
|
color: 'var(--accent-amber)',
|
|
|
|
|
|
|
|
fontFamily: 'Orbitron, monospace',
|
|
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
onClick={() => setShowSettings(true)}
|
|
|
|
|
|
|
|
title="Settings"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{config.callsign}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
|
|
|
|
|
|
|
<div style={{ textAlign: 'center' }}>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '9px', color: 'var(--text-muted)', textTransform: 'uppercase' }}>UTC</div>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '22px', fontWeight: '700', color: 'var(--accent-cyan)', lineHeight: 1 }}>{utcTime}</div>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '10px', color: 'var(--text-muted)' }}>{utcDate}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
style={{ textAlign: 'center', cursor: 'pointer' }}
|
|
|
|
|
|
|
|
onClick={handleTimeFormatToggle}
|
|
|
|
|
|
|
|
title={`Click for ${use12Hour ? '24h' : '12h'}`}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '9px', color: 'var(--text-muted)', textTransform: 'uppercase' }}>Local</div>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '22px', fontWeight: '700', color: 'var(--accent-amber)', lineHeight: 1 }}>{localTime}</div>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '10px', color: 'var(--text-muted)' }}>{localDate}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '4px' }}>
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
|
|
onClick={() => setShowSettings(true)}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
background: 'var(--bg-tertiary)',
|
|
|
|
|
|
|
|
border: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
|
|
color: 'var(--text-secondary)',
|
|
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>⚙</button>
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
|
|
onClick={handleFullscreenToggle}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
background: 'var(--bg-tertiary)',
|
|
|
|
|
|
|
|
border: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
|
|
color: 'var(--text-secondary)',
|
|
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>⛶</button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Row 2: Solar indices inline */}
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '12px', fontSize: '12px', justifyContent: 'center' }}>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>SFI </span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--accent-amber)', fontWeight: '700' }}>{solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'}</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>K </span>
|
|
|
|
|
|
|
|
<span style={{ color: parseInt(solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex) >= 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}>
|
|
|
|
|
|
|
|
{solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>SSN </span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--accent-cyan)', fontWeight: '700' }}>{solarIndices?.data?.ssn?.current || '--'}</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
{propagation.data && (
|
|
|
|
|
|
|
|
<>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>MUF </span>
|
|
|
|
|
|
|
|
<span style={{ color: '#ff8800', fontWeight: '600' }}>{propagation.data.muf || '?'} MHz</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)' }}>LUF </span>
|
|
|
|
|
|
|
|
<span style={{ color: '#00aaff', fontWeight: '600' }}>{propagation.data.luf || '?'} MHz</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
{localWeather?.data && (
|
|
|
|
|
|
|
|
<span>
|
|
|
|
|
|
|
|
<span style={{ marginRight: '2px' }}>{localWeather.data.icon}</span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--accent-cyan)', fontWeight: '600' }}>{localWeather.data.temp}°{localWeather.data.tempUnit || tempUnit}</span>
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* BAND CONDITIONS - Full Width */}
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
padding: '6px 12px',
|
|
|
|
|
|
|
|
borderBottom: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
background: 'var(--bg-secondary)',
|
|
|
|
|
|
|
|
flexShrink: 0
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'center', gap: '4px', flexWrap: 'wrap' }}>
|
|
|
|
|
|
|
|
{(bandConditions?.data || []).slice(0, 13).map((band, idx) => {
|
|
|
|
|
|
|
|
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)' },
|
|
|
|
|
|
|
|
POOR: { bg: 'rgba(255,68,102,0.2)', color: '#ff4466', border: 'rgba(255,68,102,0.4)' }
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const s = colors[band.condition] || colors.FAIR;
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<div key={idx} style={{
|
|
|
|
|
|
|
|
background: s.bg,
|
|
|
|
|
|
|
|
border: `1px solid ${s.border}`,
|
|
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
|
|
minWidth: '52px'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
<div style={{ fontFamily: 'Orbitron, monospace', fontSize: '13px', fontWeight: '700', color: s.color }}>{band.band}</div>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '9px', fontWeight: '600', color: s.color, opacity: 0.8 }}>{band.condition}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* MAIN: Map + DX Cluster side by side */}
|
|
|
|
|
|
|
|
<div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
|
|
|
|
|
|
|
|
{/* Map */}
|
|
|
|
|
|
|
|
<div style={{ flex: 1, position: 'relative' }}>
|
|
|
|
|
|
|
|
<WorldMap
|
|
|
|
|
|
|
|
deLocation={config.location}
|
|
|
|
|
|
|
|
dxLocation={dxLocation}
|
|
|
|
|
|
|
|
onDXChange={handleDXChange}
|
|
|
|
|
|
|
|
potaSpots={potaSpots.data}
|
|
|
|
|
|
|
|
mySpots={mySpots.data}
|
|
|
|
|
|
|
|
dxPaths={dxPaths.data}
|
|
|
|
|
|
|
|
dxFilters={dxFilters}
|
|
|
|
|
|
|
|
satellites={satellites.data}
|
|
|
|
|
|
|
|
pskReporterSpots={filteredPskSpots}
|
|
|
|
|
|
|
|
showDXPaths={mapLayers.showDXPaths}
|
|
|
|
|
|
|
|
showDXLabels={mapLayers.showDXLabels}
|
|
|
|
|
|
|
|
onToggleDXLabels={toggleDXLabels}
|
|
|
|
|
|
|
|
showPOTA={mapLayers.showPOTA}
|
|
|
|
|
|
|
|
showSatellites={mapLayers.showSatellites}
|
|
|
|
|
|
|
|
showPSKReporter={mapLayers.showPSKReporter}
|
|
|
|
|
|
|
|
wsjtxSpots={wsjtxMapSpots}
|
|
|
|
|
|
|
|
showWSJTX={mapLayers.showWSJTX}
|
|
|
|
|
|
|
|
onToggleSatellites={toggleSatellites}
|
|
|
|
|
|
|
|
hoveredSpot={hoveredSpot}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
|
|
bottom: '6px',
|
|
|
|
|
|
|
|
left: '50%',
|
|
|
|
|
|
|
|
transform: 'translateX(-50%)',
|
|
|
|
|
|
|
|
fontSize: '11px',
|
|
|
|
|
|
|
|
color: 'var(--text-muted)',
|
|
|
|
|
|
|
|
background: 'rgba(0,0,0,0.7)',
|
|
|
|
|
|
|
|
padding: '2px 8px',
|
|
|
|
|
|
|
|
borderRadius: '4px'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
{deGrid} → {dxGrid} • Click map to set DX
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Compact DX Cluster */}
|
|
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
|
|
width: '200px',
|
|
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
|
|
borderLeft: '1px solid var(--border-color)',
|
|
|
|
|
|
|
|
background: 'var(--bg-secondary)',
|
|
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
|
|
overflow: 'hidden'
|
|
|
|
|
|
|
|
}}>
|
|
|
|
|
|
|
|
<div style={{ padding: '4px 6px', borderBottom: '1px solid var(--border-color)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '10px', color: 'var(--accent-red)', fontWeight: '700', textTransform: 'uppercase' }}>DX Cluster</span>
|
|
|
|
|
|
|
|
<span style={{ fontSize: '9px', color: 'var(--text-muted)' }}>{dxCluster.data?.length || 0}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ flex: 1, overflowY: 'auto' }}>
|
|
|
|
|
|
|
|
{dxCluster.data?.slice(0, 40).map((spot, i) => (
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
key={i}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
padding: '2px 6px',
|
|
|
|
|
|
|
|
display: 'grid',
|
|
|
|
|
|
|
|
gridTemplateColumns: '50px 1fr 28px',
|
|
|
|
|
|
|
|
gap: '3px',
|
|
|
|
|
|
|
|
borderBottom: '1px solid rgba(255,255,255,0.04)',
|
|
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
|
|
background: hoveredSpot?.call === spot.call ? 'var(--bg-tertiary)' : 'transparent',
|
|
|
|
|
|
|
|
fontSize: '10px'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
onMouseEnter={() => setHoveredSpot(spot)}
|
|
|
|
|
|
|
|
onMouseLeave={() => setHoveredSpot(null)}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<span style={{ color: '#ffff00' }}>{parseFloat(spot.freq).toFixed(1)}</span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--accent-cyan)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{spot.call}</span>
|
|
|
|
|
|
|
|
<span style={{ color: 'var(--text-muted)', textAlign: 'right' }}>{spot.time || '--'}</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
) : (
|
|
|
|
) : (
|
|
|
|
/* MODERN LAYOUT */
|
|
|
|
/* MODERN LAYOUT */
|
|
|
|
<div style={{
|
|
|
|
<div style={{
|
|
|
|
|