pull/6/head
accius 3 days ago
parent cbae93ba61
commit 4a76b28c50

@ -223,19 +223,286 @@ const App = () => {
alignItems: 'center',
overflow: 'hidden'
}}>
<div style={{
width: scale < 1 ? `${100 / scale}vw` : '100vw',
height: scale < 1 ? `${100 / scale}vh` : '100vh',
transform: `scale(${scale})`,
transformOrigin: 'center center',
display: 'grid',
gridTemplateColumns: '270px 1fr 300px',
gridTemplateRows: '65px 1fr',
gap: '8px',
padding: '8px',
overflow: 'hidden',
boxSizing: 'border-box'
}}>
{config.layout === 'classic' ? (
/* CLASSIC HAMCLOCK-STYLE LAYOUT */
<div style={{
width: '100vw',
height: '100vh',
display: 'flex',
flexDirection: 'column',
background: '#000000',
fontFamily: 'JetBrains Mono, monospace',
overflow: 'hidden'
}}>
{/* TOP BAR - HamClock style */}
<div style={{
display: 'grid',
gridTemplateColumns: '280px 1fr 300px',
height: '130px',
borderBottom: '2px solid #333',
background: '#000'
}}>
{/* Callsign & Time */}
<div style={{ padding: '8px 12px', borderRight: '1px solid #333' }}>
<div
style={{
fontSize: '42px',
fontWeight: '900',
color: '#ff4444',
fontFamily: 'Orbitron, monospace',
cursor: 'pointer',
lineHeight: 1
}}
onClick={() => setShowSettings(true)}
title="Click for settings"
>
{config.callsign}
</div>
<div style={{ fontSize: '11px', color: '#888', marginTop: '2px' }}>
Up 35d 18h v4.20
</div>
<div style={{ marginTop: '8px' }}>
<div style={{ fontSize: '36px', fontWeight: '700', color: '#00ff00', fontFamily: 'Orbitron, monospace', lineHeight: 1 }}>
{utcTime}<span style={{ fontSize: '20px', color: '#00cc00' }}>:{String(new Date().getUTCSeconds()).padStart(2, '0')}</span>
</div>
<div style={{ fontSize: '14px', color: '#00cc00', marginTop: '2px' }}>
{utcDate} <span style={{ color: '#666', marginLeft: '8px' }}>UTC</span>
</div>
</div>
</div>
{/* Solar Indices - SSN & SFI */}
<div style={{ display: 'flex', borderRight: '1px solid #333' }}>
{/* SSN */}
<div style={{ flex: 1, padding: '8px', borderRight: '1px solid #333' }}>
<div style={{ fontSize: '10px', color: '#888', textAlign: 'center' }}>Sunspot Number</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ flex: 1, height: '70px', background: '#001100', border: '1px solid #333', borderRadius: '2px', padding: '4px' }}>
{solarIndices?.data?.ssn?.history?.length > 0 && (
<svg width="100%" height="100%" viewBox="0 0 100 60" preserveAspectRatio="none">
{(() => {
const data = solarIndices.data.ssn.history.slice(-30);
const values = data.map(d => d.value);
const max = Math.max(...values, 1);
const min = Math.min(...values, 0);
const range = max - min || 1;
const points = data.map((d, i) => {
const x = (i / (data.length - 1)) * 100;
const y = 60 - ((d.value - min) / range) * 55;
return `${x},${y}`;
}).join(' ');
return <polyline points={points} fill="none" stroke="#00ff00" strokeWidth="1.5" />;
})()}
</svg>
)}
</div>
<div style={{ fontSize: '48px', fontWeight: '700', color: '#00ffff', fontFamily: 'Orbitron, monospace' }}>
{solarIndices?.data?.ssn?.current || '--'}
</div>
</div>
<div style={{ fontSize: '10px', color: '#666', textAlign: 'center', marginTop: '2px' }}>-30 Days</div>
</div>
{/* SFI */}
<div style={{ flex: 1, padding: '8px' }}>
<div style={{ fontSize: '10px', color: '#888', textAlign: 'center' }}>10.7 cm Solar flux</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ flex: 1, height: '70px', background: '#001100', border: '1px solid #333', borderRadius: '2px', padding: '4px' }}>
{solarIndices?.data?.sfi?.history?.length > 0 && (
<svg width="100%" height="100%" viewBox="0 0 100 60" preserveAspectRatio="none">
{(() => {
const data = solarIndices.data.sfi.history.slice(-30);
const values = data.map(d => d.value);
const max = Math.max(...values, 1);
const min = Math.min(...values);
const range = max - min || 1;
const points = data.map((d, i) => {
const x = (i / (data.length - 1)) * 100;
const y = 60 - ((d.value - min) / range) * 55;
return `${x},${y}`;
}).join(' ');
return <polyline points={points} fill="none" stroke="#00ff00" strokeWidth="1.5" />;
})()}
</svg>
)}
</div>
<div style={{ fontSize: '48px', fontWeight: '700', color: '#ff66ff', fontFamily: 'Orbitron, monospace' }}>
{solarIndices?.data?.sfi?.current || '--'}
</div>
</div>
<div style={{ fontSize: '10px', color: '#666', textAlign: 'center', marginTop: '2px' }}>-30 Days +7</div>
</div>
</div>
{/* Live Spots & Indices */}
<div style={{ display: 'flex' }}>
{/* Live Spots by Band */}
<div style={{ flex: 1, padding: '8px', borderRight: '1px solid #333' }}>
<div style={{ fontSize: '12px', color: '#ff6666', fontWeight: '700' }}>Live Spots</div>
<div style={{ fontSize: '9px', color: '#888', marginBottom: '4px' }}>of {deGrid} - 15 mins</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2px', fontSize: '10px' }}>
{[
{ band: '160m', color: '#ff6666' },
{ band: '80m', color: '#ff9966' },
{ band: '60m', color: '#ffcc66' },
{ band: '40m', color: '#ccff66' },
{ band: '30m', color: '#66ff99' },
{ band: '20m', color: '#66ffcc' },
{ band: '17m', color: '#66ccff' },
{ band: '15m', color: '#6699ff' },
{ band: '12m', color: '#9966ff' },
{ band: '10m', color: '#cc66ff' },
].map(b => (
<div key={b.band} style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: b.color }}>{b.band}</span>
<span style={{ color: '#fff' }}>
{dxCluster.data?.filter(s => {
const freq = parseFloat(s.freq);
const bands = {
'160m': [1.8, 2], '80m': [3.5, 4], '60m': [5.3, 5.4], '40m': [7, 7.3],
'30m': [10.1, 10.15], '20m': [14, 14.35], '17m': [18.068, 18.168],
'15m': [21, 21.45], '12m': [24.89, 24.99], '10m': [28, 29.7]
};
const r = bands[b.band];
return r && freq >= r[0] && freq <= r[1];
}).length || 0}
</span>
</div>
))}
</div>
</div>
{/* Space Weather Indices */}
<div style={{ width: '70px', padding: '8px', fontSize: '11px' }}>
<div style={{ marginBottom: '6px' }}>
<div style={{ color: '#888' }}>X-Ray</div>
<div style={{ color: '#ffff00', fontSize: '16px', fontWeight: '700' }}>M3.0</div>
</div>
<div style={{ marginBottom: '6px' }}>
<div style={{ color: '#888' }}>Kp</div>
<div style={{ color: '#00ff00', fontSize: '16px', fontWeight: '700' }}>{spaceWeather?.data?.kIndex ?? '--'}</div>
</div>
<div style={{ marginBottom: '6px' }}>
<div style={{ color: '#888' }}>Bz</div>
<div style={{ color: '#00ffff', fontSize: '16px', fontWeight: '700' }}>-0</div>
</div>
<div>
<div style={{ color: '#888' }}>Aurora</div>
<div style={{ color: '#ff00ff', fontSize: '16px', fontWeight: '700' }}>18</div>
</div>
</div>
</div>
</div>
{/* MAIN AREA */}
<div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
{/* DX Cluster List */}
<div style={{ width: '220px', borderRight: '1px solid #333', display: 'flex', flexDirection: 'column', background: '#000' }}>
<div style={{ padding: '4px 8px', borderBottom: '1px solid #333', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ color: '#ff6666', fontSize: '14px', fontWeight: '700' }}>Cluster</span>
<span style={{ color: '#00ff00', fontSize: '10px' }}>dxspider.co.uk:7300</span>
</div>
<div style={{ flex: 1, overflow: 'auto', fontSize: '11px' }}>
{dxCluster.data?.slice(0, 25).map((spot, i) => (
<div
key={i}
style={{
padding: '2px 6px',
display: 'grid',
gridTemplateColumns: '65px 1fr 35px',
gap: '4px',
borderBottom: '1px solid #111',
cursor: 'pointer',
background: hoveredSpot?.call === spot.call ? '#333' : 'transparent'
}}
onMouseEnter={() => setHoveredSpot(spot)}
onMouseLeave={() => setHoveredSpot(null)}
>
<span style={{ color: '#ffff00' }}>{parseFloat(spot.freq).toFixed(1)}</span>
<span style={{ color: '#00ffff' }}>{spot.call}</span>
<span style={{ color: '#888' }}>{spot.time || '--'}</span>
</div>
))}
</div>
</div>
{/* 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}
showDXPaths={mapLayers.showDXPaths}
showDXLabels={mapLayers.showDXLabels}
onToggleDXLabels={toggleDXLabels}
showPOTA={mapLayers.showPOTA}
showSatellites={mapLayers.showSatellites}
onToggleSatellites={toggleSatellites}
hoveredSpot={hoveredSpot}
/>
{/* Settings button overlay */}
<button
onClick={() => setShowSettings(true)}
style={{
position: 'absolute',
top: '10px',
left: '10px',
background: 'rgba(0,0,0,0.7)',
border: '1px solid #444',
color: '#fff',
padding: '6px 12px',
fontSize: '12px',
cursor: 'pointer',
borderRadius: '4px'
}}
>
Settings
</button>
</div>
</div>
{/* BOTTOM - Frequency Scale */}
<div style={{
height: '24px',
background: 'linear-gradient(90deg, #ff0000 0%, #ff8800 15%, #ffff00 30%, #00ff00 45%, #00ffff 60%, #0088ff 75%, #8800ff 90%, #ff00ff 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-around',
fontSize: '10px',
color: '#000',
fontWeight: '700'
}}>
<span>MHz</span>
<span>5</span>
<span>10</span>
<span>15</span>
<span>20</span>
<span>25</span>
<span>30</span>
<span>35</span>
</div>
</div>
) : (
/* MODERN LAYOUT */
<div style={{
width: scale < 1 ? `${100 / scale}vw` : '100vw',
height: scale < 1 ? `${100 / scale}vh` : '100vh',
transform: `scale(${scale})`,
transformOrigin: 'center center',
display: 'grid',
gridTemplateColumns: '270px 1fr 300px',
gridTemplateRows: '65px 1fr',
gap: '8px',
padding: '8px',
overflow: 'hidden',
boxSizing: 'border-box'
}}>
{/* TOP BAR */}
<Header
config={config}
@ -368,6 +635,7 @@ const App = () => {
</div>
</div>
</div>
)}
{/* Modals */}
<SettingsPanel

@ -107,12 +107,12 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => {
dark: '→ Modern dark theme (default)',
light: '→ Light theme for daytime use',
legacy: '→ Green terminal CRT style',
classic: '→ 90s Windows retro style'
retro: '→ 90s Windows retro style'
};
const layoutDescriptions = {
modern: '→ Modern responsive grid layout',
classic: '→ Classic HamClock-style layout'
classic: '→ Original HamClock-style layout'
};
return (
@ -270,7 +270,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => {
Theme
</label>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px' }}>
{['dark', 'light', 'legacy', 'classic'].map((t) => (
{['dark', 'light', 'legacy', 'retro'].map((t) => (
<button
key={t}
onClick={() => setTheme(t)}

@ -71,9 +71,9 @@
}
/* ============================================
THEME: CLASSIC (90s Windows Style)
THEME: RETRO (90s Windows Style)
============================================ */
[data-theme="classic"] {
[data-theme="retro"] {
--bg-primary: #008080;
--bg-secondary: #c0c0c0;
--bg-tertiary: #dfdfdf;
@ -116,8 +116,8 @@ body {
}
/* Classic theme uses system fonts */
[data-theme="classic"] body,
[data-theme="classic"] * {
[data-theme="retro"] body,
[data-theme="retro"] * {
font-family: 'Tahoma', 'MS Sans Serif', 'Arial', sans-serif !important;
}
@ -136,7 +136,7 @@ body::before {
============================================ */
/* 3D Beveled border effect for classic theme */
[data-theme="classic"] .panel {
[data-theme="retro"] .panel {
background: #c0c0c0 !important;
border: none !important;
border-radius: 0 !important;
@ -149,8 +149,8 @@ body::before {
}
/* Classic window title bar style */
[data-theme="classic"] .panel-header,
[data-theme="classic"] .panel > div:first-child {
[data-theme="retro"] .panel-header,
[data-theme="retro"] .panel > div:first-child {
background: linear-gradient(90deg, #000080, #1084d0) !important;
color: #ffffff !important;
font-weight: bold !important;
@ -160,8 +160,8 @@ body::before {
}
/* Classic buttons */
[data-theme="classic"] button,
[data-theme="classic"] .map-style-btn {
[data-theme="retro"] button,
[data-theme="retro"] .map-style-btn {
background: #c0c0c0 !important;
color: #000000 !important;
border: none !important;
@ -176,14 +176,14 @@ body::before {
padding: 4px 12px !important;
}
[data-theme="classic"] button:hover,
[data-theme="classic"] .map-style-btn:hover {
[data-theme="retro"] button:hover,
[data-theme="retro"] .map-style-btn:hover {
background: #dfdfdf !important;
}
[data-theme="classic"] button:active,
[data-theme="classic"] .map-style-btn:active,
[data-theme="classic"] .map-style-btn.active {
[data-theme="retro"] button:active,
[data-theme="retro"] .map-style-btn:active,
[data-theme="retro"] .map-style-btn.active {
box-shadow:
inset 1px 1px 0 #404040,
inset -1px -1px 0 #ffffff,
@ -192,8 +192,8 @@ body::before {
}
/* Classic inputs */
[data-theme="classic"] input,
[data-theme="classic"] select {
[data-theme="retro"] input,
[data-theme="retro"] select {
background: #ffffff !important;
color: #000000 !important;
border: none !important;
@ -209,12 +209,12 @@ body::before {
}
/* Classic scrollbars */
[data-theme="classic"] ::-webkit-scrollbar {
[data-theme="retro"] ::-webkit-scrollbar {
width: 16px;
height: 16px;
}
[data-theme="classic"] ::-webkit-scrollbar-track {
[data-theme="retro"] ::-webkit-scrollbar-track {
background: #c0c0c0;
background-image:
linear-gradient(45deg, #808080 25%, transparent 25%),
@ -225,7 +225,7 @@ body::before {
background-position: 0 0, 0 2px, 2px -2px, -2px 0px;
}
[data-theme="classic"] ::-webkit-scrollbar-thumb {
[data-theme="retro"] ::-webkit-scrollbar-thumb {
background: #c0c0c0;
border-radius: 0;
box-shadow:
@ -235,7 +235,7 @@ body::before {
inset 2px 2px 0 #dfdfdf;
}
[data-theme="classic"] ::-webkit-scrollbar-button {
[data-theme="retro"] ::-webkit-scrollbar-button {
background: #c0c0c0;
box-shadow:
inset -1px -1px 0 #404040,
@ -245,56 +245,56 @@ body::before {
}
/* Classic links */
[data-theme="classic"] a {
[data-theme="retro"] a {
color: #0000ff !important;
text-decoration: underline !important;
}
[data-theme="classic"] a:visited {
[data-theme="retro"] a:visited {
color: #800080 !important;
}
/* Classic header styling */
[data-theme="classic"] [style*="gridColumn: '1 / -1'"] {
[data-theme="retro"] [style*="gridColumn: '1 / -1'"] {
background: #c0c0c0 !important;
}
/* Classic text colors override */
[data-theme="classic"] [style*="color: var(--accent-cyan)"],
[data-theme="classic"] [style*="color: var(--accent-amber)"],
[data-theme="classic"] [style*="color: var(--accent-green)"] {
[data-theme="retro"] [style*="color: var(--accent-cyan)"],
[data-theme="retro"] [style*="color: var(--accent-amber)"],
[data-theme="retro"] [style*="color: var(--accent-green)"] {
color: #000080 !important;
}
/* Classic modal/dialog styling */
[data-theme="classic"] [style*="position: fixed"][style*="background: rgba"] {
[data-theme="retro"] [style*="position: fixed"][style*="background: rgba"] {
background: rgba(0, 128, 128, 0.9) !important;
}
/* Classic table styling for DX cluster */
[data-theme="classic"] table,
[data-theme="classic"] [style*="display: grid"] {
[data-theme="retro"] table,
[data-theme="retro"] [style*="display: grid"] {
border-collapse: collapse;
}
[data-theme="classic"] tr:nth-child(even) {
[data-theme="retro"] tr:nth-child(even) {
background: #dfdfdf;
}
[data-theme="classic"] tr:nth-child(odd) {
[data-theme="retro"] tr:nth-child(odd) {
background: #c0c0c0;
}
/* Classic Leaflet overrides */
[data-theme="classic"] .leaflet-container {
[data-theme="retro"] .leaflet-container {
background: #000080 !important;
}
[data-theme="classic"] .leaflet-control-zoom {
[data-theme="retro"] .leaflet-control-zoom {
border-radius: 0 !important;
}
[data-theme="classic"] .leaflet-control-zoom a {
[data-theme="retro"] .leaflet-control-zoom a {
background: #c0c0c0 !important;
color: #000000 !important;
border-radius: 0 !important;
@ -303,7 +303,7 @@ body::before {
inset 1px 1px 0 #ffffff !important;
}
[data-theme="classic"] .leaflet-popup-content-wrapper {
[data-theme="retro"] .leaflet-popup-content-wrapper {
background: #c0c0c0 !important;
border-radius: 0 !important;
box-shadow:
@ -312,7 +312,7 @@ body::before {
2px 2px 4px rgba(0,0,0,0.5) !important;
}
[data-theme="classic"] .leaflet-popup-tip {
[data-theme="retro"] .leaflet-popup-tip {
background: #c0c0c0 !important;
}
@ -340,7 +340,7 @@ body::before {
}
/* Classic theme uses hourglass cursor for loading */
[data-theme="classic"] .loading-spinner {
[data-theme="retro"] .loading-spinner {
border-radius: 0;
border-color: #000000;
border-top-color: #808080;
@ -425,9 +425,9 @@ body::before {
}
/* Classic theme markers - square */
[data-theme="classic"] .custom-marker,
[data-theme="classic"] .de-marker,
[data-theme="classic"] .dx-marker {
[data-theme="retro"] .custom-marker,
[data-theme="retro"] .de-marker,
[data-theme="retro"] .dx-marker {
border-radius: 0 !important;
}
@ -542,7 +542,7 @@ body::before {
}
/* Classic tooltips */
[data-theme="classic"] .dx-tooltip {
[data-theme="retro"] .dx-tooltip {
background: #ffffcc !important;
border: 1px solid #000000 !important;
border-radius: 0 !important;

@ -7,7 +7,7 @@ export const DEFAULT_CONFIG = {
callsign: 'N0CALL',
location: { lat: 40.0150, lon: -105.2705 }, // Boulder, CO (default)
defaultDX: { lat: 35.6762, lon: 139.6503 }, // Tokyo
theme: 'dark', // 'dark', 'light', 'legacy', or 'classic'
theme: 'dark', // 'dark', 'light', 'legacy', or 'retro'
layout: 'modern', // 'modern' or 'legacy'
refreshIntervals: {
spaceWeather: 300000,

Loading…
Cancel
Save

Powered by TurnKey Linux.