From 50ab931933beda9bb79988c6f040a95b61345ac9 Mon Sep 17 00:00:00 2001 From: accius Date: Wed, 4 Feb 2026 00:22:13 -0500 Subject: [PATCH 1/7] merge to my test branch --- src/components/DXFilterManager.jsx | 2 +- src/hooks/useDXCluster.js | 8 +- src/plugins/layers/useEarthquakes.js | 37 ++- src/plugins/layers/useGrayLine.js | 366 +++++++++++++++++++-------- src/styles/main.css | 4 +- src/utils/callsign.js | 3 +- test_coords.py | 26 ++ test_earthquake_coords.js | 28 ++ 8 files changed, 350 insertions(+), 124 deletions(-) create mode 100644 test_coords.py create mode 100644 test_earthquake_coords.js 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:
{ // 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; } diff --git a/src/plugins/layers/useEarthquakes.js b/src/plugins/layers/useEarthquakes.js index 3396a89..bdca710 100644 --- a/src/plugins/layers/useEarthquakes.js +++ b/src/plugins/layers/useEarthquakes.js @@ -81,11 +81,27 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null }) { const coords = quake.geometry.coordinates; const props = quake.properties; const mag = props.mag; - const lat = coords[1]; - const lon = coords[0]; + + // GeoJSON standard format: [longitude, latitude, elevation] + // For Santa Lucía, Peru: [-70.5639, -15.6136, 206.486] + // coords[0] = -70.5639 = Longitude (W) + // coords[1] = -15.6136 = Latitude (S) + // coords[2] = 206.486 = Depth (km) + const lat = coords[1]; // Latitude (y-axis) + const lon = coords[0]; // Longitude (x-axis) const depth = coords[2]; const quakeId = quake.id; + // Debug logging with detailed info + console.log(`🌋 Earthquake ${quakeId}:`, { + place: props.place, + mag: mag, + geojson: `[lon=${coords[0]}, lat=${coords[1]}, depth=${coords[2]}]`, + extracted: `lat=${lat} (coords[1]), lon=${lon} (coords[0])`, + leafletMarkerCall: `L.marker([${lat}, ${lon}])`, + explanation: `Standard Leaflet [latitude, longitude] format - CSS position fixed` + }); + currentQuakeIds.add(quakeId); // Skip if invalid coordinates @@ -133,11 +149,20 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null }) { box-shadow: 0 2px 8px rgba(0,0,0,0.5); ">${waveIcon}
`, iconSize: [size, size], - iconAnchor: [size/2, size/2] + iconAnchor: [size/2, size/2], + popupAnchor: [0, 0] // Popup appears at the marker position (icon center) }); - console.log('Creating earthquake marker:', quakeId, 'M' + mag.toFixed(1), 'at', lat, lon, 'size:', size + 'px', 'color:', color); - const circle = L.marker([lat, lon], { + console.log(`📍 Creating marker for ${quakeId}: M${mag.toFixed(1)} at [lat=${lat}, lon=${lon}] - ${props.place}`); + + // Use standard Leaflet [latitude, longitude] format + // The popup was appearing in the correct location, confirming marker position is correct + // The icon was appearing offset due to CSS position: relative issue (now fixed) + const markerCoords = [lat, lon]; // CORRECT: [latitude, longitude] + + console.log(` → Creating L.marker([${markerCoords[0]}, ${markerCoords[1]}]) = [lat, lon]`); + + const circle = L.marker(markerCoords, { icon, opacity, zIndexOffset: 10000 // Ensure markers appear on top @@ -170,7 +195,7 @@ export function useLayer({ enabled = false, opacity = 0.9, map = null }) { } }, 10); - // Create pulsing ring effect + // Create pulsing ring effect - use same [lat, lon] format const pulseRing = L.circle([lat, lon], { radius: 50000, // 50km radius in meters fillColor: color, diff --git a/src/plugins/layers/useGrayLine.js b/src/plugins/layers/useGrayLine.js index 51df3b5..af4d115 100644 --- a/src/plugins/layers/useGrayLine.js +++ b/src/plugins/layers/useGrayLine.js @@ -100,6 +100,87 @@ function calculateSolarAltitude(date, latitude, longitude) { return altitude; } +// Split polyline at date line to avoid lines cutting across the map +function splitAtDateLine(points) { + if (points.length < 2) return [points]; + + // Check if line spans the full world (-180 to 180) + const lons = points.map(p => p[1]); + const minLon = Math.min(...lons); + const maxLon = Math.max(...lons); + const span = maxLon - minLon; + + console.log('🔍 splitAtDateLine debug:', { + totalPoints: points.length, + lonRange: `${minLon.toFixed(1)} to ${maxLon.toFixed(1)}`, + span: span.toFixed(1) + }); + + // If the line spans close to 360°, it wraps around the world + // We need to split it at the ±180° boundary + if (span > 350) { + console.log('🔍 Full-world span detected, splitting at ±180°'); + + // Strategy: Create two segments that meet at ±180° longitude + // Segment 1: Western hemisphere (-180° to slightly past 0°) + // Segment 2: Eastern hemisphere (slightly before 0° to +180°) + + const westSegment = []; // Points from -180° to ~0° + const eastSegment = []; // Points from ~0° to +180° + + // Sort points by longitude to ensure correct ordering + const sortedPoints = [...points].sort((a, b) => a[1] - b[1]); + + // Find the midpoint longitude (should be around 0°) + const midIndex = Math.floor(sortedPoints.length / 2); + + // Split at midpoint, with some overlap + westSegment.push(...sortedPoints.slice(0, midIndex + 1)); + eastSegment.push(...sortedPoints.slice(midIndex)); + + const segments = []; + if (westSegment.length >= 2) segments.push(westSegment); + if (eastSegment.length >= 2) segments.push(eastSegment); + + console.log('🔍 Split into segments:', segments.map(s => { + const lons = s.map(p => p[1]); + return { + points: s.length, + lonRange: `${Math.min(...lons).toFixed(1)} to ${Math.max(...lons).toFixed(1)}` + }; + })); + return segments; + } + + // Otherwise, check for sudden longitude jumps (traditional date line crossing) + const segments = []; + let currentSegment = [points[0]]; + + for (let i = 1; i < points.length; i++) { + const prev = points[i - 1]; + const curr = points[i]; + const prevLon = prev[1]; + const currLon = curr[1]; + const lonDiff = Math.abs(currLon - prevLon); + + // If longitude jumps more than 180°, we've crossed the date line + if (lonDiff > 180) { + console.log(`🔍 Date line jump detected at index ${i}: ${prevLon.toFixed(1)}° → ${currLon.toFixed(1)}°`); + segments.push(currentSegment); + currentSegment = [curr]; + } else { + currentSegment.push(curr); + } + } + + if (currentSegment.length > 0) { + segments.push(currentSegment); + } + + console.log('🔍 splitAtDateLine result:', segments.length, 'segments'); + return segments.filter(seg => seg.length >= 2); +} + // Generate terminator line for a specific solar altitude function generateTerminatorLine(date, solarAltitude = 0, numPoints = 360) { const points = []; @@ -113,64 +194,78 @@ function generateTerminatorLine(date, solarAltitude = 0, numPoints = 360) { const hourAngle = calculateHourAngle(date, lon); const haRad = hourAngle * Math.PI / 180; - // Use the solar altitude equation to solve for latitude - // sin(altitude) = sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(HA) - // Rearranging: sin(altitude) - sin(lat) * sin(dec) = cos(lat) * cos(dec) * cos(HA) - - // For terminator (altitude = 0), the equation simplifies - // We need to solve: tan(lat) = -tan(dec) / cos(HA) - const cosHA = Math.cos(haRad); const sinDec = Math.sin(decRad); const cosDec = Math.cos(decRad); const sinAlt = Math.sin(altRad); - // Solve using the quadratic formula or direct calculation - // sin(lat) = (sin(alt) - cos(lat) * cos(dec) * cos(HA)) / sin(dec) - - // Better approach: use atan2 for proper terminator calculation - // The terminator latitude for a given longitude is: - // lat = atan(-cos(HA) / tan(dec)) when dec != 0 - let lat; + // Check if solution exists (sun can reach this altitude at this longitude) + // For terminator and twilight, check if |cos(HA) * cos(dec)| <= 1 - sin(alt) * sin(dec) + const testValue = (sinAlt - sinDec * sinDec) / (cosDec * cosDec * cosHA * cosHA); + if (Math.abs(declination) < 0.01) { // Near equinox: terminator is nearly straight along equator lat = 0; + } else if (Math.abs(cosDec) < 0.001) { + // Near solstice: sun is directly over tropic, skip this point + continue; } else { // Standard case: calculate terminator latitude - // Formula: cos(lat) * cos(dec) * cos(HA) = -sin(lat) * sin(dec) (for altitude = 0) - // This gives: tan(lat) = -cos(HA) / tan(dec) - const tanDec = Math.tan(decRad); - if (Math.abs(tanDec) < 0.0001) { - lat = 0; - } else { - lat = Math.atan(-cosHA / tanDec) * 180 / Math.PI; - } - // For twilight (altitude < 0), we need to adjust - if (solarAltitude !== 0) { - // Use iterative solution for twilight calculations - // cos(lat) * cos(dec) * cos(HA) + sin(lat) * sin(dec) = sin(alt) + if (solarAltitude === 0) { + // Terminator (sunrise/sunset line) + // Formula: tan(lat) = -cos(HA) / tan(dec) + if (Math.abs(tanDec) > 0.0001) { + lat = Math.atan(-cosHA / tanDec) * 180 / Math.PI; + } else { + lat = 0; + } + } else { + // Twilight zones (negative solar altitude) + // Use Newton-Raphson iteration to solve for latitude + // Equation: sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(HA) = sin(alt) - // Newton-Raphson iteration to solve for latitude - let testLat = lat * Math.PI / 180; - for (let iter = 0; iter < 5; iter++) { + // Initial guess based on terminator + let testLat = Math.atan(-cosHA / tanDec); + + // Iterate to find solution + let converged = false; + for (let iter = 0; iter < 10; iter++) { const f = Math.sin(testLat) * sinDec + Math.cos(testLat) * cosDec * cosHA - sinAlt; const fPrime = Math.cos(testLat) * sinDec - Math.sin(testLat) * cosDec * cosHA; + + if (Math.abs(f) < 0.0001) { + converged = true; + break; + } + if (Math.abs(fPrime) > 0.0001) { testLat = testLat - f / fPrime; + } else { + break; } + + // Constrain to valid latitude range during iteration + testLat = Math.max(-Math.PI/2, Math.min(Math.PI/2, testLat)); + } + + // Only use the point if iteration converged + if (!converged) { + continue; } + lat = testLat * 180 / Math.PI; } } - // Clamp latitude to valid range - lat = Math.max(-90, Math.min(90, lat)); + // Strict clamping to valid latitude range + lat = Math.max(-85, Math.min(85, lat)); - if (isFinite(lat) && isFinite(lon)) { + // Only add point if it's valid and not at extreme latitude + if (isFinite(lat) && isFinite(lon) && Math.abs(lat) < 85) { points.push([lat, lon]); } } @@ -488,104 +583,159 @@ export function useLayer({ enabled = false, opacity = 0.5, map = null }) { // Main terminator (solar altitude = 0°) const terminator = generateTerminatorLine(currentTime, 0, 360); - const terminatorLine = L.polyline(terminator, { - color: '#ff6600', - weight: 3, - opacity: opacity * 0.8, - dashArray: '10, 5' + const terminatorSegments = splitAtDateLine(terminator); + + terminatorSegments.forEach(segment => { + const terminatorLine = L.polyline(segment, { + color: '#ff6600', + weight: 3, + opacity: opacity * 0.8, + dashArray: '10, 5' + }); + terminatorLine.bindPopup(` +
+ 🌅 Solar Terminator
+ Sun altitude: 0°
+ Enhanced HF propagation zone
+ UTC: ${currentTime.toUTCString()} +
+ `); + terminatorLine.addTo(map); + newLayers.push(terminatorLine); }); - terminatorLine.bindPopup(` -
- 🌅 Solar Terminator
- Sun altitude: 0°
- Enhanced HF propagation zone
- UTC: ${currentTime.toUTCString()} -
- `); - terminatorLine.addTo(map); - newLayers.push(terminatorLine); // Enhanced DX zone (±5° from terminator) if (showEnhancedZone) { const enhancedUpper = generateTerminatorLine(currentTime, 5, 360); const enhancedLower = generateTerminatorLine(currentTime, -5, 360); - // Create polygon for enhanced zone - const enhancedZone = [...enhancedUpper, ...enhancedLower.reverse()]; - const enhancedPoly = L.polygon(enhancedZone, { - color: '#ffaa00', - fillColor: '#ffaa00', - fillOpacity: opacity * 0.15, - weight: 1, - opacity: opacity * 0.3 - }); - enhancedPoly.bindPopup(` -
- ⭐ Enhanced DX Zone
- Best HF propagation window
- ±5° from terminator
- Ideal for long-distance contacts -
- `); - enhancedPoly.addTo(map); - newLayers.push(enhancedPoly); + // Only create polygon if we have valid points + if (enhancedUpper.length > 2 && enhancedLower.length > 2) { + // Split both upper and lower lines at date line + const upperSegments = splitAtDateLine(enhancedUpper); + const lowerSegments = splitAtDateLine(enhancedLower); + + console.log('🔶 Enhanced DX Zone segments:', { + upperCount: upperSegments.length, + lowerCount: lowerSegments.length, + upperSegmentLengths: upperSegments.map(s => s.length), + lowerSegmentLengths: lowerSegments.map(s => s.length) + }); + + // Create polygon for each corresponding segment pair + // Both upper and lower should have same number of segments + const numSegments = Math.min(upperSegments.length, lowerSegments.length); + + for (let i = 0; i < numSegments; i++) { + const upperSeg = upperSegments[i]; + const lowerSeg = lowerSegments[i]; + + if (upperSeg.length > 1 && lowerSeg.length > 1) { + // Create polygon from upper segment + reversed lower segment + // This creates a closed shape between the two lines + const enhancedZone = [...upperSeg, ...lowerSeg.slice().reverse()]; + + // Debug: Show longitude range of this polygon + const polyLons = enhancedZone.map(p => p[1]); + const polyMinLon = Math.min(...polyLons); + const polyMaxLon = Math.max(...polyLons); + + console.log(`🔶 Creating Enhanced DX polygon segment ${i+1}/${numSegments}:`, { + upperPoints: upperSeg.length, + lowerPoints: lowerSeg.length, + totalPolygonPoints: enhancedZone.length, + lonRange: `${polyMinLon.toFixed(1)} to ${polyMaxLon.toFixed(1)}` + }); + + const enhancedPoly = L.polygon(enhancedZone, { + color: '#ffaa00', + fillColor: '#ffaa00', + fillOpacity: opacity * 0.15, + weight: 1, + opacity: opacity * 0.3 + }); + enhancedPoly.bindPopup(` +
+ ⭐ Enhanced DX Zone
+ Best HF propagation window
+ ±5° from terminator
+ Ideal for long-distance contacts +
+ `); + enhancedPoly.addTo(map); + newLayers.push(enhancedPoly); + } + } + } } // Twilight zones if (showTwilight) { // Civil twilight (sun altitude -6°) const civilTwilight = generateTerminatorLine(currentTime, -6, 360); - const civilLine = L.polyline(civilTwilight, { - color: '#4488ff', - weight: 2, - opacity: twilightOpacity * 0.6, - dashArray: '5, 5' + const civilSegments = splitAtDateLine(civilTwilight); + + civilSegments.forEach(segment => { + const civilLine = L.polyline(segment, { + color: '#4488ff', + weight: 2, + opacity: twilightOpacity * 0.6, + dashArray: '5, 5' + }); + civilLine.bindPopup(` +
+ 🌆 Civil Twilight
+ Sun altitude: -6°
+ Good propagation conditions +
+ `); + civilLine.addTo(map); + newLayers.push(civilLine); }); - civilLine.bindPopup(` -
- 🌆 Civil Twilight
- Sun altitude: -6°
- Good propagation conditions -
- `); - civilLine.addTo(map); - newLayers.push(civilLine); // Nautical twilight (sun altitude -12°) const nauticalTwilight = generateTerminatorLine(currentTime, -12, 360); - const nauticalLine = L.polyline(nauticalTwilight, { - color: '#6666ff', - weight: 1.5, - opacity: twilightOpacity * 0.4, - dashArray: '3, 3' + const nauticalSegments = splitAtDateLine(nauticalTwilight); + + nauticalSegments.forEach(segment => { + const nauticalLine = L.polyline(segment, { + color: '#6666ff', + weight: 1.5, + opacity: twilightOpacity * 0.4, + dashArray: '3, 3' + }); + nauticalLine.bindPopup(` +
+ 🌃 Nautical Twilight
+ Sun altitude: -12°
+ Moderate propagation +
+ `); + nauticalLine.addTo(map); + newLayers.push(nauticalLine); }); - nauticalLine.bindPopup(` -
- 🌃 Nautical Twilight
- Sun altitude: -12°
- Moderate propagation -
- `); - nauticalLine.addTo(map); - newLayers.push(nauticalLine); // Astronomical twilight (sun altitude -18°) const astroTwilight = generateTerminatorLine(currentTime, -18, 360); - const astroLine = L.polyline(astroTwilight, { - color: '#8888ff', - weight: 1, - opacity: twilightOpacity * 0.3, - dashArray: '2, 2' + const astroSegments = splitAtDateLine(astroTwilight); + + astroSegments.forEach(segment => { + const astroLine = L.polyline(segment, { + color: '#8888ff', + weight: 1, + opacity: twilightOpacity * 0.3, + dashArray: '2, 2' + }); + astroLine.bindPopup(` +
+ 🌌 Astronomical Twilight
+ Sun altitude: -18°
+ Transition to night propagation +
+ `); + astroLine.addTo(map); + newLayers.push(astroLine); }); - astroLine.bindPopup(` -
- 🌌 Astronomical Twilight
- Sun altitude: -18°
- Transition to night propagation -
- `); - astroLine.addTo(map); - newLayers.push(astroLine); } setLayers(newLayers); diff --git a/src/styles/main.css b/src/styles/main.css index 022b7a4..ad8b710 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -876,7 +876,7 @@ body::before { .earthquake-icon { z-index: 10000 !important; pointer-events: auto; - position: relative !important; + /* Removed position: relative - was causing icons to appear offset from marker position */ } .earthquake-icon div { @@ -885,7 +885,7 @@ body::before { justify-content: center; cursor: pointer; user-select: none; - position: relative; + /* position: relative removed here too */ z-index: 10000 !important; } 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/test_coords.py b/test_coords.py new file mode 100644 index 0000000..60cf381 --- /dev/null +++ b/test_coords.py @@ -0,0 +1,26 @@ +# Test coordinate wrapping for Kamchatka, Russia +# Kamchatka is around: 56°N, 162°E + +lat = 56.0 # Correct latitude +lon = 162.0 # Correct longitude (Eastern hemisphere) + +print(f"Kamchatka, Russia:") +print(f" Correct coordinates: lat={lat}, lon={lon}") +print(f" Leaflet marker: L.marker([{lat}, {lon}])") +print(f" This should plot in: Eastern Russia (Kamchatka Peninsula)") +print() + +# What if longitude is negative? +lon_neg = -162.0 +print(f"If longitude was negative:") +print(f" L.marker([{lat}, {lon_neg}])") +print(f" This would plot in: Western Alaska (near Bering Strait)") +print() + +# Peru example +peru_lat = -15.6 +peru_lon = -70.6 +print(f"Peru earthquake:") +print(f" Correct coordinates: lat={peru_lat}, lon={peru_lon}") +print(f" Leaflet marker: L.marker([{peru_lat}, {peru_lon}])") +print(f" This should plot in: Peru, South America") diff --git a/test_earthquake_coords.js b/test_earthquake_coords.js new file mode 100644 index 0000000..3e70a8b --- /dev/null +++ b/test_earthquake_coords.js @@ -0,0 +1,28 @@ +// Test to understand the coordinate flow + +// Sample Dominican Republic earthquake from USGS +const geojsonData = { + geometry: { + coordinates: [-68.625, 18.0365, 75.0] // [lon, lat, depth] - GeoJSON format + }, + properties: { + place: "37 km S of Boca de Yuma, Dominican Republic" + } +}; + +// Current code extraction +const coords = geojsonData.geometry.coordinates; +const lat = coords[1]; // 18.0365 +const lon = coords[0]; // -68.625 + +console.log("GeoJSON coordinates:", coords); +console.log("Extracted lat:", lat, "(should be 18.0365)"); +console.log("Extracted lon:", lon, "(should be -68.625)"); +console.log(""); +console.log("If we call L.marker([lat, lon]):"); +console.log(" L.marker([" + lat + ", " + lon + "])"); +console.log(" This should plot at: 18.0365°N, 68.625°W (Dominican Republic)"); +console.log(""); +console.log("If we call L.marker([lon, lat]):"); +console.log(" L.marker([" + lon + ", " + lat + "])"); +console.log(" This would plot at: -68.625°S, 18.0365°E (Indian Ocean!)"); From bb16dff9280907736f2751fefe4acf2aff192d7a Mon Sep 17 00:00:00 2001 From: accius Date: Wed, 4 Feb 2026 00:23:27 -0500 Subject: [PATCH 2/7] small screen support --- src/App.jsx | 433 ++++++++++++++ src/App.jsx.bak | 947 +++++++++++++++++++++++++++++++ src/components/SettingsPanel.jsx | 8 +- src/lang/de.json | 6 +- src/lang/en.json | 4 + src/lang/es.json | 6 +- src/lang/fr.json | 6 +- src/lang/it.json | 6 +- src/lang/ja.json | 6 +- src/lang/ko.json | 6 +- src/lang/nl.json | 6 +- src/lang/pt.json | 6 +- src/styles/main.css | 102 ++++ 13 files changed, 1531 insertions(+), 11 deletions(-) create mode 100644 src/App.jsx.bak diff --git a/src/App.jsx b/src/App.jsx index b10ab3b..8a5f05e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -572,6 +572,439 @@ const App = () => { 35
+ ) : config.layout === 'tablet' ? ( + /* TABLET LAYOUT - Optimized for 7-10" widescreen displays (16:9) */ +
+ {/* COMPACT TOP BAR */} +
+ {/* Callsign */} + setShowSettings(true)} + title="Settings" + > + {config.callsign} + + + {/* UTC */} +
+ UTC + {utcTime} +
+ + {/* Local */} +
+ LOC + {localTime} +
+ + {/* Solar Quick Stats */} +
+ + SFI + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + + + K + = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} + + + + SSN + {solarIndices?.data?.ssn?.current || '--'} + +
+ + {/* Controls */} +
+ + +
+
+ + {/* MAIN AREA: Map + Data Sidebar */} +
+ {/* MAP */} +
+ +
+ + {/* DATA SIDEBAR */} +
+ {/* Band Conditions Grid */} +
+
Band Conditions
+
+ {(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 ( +
+
{band.band}
+
{band.condition}
+
+ ); + })} +
+ {/* MUF/LUF */} + {propagation.data && ( +
+ MUF {propagation.data.muf || '?'} + LUF {propagation.data.luf || '?'} +
+ )} +
+ + {/* Compact DX Cluster */} +
+
+ DX Cluster + {dxCluster.data?.length || 0} spots +
+
+ {dxCluster.data?.slice(0, 30).map((spot, i) => ( +
setHoveredSpot(spot)} + onMouseLeave={() => setHoveredSpot(null)} + > + {parseFloat(spot.freq).toFixed(1)} + {spot.call} + {spot.time || '--'} +
+ ))} +
+
+
+
+
+ + ) : config.layout === 'compact' ? ( + /* COMPACT LAYOUT - Optimized for 4:3 screens and data-first display */ +
+ {/* TOP: Callsign + Times + Solar */} +
+ {/* Row 1: Callsign + Times */} +
+ setShowSettings(true)} + title="Settings" + > + {config.callsign} + +
+
+
UTC
+
{utcTime}
+
{utcDate}
+
+
+
Local
+
{localTime}
+
{localDate}
+
+
+
+ + +
+
+ {/* Row 2: Solar indices inline */} +
+ + SFI + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + + + K + = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} + + + + SSN + {solarIndices?.data?.ssn?.current || '--'} + + {propagation.data && ( + <> + + MUF + {propagation.data.muf || '?'} MHz + + + LUF + {propagation.data.luf || '?'} MHz + + + )} + {localWeather?.data && ( + + {localWeather.data.icon} + {localWeather.data.temp}°{localWeather.data.tempUnit || tempUnit} + + )} +
+
+ + {/* BAND CONDITIONS - Full Width */} +
+
+ {(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 ( +
+
{band.band}
+
{band.condition}
+
+ ); + })} +
+
+ + {/* MAIN: Map + DX Cluster side by side */} +
+ {/* Map */} +
+ +
+ {deGrid} → {dxGrid} • Click map to set DX +
+
+ + {/* Compact DX Cluster */} +
+
+ DX Cluster + {dxCluster.data?.length || 0} +
+
+ {dxCluster.data?.slice(0, 40).map((spot, i) => ( +
setHoveredSpot(spot)} + onMouseLeave={() => setHoveredSpot(null)} + > + {parseFloat(spot.freq).toFixed(1)} + {spot.call} + {spot.time || '--'} +
+ ))} +
+
+
+
+ ) : ( /* MODERN LAYOUT */
{ + // Configuration state - initially use defaults, then load from server + const [config, setConfig] = useState(loadConfig); + const [configLoaded, setConfigLoaded] = useState(false); + const [currentTime, setCurrentTime] = useState(new Date()); + const [startTime] = useState(Date.now()); + const [uptime, setUptime] = useState('0d 0h 0m'); + + // Load server configuration on startup (only matters for first-time users) + useEffect(() => { + const initConfig = async () => { + // Fetch server config (provides defaults for new users without localStorage) + await fetchServerConfig(); + + // Load config - localStorage takes priority over server config + const loadedConfig = loadConfig(); + setConfig(loadedConfig); + setConfigLoaded(true); + + // Only show settings if user has no saved config AND no valid callsign + // This prevents the popup from appearing every refresh + const hasLocalStorage = localStorage.getItem('openhamclock_config'); + if (!hasLocalStorage && loadedConfig.callsign === 'N0CALL') { + setShowSettings(true); + } + }; + initConfig(); + }, []); + + // DX Location with localStorage persistence + const [dxLocation, setDxLocation] = useState(() => { + try { + const stored = localStorage.getItem('openhamclock_dxLocation'); + if (stored) { + const parsed = JSON.parse(stored); + if (parsed.lat && parsed.lon) return parsed; + } + } catch (e) {} + return config.defaultDX; + }); + + useEffect(() => { + try { + localStorage.setItem('openhamclock_dxLocation', JSON.stringify(dxLocation)); + } catch (e) {} + }, [dxLocation]); + + // UI state + const [showSettings, setShowSettings] = useState(false); + const [showDXFilters, setShowDXFilters] = useState(false); + const [showPSKFilters, setShowPSKFilters] = useState(false); + const [weatherExpanded, setWeatherExpanded] = useState(() => { + try { return localStorage.getItem('openhamclock_weatherExpanded') === 'true'; } catch { return false; } + }); + const [tempUnit, setTempUnit] = useState(() => { + try { return localStorage.getItem('openhamclock_tempUnit') || 'F'; } catch { return 'F'; } + }); + const [isFullscreen, setIsFullscreen] = useState(false); + + // Map layer visibility + const [mapLayers, setMapLayers] = useState(() => { + try { + const stored = localStorage.getItem('openhamclock_mapLayers'); + const defaults = { showDXPaths: true, showDXLabels: true, showPOTA: true, showSatellites: false, showPSKReporter: true, showWSJTX: true }; + return stored ? { ...defaults, ...JSON.parse(stored) } : defaults; + } catch (e) { return { showDXPaths: true, showDXLabels: true, showPOTA: true, showSatellites: false, showPSKReporter: true, showWSJTX: true }; } + }); + + useEffect(() => { + try { + localStorage.setItem('openhamclock_mapLayers', JSON.stringify(mapLayers)); + } catch (e) {} + }, [mapLayers]); + + const [hoveredSpot, setHoveredSpot] = useState(null); + + const toggleDXPaths = useCallback(() => setMapLayers(prev => ({ ...prev, showDXPaths: !prev.showDXPaths })), []); + const toggleDXLabels = useCallback(() => setMapLayers(prev => ({ ...prev, showDXLabels: !prev.showDXLabels })), []); + const togglePOTA = useCallback(() => setMapLayers(prev => ({ ...prev, showPOTA: !prev.showPOTA })), []); + const toggleSatellites = useCallback(() => setMapLayers(prev => ({ ...prev, showSatellites: !prev.showSatellites })), []); + const togglePSKReporter = useCallback(() => setMapLayers(prev => ({ ...prev, showPSKReporter: !prev.showPSKReporter })), []); + const toggleWSJTX = useCallback(() => setMapLayers(prev => ({ ...prev, showWSJTX: !prev.showWSJTX })), []); + + // 12/24 hour format + const [use12Hour, setUse12Hour] = useState(() => { + try { + return localStorage.getItem('openhamclock_use12Hour') === 'true'; + } catch (e) { return false; } + }); + + useEffect(() => { + try { + localStorage.setItem('openhamclock_use12Hour', use12Hour.toString()); + } catch (e) {} + }, [use12Hour]); + + const handleTimeFormatToggle = useCallback(() => setUse12Hour(prev => !prev), []); + + // Fullscreen + const handleFullscreenToggle = useCallback(() => { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen().then(() => setIsFullscreen(true)).catch(() => {}); + } else { + document.exitFullscreen().then(() => setIsFullscreen(false)).catch(() => {}); + } + }, []); + + useEffect(() => { + const handler = () => setIsFullscreen(!!document.fullscreenElement); + document.addEventListener('fullscreenchange', handler); + return () => document.removeEventListener('fullscreenchange', handler); + }, []); + + useEffect(() => { + applyTheme(config.theme || 'dark'); + }, []); + + // Config save handler - persists to localStorage + const handleSaveConfig = (newConfig) => { + setConfig(newConfig); + saveConfig(newConfig); + applyTheme(newConfig.theme || 'dark'); + console.log('[Config] Saved to localStorage:', newConfig.callsign); + }; + + // Data hooks + const spaceWeather = useSpaceWeather(); + const bandConditions = useBandConditions(spaceWeather.data); + const solarIndices = useSolarIndices(); + const potaSpots = usePOTASpots(); + + // DX Filters + const [dxFilters, setDxFilters] = useState(() => { + try { + const stored = localStorage.getItem('openhamclock_dxFilters'); + return stored ? JSON.parse(stored) : {}; + } catch (e) { return {}; } + }); + + useEffect(() => { + try { + localStorage.setItem('openhamclock_dxFilters', JSON.stringify(dxFilters)); + } catch (e) {} + }, [dxFilters]); + + // PSKReporter Filters + const [pskFilters, setPskFilters] = useState(() => { + try { + const stored = localStorage.getItem('openhamclock_pskFilters'); + return stored ? JSON.parse(stored) : {}; + } catch (e) { return {}; } + }); + + useEffect(() => { + try { + localStorage.setItem('openhamclock_pskFilters', JSON.stringify(pskFilters)); + } catch (e) {} + }, [pskFilters]); + + const dxCluster = useDXCluster(config.dxClusterSource || 'auto', dxFilters); + const dxPaths = useDXPaths(); + const dxpeditions = useDXpeditions(); + const contests = useContests(); + const propagation = usePropagation(config.location, dxLocation); + const mySpots = useMySpots(config.callsign); + const satellites = useSatellites(config.location); + const localWeather = useLocalWeather(config.location, tempUnit); + const pskReporter = usePSKReporter(config.callsign, { minutes: 15, enabled: config.callsign !== 'N0CALL' }); + const wsjtx = useWSJTX(); + + // Filter PSKReporter spots for map display + const filteredPskSpots = useMemo(() => { + const allSpots = [...(pskReporter.txReports || []), ...(pskReporter.rxReports || [])]; + if (!pskFilters?.bands?.length && !pskFilters?.grids?.length && !pskFilters?.modes?.length) { + return allSpots; + } + return allSpots.filter(spot => { + if (pskFilters?.bands?.length && !pskFilters.bands.includes(spot.band)) return false; + if (pskFilters?.modes?.length && !pskFilters.modes.includes(spot.mode)) return false; + if (pskFilters?.grids?.length) { + const grid = spot.receiverGrid || spot.senderGrid; + if (!grid) return false; + const gridPrefix = grid.substring(0, 2).toUpperCase(); + if (!pskFilters.grids.includes(gridPrefix)) return false; + } + return true; + }); + }, [pskReporter.txReports, pskReporter.rxReports, pskFilters]); + + // Filter WSJT-X decodes for map display (only those with lat/lon from grid) + const wsjtxMapSpots = useMemo(() => { + return wsjtx.decodes.filter(d => d.lat && d.lon && d.type === 'CQ'); + }, [wsjtx.decodes]); + + // Computed values + const deGrid = useMemo(() => calculateGridSquare(config.location.lat, config.location.lon), [config.location]); + const dxGrid = useMemo(() => calculateGridSquare(dxLocation.lat, dxLocation.lon), [dxLocation]); + const deSunTimes = useMemo(() => calculateSunTimes(config.location.lat, config.location.lon, currentTime), [config.location, currentTime]); + const dxSunTimes = useMemo(() => calculateSunTimes(dxLocation.lat, dxLocation.lon, currentTime), [dxLocation, currentTime]); + + // Time update + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()); + const elapsed = Date.now() - startTime; + const d = Math.floor(elapsed / 86400000); + const h = Math.floor((elapsed % 86400000) / 3600000); + const m = Math.floor((elapsed % 3600000) / 60000); + setUptime(`${d}d ${h}h ${m}m`); + }, 1000); + return () => clearInterval(timer); + }, [startTime]); + + const handleDXChange = useCallback((coords) => { + setDxLocation({ lat: coords.lat, lon: coords.lon }); + }, []); + + // Format times — use explicit timezone if configured (fixes privacy browsers like Librewolf + // that spoof timezone to UTC via privacy.resistFingerprinting) + const utcTime = currentTime.toISOString().substr(11, 8); + const utcDate = currentTime.toISOString().substr(0, 10); + const localTimeOpts = { hour12: use12Hour }; + const localDateOpts = { weekday: 'short', month: 'short', day: 'numeric' }; + if (config.timezone) { + localTimeOpts.timeZone = config.timezone; + localDateOpts.timeZone = config.timezone; + } + const localTime = currentTime.toLocaleTimeString('en-US', localTimeOpts); + const localDate = currentTime.toLocaleDateString('en-US', localDateOpts); + + // Scale for small screens + const [scale, setScale] = useState(1); + useEffect(() => { + const calculateScale = () => { + const minWidth = 1200; + const minHeight = 800; + const scaleX = window.innerWidth / minWidth; + const scaleY = window.innerHeight / minHeight; + setScale(Math.min(scaleX, scaleY, 1)); + }; + calculateScale(); + window.addEventListener('resize', calculateScale); + return () => window.removeEventListener('resize', calculateScale); + }, []); + + return ( +
+ {config.layout === 'classic' ? ( + /* CLASSIC HAMCLOCK-STYLE LAYOUT */ +
+ {/* TOP BAR - HamClock style */} +
+ {/* Callsign & Time */} +
+
setShowSettings(true)} + title="Click for settings" + > + {config.callsign} +
+
+ Up 35d 18h • v4.20 +
+
+
+ {utcTime}:{String(new Date().getUTCSeconds()).padStart(2, '0')} +
+
+ {utcDate} UTC +
+
+
+ + {/* Solar Indices - SSN & SFI */} +
+ {/* SSN */} +
+
Sunspot Number
+
+
+ {solarIndices?.data?.ssn?.history?.length > 0 && ( + + {(() => { + 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 ; + })()} + + )} +
+
+ {solarIndices?.data?.ssn?.current || '--'} +
+
+
-30 Days
+
+ + {/* SFI */} +
+
10.7 cm Solar flux
+
+
+ {solarIndices?.data?.sfi?.history?.length > 0 && ( + + {(() => { + 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 ; + })()} + + )} +
+
+ {solarIndices?.data?.sfi?.current || '--'} +
+
+
-30 Days +7
+
+
+ + {/* Live Spots & Indices */} +
+ {/* Live Spots by Band */} +
+
Live Spots
+
of {deGrid} - 15 mins
+
+ {[ + { 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 => ( +
+ {b.band} + + {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} + +
+ ))} +
+
+ + {/* Space Weather Indices */} +
+
+
X-Ray
+
M3.0
+
+
+
Kp
+
{solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'}
+
+
+
Bz
+
-0
+
+
+
Aurora
+
18
+
+
+
+
+ + {/* MAIN AREA */} +
+ {/* DX Cluster List */} +
+
+ Cluster + dxspider.co.uk:7300 +
+
+ {dxCluster.data?.slice(0, 25).map((spot, i) => ( +
setHoveredSpot(spot)} + onMouseLeave={() => setHoveredSpot(null)} + > + {parseFloat(spot.freq).toFixed(1)} + {spot.call} + {spot.time || '--'} +
+ ))} +
+
+ + {/* Map */} +
+ + + {/* Settings button overlay */} + +
+
+ + {/* BOTTOM - Frequency Scale */} +
+ MHz + 5 + 10 + 15 + 20 + 25 + 30 + 35 +
+
+ ) : ( + /* MODERN LAYOUT */ +
+ {/* TOP BAR */} +
setShowSettings(true)} + onFullscreenToggle={handleFullscreenToggle} + isFullscreen={isFullscreen} + /> + + {/* LEFT SIDEBAR */} +
+ {/* DE Location + Weather */} +
+
📍 DE - YOUR LOCATION
+
+
{deGrid}
+
{config.location.lat.toFixed(4)}°, {config.location.lon.toFixed(4)}°
+
+ + {deSunTimes.sunrise} + + {deSunTimes.sunset} +
+
+ + {/* Local Weather — compact by default, click to expand */} + {localWeather.data && (() => { + const w = localWeather.data; + const deg = `°${w.tempUnit || tempUnit}`; + const wind = w.windUnit || 'mph'; + const vis = w.visUnit || 'mi'; + return ( +
+ {/* Compact summary row — always visible */} +
+
{ const next = !weatherExpanded; setWeatherExpanded(next); try { localStorage.setItem('openhamclock_weatherExpanded', next.toString()); } catch {} }} + style={{ + display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer', + userSelect: 'none', flex: 1, minWidth: 0, + }} + > + {w.icon} + + {w.temp}{deg} + + {w.description} + + 💨{w.windSpeed} + + +
+ {/* F/C toggle */} + +
+ + {/* Expanded details */} + {weatherExpanded && ( +
+ {/* Feels like + hi/lo */} +
+ {w.feelsLike !== w.temp && ( + Feels like {w.feelsLike}{deg} + )} + {w.todayHigh != null && ( + + ▲{w.todayHigh}° + {' '} + ▼{w.todayLow}° + + )} +
+ + {/* Detail grid */} +
+
+ 💨 Wind + {w.windDir} {w.windSpeed} {wind} +
+
+ 💧 Humidity + {w.humidity}% +
+ {w.windGusts > w.windSpeed + 5 && ( +
+ 🌬️ Gusts + {w.windGusts} {wind} +
+ )} +
+ 🌡️ Dew Pt + {w.dewPoint}{deg} +
+ {w.pressure && ( +
+ 🔵 Pressure + {w.pressure} hPa +
+ )} +
+ ☁️ Clouds + {w.cloudCover}% +
+ {w.visibility && ( +
+ 👁️ Vis + {w.visibility} {vis} +
+ )} + {w.uvIndex > 0 && ( +
+ ☀️ UV + = 8 ? '#ef4444' : w.uvIndex >= 6 ? '#f97316' : w.uvIndex >= 3 ? '#eab308' : 'var(--text-secondary)' }}> + {w.uvIndex.toFixed(1)} + +
+ )} +
+ + {/* 3-Day Forecast */} + {w.daily?.length > 0 && ( +
+
FORECAST
+
+ {w.daily.map((day, i) => ( +
+
{i === 0 ? 'Today' : day.date}
+
{day.icon}
+
+ {day.high}° + / + {day.low}° +
+ {day.precipProb > 0 && ( +
+ 💧{day.precipProb}% +
+ )} +
+ ))} +
+
+ )} +
+ )} +
+ ); + })()} +
+ + {/* DX Location */} +
+
🎯 DX - TARGET
+
+
{dxGrid}
+
{dxLocation.lat.toFixed(4)}°, {dxLocation.lon.toFixed(4)}°
+
+ + {dxSunTimes.sunrise} + + {dxSunTimes.sunset} +
+
+
+ + {/* Solar Panel */} + + + {/* VOACAP/Propagation Panel */} + +
+ + {/* CENTER - MAP */} +
+ +
+ Click map to set DX • 73 de {config.callsign} +
+
+ + {/* RIGHT SIDEBAR */} +
+ {/* DX Cluster - primary panel, takes most space */} +
+ setShowDXFilters(true)} + onHoverSpot={setHoveredSpot} + hoveredSpot={hoveredSpot} + showOnMap={mapLayers.showDXPaths} + onToggleMap={toggleDXPaths} + /> +
+ + {/* PSKReporter + WSJT-X - digital mode spots */} +
+ setShowPSKFilters(true)} + onShowOnMap={(report) => { + if (report.lat && report.lon) { + setDxLocation({ lat: report.lat, lon: report.lon, call: report.receiver || report.sender }); + } + }} + wsjtxDecodes={wsjtx.decodes} + wsjtxClients={wsjtx.clients} + wsjtxQsos={wsjtx.qsos} + wsjtxStats={wsjtx.stats} + wsjtxLoading={wsjtx.loading} + wsjtxEnabled={wsjtx.enabled} + wsjtxPort={wsjtx.port} + wsjtxRelayEnabled={wsjtx.relayEnabled} + wsjtxRelayConnected={wsjtx.relayConnected} + wsjtxSessionId={wsjtx.sessionId} + showWSJTXOnMap={mapLayers.showWSJTX} + onToggleWSJTXMap={toggleWSJTX} + /> +
+ + {/* DXpeditions */} +
+ +
+ + {/* POTA */} +
+ +
+ + {/* Contests - at bottom, compact */} +
+ +
+
+
+ )} + + {/* Modals */} + setShowSettings(false)} + config={config} + onSave={handleSaveConfig} + /> + setShowDXFilters(false)} + /> + setShowPSKFilters(false)} + /> +
+ ); +}; + +export default App; diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index 54bb296..d10eb61 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -176,7 +176,9 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { const layoutDescriptions = { modern: t('station.settings.layout.modern.describe'), - classic: t('station.settings.layout.classic.describe') + classic: t('station.settings.layout.classic.describe'), + tablet: t('station.settings.layout.tablet.describe'), + compact: t('station.settings.layout.compact.describe') }; return ( @@ -461,7 +463,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { {t('station.settings.layout')}
- {['modern', 'classic'].map((l) => ( + {['modern', 'classic', 'tablet', 'compact'].map((l) => ( ))}
diff --git a/src/lang/de.json b/src/lang/de.json index 7f23a69..bd17759 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Wetterdaten © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/en.json b/src/lang/en.json index 49a03bd..08a0d2a 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -28,6 +28,10 @@ "station.settings.layout.classic.describe": "→ Original HamClock-style layout", "station.settings.layout.modern": "Modern", "station.settings.layout.modern.describe": "→ Modern responsive grid layout", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens", "station.settings.latitude": "Latitude", "station.settings.locator": "Grid Square (or enter Lat/Lon below)", "station.settings.longitude": "Longitude", diff --git a/src/lang/es.json b/src/lang/es.json index 4bda44d..dcd99e0 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -54,5 +54,9 @@ "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" + "plugins.layers.wxradar.attribution": "Datos meteorológicos © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/fr.json b/src/lang/fr.json index cae97fc..5d33094 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Données météo © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/it.json b/src/lang/it.json index b18d457..d2d24fa 100644 --- a/src/lang/it.json +++ b/src/lang/it.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Dati meteo © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/ja.json b/src/lang/ja.json index 59d4447..1407a08 100644 --- a/src/lang/ja.json +++ b/src/lang/ja.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "気象データ © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/ko.json b/src/lang/ko.json index 8d703c0..21db534 100644 --- a/src/lang/ko.json +++ b/src/lang/ko.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "기상 데이터 © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/nl.json b/src/lang/nl.json index b45fcb5..b46b5d9 100644 --- a/src/lang/nl.json +++ b/src/lang/nl.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Weergegevens © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/pt.json b/src/lang/pt.json index 8091927..4cd8cb2 100644 --- a/src/lang/pt.json +++ b/src/lang/pt.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Dados meteorológicos © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/styles/main.css b/src/styles/main.css index ad8b710..eda3aa7 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -905,3 +905,105 @@ body::before { position: relative; z-index: 10000 !important; } + +/* ============================================ + RESPONSIVE: Phone & Small Screen Overrides + ============================================ */ + +/* Tablets and small screens (under 1024px) */ +@media (max-width: 1024px) { + .panel { + padding: 6px !important; + border-radius: 4px !important; + } + + .panel-header { + font-size: 11px !important; + padding: 4px 6px !important; + } +} + +/* Phone landscape and small tablets (under 768px) */ +@media (max-width: 768px) { + /* Force single-column on modern layout */ + .ohc-mobile-stack { + display: flex !important; + flex-direction: column !important; + grid-template-columns: unset !important; + } + + .panel { + padding: 4px !important; + margin-bottom: 2px !important; + border-radius: 3px !important; + } + + .panel-header { + font-size: 10px !important; + padding: 3px 6px !important; + } + + /* Prevent horizontal overflow */ + body { + overflow-x: hidden; + } +} + +/* Phone portrait (under 480px) */ +@media (max-width: 480px) { + /* Extra compact for phones */ + .panel { + padding: 3px !important; + font-size: 11px !important; + } + + .panel-header { + font-size: 9px !important; + } + + /* Reduce font sizes globally for very small screens */ + body { + font-size: 12px; + } +} + +/* Short screens (Pi 7" touchscreen at 480px height) */ +@media (max-height: 520px) { + .panel { + padding: 3px !important; + } + + .panel-header { + padding: 2px 4px !important; + font-size: 10px !important; + } +} + +/* Touch device improvements */ +@media (pointer: coarse) { + /* Larger tap targets for touchscreens */ + button { + min-height: 36px; + min-width: 36px; + } + + /* Prevent text selection on tap */ + .panel-header { + user-select: none; + -webkit-user-select: none; + } + + /* Scrollbar styling for touch */ + ::-webkit-scrollbar { + width: 4px; + } + + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + border-radius: 2px; + } +} From 01aa159a019a78ca24916fcd822d9ca7330cb4a1 Mon Sep 17 00:00:00 2001 From: accius Date: Wed, 4 Feb 2026 00:51:44 -0500 Subject: [PATCH 3/7] revert --- src/App.jsx | 433 ------------------------------- src/components/SettingsPanel.jsx | 8 +- src/lang/de.json | 6 +- src/lang/en.json | 4 - src/lang/es.json | 6 +- src/lang/fr.json | 6 +- src/lang/it.json | 6 +- src/lang/ja.json | 6 +- src/lang/ko.json | 6 +- src/lang/nl.json | 6 +- src/lang/pt.json | 6 +- src/styles/main.css | 102 -------- 12 files changed, 11 insertions(+), 584 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 8a5f05e..b10ab3b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -572,439 +572,6 @@ const App = () => { 35
- ) : config.layout === 'tablet' ? ( - /* TABLET LAYOUT - Optimized for 7-10" widescreen displays (16:9) */ -
- {/* COMPACT TOP BAR */} -
- {/* Callsign */} - setShowSettings(true)} - title="Settings" - > - {config.callsign} - - - {/* UTC */} -
- UTC - {utcTime} -
- - {/* Local */} -
- LOC - {localTime} -
- - {/* Solar Quick Stats */} -
- - SFI - {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} - - - K - = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> - {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} - - - - SSN - {solarIndices?.data?.ssn?.current || '--'} - -
- - {/* Controls */} -
- - -
-
- - {/* MAIN AREA: Map + Data Sidebar */} -
- {/* MAP */} -
- -
- - {/* DATA SIDEBAR */} -
- {/* Band Conditions Grid */} -
-
Band Conditions
-
- {(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 ( -
-
{band.band}
-
{band.condition}
-
- ); - })} -
- {/* MUF/LUF */} - {propagation.data && ( -
- MUF {propagation.data.muf || '?'} - LUF {propagation.data.luf || '?'} -
- )} -
- - {/* Compact DX Cluster */} -
-
- DX Cluster - {dxCluster.data?.length || 0} spots -
-
- {dxCluster.data?.slice(0, 30).map((spot, i) => ( -
setHoveredSpot(spot)} - onMouseLeave={() => setHoveredSpot(null)} - > - {parseFloat(spot.freq).toFixed(1)} - {spot.call} - {spot.time || '--'} -
- ))} -
-
-
-
-
- - ) : config.layout === 'compact' ? ( - /* COMPACT LAYOUT - Optimized for 4:3 screens and data-first display */ -
- {/* TOP: Callsign + Times + Solar */} -
- {/* Row 1: Callsign + Times */} -
- setShowSettings(true)} - title="Settings" - > - {config.callsign} - -
-
-
UTC
-
{utcTime}
-
{utcDate}
-
-
-
Local
-
{localTime}
-
{localDate}
-
-
-
- - -
-
- {/* Row 2: Solar indices inline */} -
- - SFI - {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} - - - K - = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> - {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} - - - - SSN - {solarIndices?.data?.ssn?.current || '--'} - - {propagation.data && ( - <> - - MUF - {propagation.data.muf || '?'} MHz - - - LUF - {propagation.data.luf || '?'} MHz - - - )} - {localWeather?.data && ( - - {localWeather.data.icon} - {localWeather.data.temp}°{localWeather.data.tempUnit || tempUnit} - - )} -
-
- - {/* BAND CONDITIONS - Full Width */} -
-
- {(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 ( -
-
{band.band}
-
{band.condition}
-
- ); - })} -
-
- - {/* MAIN: Map + DX Cluster side by side */} -
- {/* Map */} -
- -
- {deGrid} → {dxGrid} • Click map to set DX -
-
- - {/* Compact DX Cluster */} -
-
- DX Cluster - {dxCluster.data?.length || 0} -
-
- {dxCluster.data?.slice(0, 40).map((spot, i) => ( -
setHoveredSpot(spot)} - onMouseLeave={() => setHoveredSpot(null)} - > - {parseFloat(spot.freq).toFixed(1)} - {spot.call} - {spot.time || '--'} -
- ))} -
-
-
-
- ) : ( /* MODERN LAYOUT */
{ const layoutDescriptions = { modern: t('station.settings.layout.modern.describe'), - classic: t('station.settings.layout.classic.describe'), - tablet: t('station.settings.layout.tablet.describe'), - compact: t('station.settings.layout.compact.describe') + classic: t('station.settings.layout.classic.describe') }; return ( @@ -463,7 +461,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { {t('station.settings.layout')}
- {['modern', 'classic', 'tablet', 'compact'].map((l) => ( + {['modern', 'classic'].map((l) => ( ))}
diff --git a/src/lang/de.json b/src/lang/de.json index bd17759..7f23a69 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -53,9 +53,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "Wetterdaten © Iowa State University Mesonet" } diff --git a/src/lang/en.json b/src/lang/en.json index 08a0d2a..49a03bd 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -28,10 +28,6 @@ "station.settings.layout.classic.describe": "→ Original HamClock-style layout", "station.settings.layout.modern": "Modern", "station.settings.layout.modern.describe": "→ Modern responsive grid layout", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens", "station.settings.latitude": "Latitude", "station.settings.locator": "Grid Square (or enter Lat/Lon below)", "station.settings.longitude": "Longitude", diff --git a/src/lang/es.json b/src/lang/es.json index dcd99e0..4bda44d 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -54,9 +54,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "Datos meteorológicos © Iowa State University Mesonet" } diff --git a/src/lang/fr.json b/src/lang/fr.json index 5d33094..cae97fc 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -53,9 +53,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "Données météo © Iowa State University Mesonet" } diff --git a/src/lang/it.json b/src/lang/it.json index d2d24fa..b18d457 100644 --- a/src/lang/it.json +++ b/src/lang/it.json @@ -53,9 +53,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "Dati meteo © Iowa State University Mesonet" } diff --git a/src/lang/ja.json b/src/lang/ja.json index 1407a08..59d4447 100644 --- a/src/lang/ja.json +++ b/src/lang/ja.json @@ -53,9 +53,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "気象データ © Iowa State University Mesonet" } diff --git a/src/lang/ko.json b/src/lang/ko.json index 21db534..8d703c0 100644 --- a/src/lang/ko.json +++ b/src/lang/ko.json @@ -53,9 +53,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "기상 데이터 © Iowa State University Mesonet" } diff --git a/src/lang/nl.json b/src/lang/nl.json index b46b5d9..b45fcb5 100644 --- a/src/lang/nl.json +++ b/src/lang/nl.json @@ -53,9 +53,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "Weergegevens © Iowa State University Mesonet" } diff --git a/src/lang/pt.json b/src/lang/pt.json index 4cd8cb2..8091927 100644 --- a/src/lang/pt.json +++ b/src/lang/pt.json @@ -53,9 +53,5 @@ "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", - "station.settings.layout.tablet": "Tablet", - "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", - "station.settings.layout.compact": "Compact", - "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" + "plugins.layers.wxradar.attribution": "Dados meteorológicos © Iowa State University Mesonet" } diff --git a/src/styles/main.css b/src/styles/main.css index eda3aa7..ad8b710 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -905,105 +905,3 @@ body::before { position: relative; z-index: 10000 !important; } - -/* ============================================ - RESPONSIVE: Phone & Small Screen Overrides - ============================================ */ - -/* Tablets and small screens (under 1024px) */ -@media (max-width: 1024px) { - .panel { - padding: 6px !important; - border-radius: 4px !important; - } - - .panel-header { - font-size: 11px !important; - padding: 4px 6px !important; - } -} - -/* Phone landscape and small tablets (under 768px) */ -@media (max-width: 768px) { - /* Force single-column on modern layout */ - .ohc-mobile-stack { - display: flex !important; - flex-direction: column !important; - grid-template-columns: unset !important; - } - - .panel { - padding: 4px !important; - margin-bottom: 2px !important; - border-radius: 3px !important; - } - - .panel-header { - font-size: 10px !important; - padding: 3px 6px !important; - } - - /* Prevent horizontal overflow */ - body { - overflow-x: hidden; - } -} - -/* Phone portrait (under 480px) */ -@media (max-width: 480px) { - /* Extra compact for phones */ - .panel { - padding: 3px !important; - font-size: 11px !important; - } - - .panel-header { - font-size: 9px !important; - } - - /* Reduce font sizes globally for very small screens */ - body { - font-size: 12px; - } -} - -/* Short screens (Pi 7" touchscreen at 480px height) */ -@media (max-height: 520px) { - .panel { - padding: 3px !important; - } - - .panel-header { - padding: 2px 4px !important; - font-size: 10px !important; - } -} - -/* Touch device improvements */ -@media (pointer: coarse) { - /* Larger tap targets for touchscreens */ - button { - min-height: 36px; - min-width: 36px; - } - - /* Prevent text selection on tap */ - .panel-header { - user-select: none; - -webkit-user-select: none; - } - - /* Scrollbar styling for touch */ - ::-webkit-scrollbar { - width: 4px; - } - - ::-webkit-scrollbar-track { - background: transparent; - } - - ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.15); - border-radius: 2px; - } -} From 4e501e8551a8c82238b42d0c52ab317f0ceb2b64 Mon Sep 17 00:00:00 2001 From: accius Date: Wed, 4 Feb 2026 00:55:01 -0500 Subject: [PATCH 4/7] redeploy --- src/App.jsx | 433 +++++++++++++++++++++++++++++++ src/components/SettingsPanel.jsx | 8 +- src/lang/de.json | 6 +- src/lang/en.json | 4 + src/lang/es.json | 6 +- src/lang/fr.json | 6 +- src/lang/it.json | 6 +- src/lang/ja.json | 6 +- src/lang/ko.json | 6 +- src/lang/nl.json | 6 +- src/lang/pt.json | 6 +- src/styles/main.css | 102 ++++++++ 12 files changed, 584 insertions(+), 11 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index b10ab3b..8a5f05e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -572,6 +572,439 @@ const App = () => { 35
+ ) : config.layout === 'tablet' ? ( + /* TABLET LAYOUT - Optimized for 7-10" widescreen displays (16:9) */ +
+ {/* COMPACT TOP BAR */} +
+ {/* Callsign */} + setShowSettings(true)} + title="Settings" + > + {config.callsign} + + + {/* UTC */} +
+ UTC + {utcTime} +
+ + {/* Local */} +
+ LOC + {localTime} +
+ + {/* Solar Quick Stats */} +
+ + SFI + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + + + K + = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} + + + + SSN + {solarIndices?.data?.ssn?.current || '--'} + +
+ + {/* Controls */} +
+ + +
+
+ + {/* MAIN AREA: Map + Data Sidebar */} +
+ {/* MAP */} +
+ +
+ + {/* DATA SIDEBAR */} +
+ {/* Band Conditions Grid */} +
+
Band Conditions
+
+ {(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 ( +
+
{band.band}
+
{band.condition}
+
+ ); + })} +
+ {/* MUF/LUF */} + {propagation.data && ( +
+ MUF {propagation.data.muf || '?'} + LUF {propagation.data.luf || '?'} +
+ )} +
+ + {/* Compact DX Cluster */} +
+
+ DX Cluster + {dxCluster.data?.length || 0} spots +
+
+ {dxCluster.data?.slice(0, 30).map((spot, i) => ( +
setHoveredSpot(spot)} + onMouseLeave={() => setHoveredSpot(null)} + > + {parseFloat(spot.freq).toFixed(1)} + {spot.call} + {spot.time || '--'} +
+ ))} +
+
+
+
+
+ + ) : config.layout === 'compact' ? ( + /* COMPACT LAYOUT - Optimized for 4:3 screens and data-first display */ +
+ {/* TOP: Callsign + Times + Solar */} +
+ {/* Row 1: Callsign + Times */} +
+ setShowSettings(true)} + title="Settings" + > + {config.callsign} + +
+
+
UTC
+
{utcTime}
+
{utcDate}
+
+
+
Local
+
{localTime}
+
{localDate}
+
+
+
+ + +
+
+ {/* Row 2: Solar indices inline */} +
+ + SFI + {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} + + + K + = 4 ? 'var(--accent-red)' : 'var(--accent-green)', fontWeight: '700' }}> + {solarIndices?.data?.kp?.current ?? spaceWeather?.data?.kIndex ?? '--'} + + + + SSN + {solarIndices?.data?.ssn?.current || '--'} + + {propagation.data && ( + <> + + MUF + {propagation.data.muf || '?'} MHz + + + LUF + {propagation.data.luf || '?'} MHz + + + )} + {localWeather?.data && ( + + {localWeather.data.icon} + {localWeather.data.temp}°{localWeather.data.tempUnit || tempUnit} + + )} +
+
+ + {/* BAND CONDITIONS - Full Width */} +
+
+ {(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 ( +
+
{band.band}
+
{band.condition}
+
+ ); + })} +
+
+ + {/* MAIN: Map + DX Cluster side by side */} +
+ {/* Map */} +
+ +
+ {deGrid} → {dxGrid} • Click map to set DX +
+
+ + {/* Compact DX Cluster */} +
+
+ DX Cluster + {dxCluster.data?.length || 0} +
+
+ {dxCluster.data?.slice(0, 40).map((spot, i) => ( +
setHoveredSpot(spot)} + onMouseLeave={() => setHoveredSpot(null)} + > + {parseFloat(spot.freq).toFixed(1)} + {spot.call} + {spot.time || '--'} +
+ ))} +
+
+
+
+ ) : ( /* MODERN LAYOUT */
{ const layoutDescriptions = { modern: t('station.settings.layout.modern.describe'), - classic: t('station.settings.layout.classic.describe') + classic: t('station.settings.layout.classic.describe'), + tablet: t('station.settings.layout.tablet.describe'), + compact: t('station.settings.layout.compact.describe') }; return ( @@ -461,7 +463,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { {t('station.settings.layout')}
- {['modern', 'classic'].map((l) => ( + {['modern', 'classic', 'tablet', 'compact'].map((l) => ( ))}
diff --git a/src/lang/de.json b/src/lang/de.json index 7f23a69..bd17759 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Wetterdaten © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/en.json b/src/lang/en.json index 49a03bd..08a0d2a 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -28,6 +28,10 @@ "station.settings.layout.classic.describe": "→ Original HamClock-style layout", "station.settings.layout.modern": "Modern", "station.settings.layout.modern.describe": "→ Modern responsive grid layout", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens", "station.settings.latitude": "Latitude", "station.settings.locator": "Grid Square (or enter Lat/Lon below)", "station.settings.longitude": "Longitude", diff --git a/src/lang/es.json b/src/lang/es.json index 4bda44d..dcd99e0 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -54,5 +54,9 @@ "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" + "plugins.layers.wxradar.attribution": "Datos meteorológicos © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/fr.json b/src/lang/fr.json index cae97fc..5d33094 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Données météo © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/it.json b/src/lang/it.json index b18d457..d2d24fa 100644 --- a/src/lang/it.json +++ b/src/lang/it.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Dati meteo © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/ja.json b/src/lang/ja.json index 59d4447..1407a08 100644 --- a/src/lang/ja.json +++ b/src/lang/ja.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "気象データ © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/ko.json b/src/lang/ko.json index 8d703c0..21db534 100644 --- a/src/lang/ko.json +++ b/src/lang/ko.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "기상 데이터 © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/nl.json b/src/lang/nl.json index b45fcb5..b46b5d9 100644 --- a/src/lang/nl.json +++ b/src/lang/nl.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Weergegevens © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/lang/pt.json b/src/lang/pt.json index 8091927..4cd8cb2 100644 --- a/src/lang/pt.json +++ b/src/lang/pt.json @@ -53,5 +53,9 @@ "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" + "plugins.layers.wxradar.attribution": "Dados meteorológicos © Iowa State University Mesonet", + "station.settings.layout.tablet": "Tablet", + "station.settings.layout.tablet.describe": "→ Optimized for 7-10\" widescreen displays (16:9)", + "station.settings.layout.compact": "Compact", + "station.settings.layout.compact.describe": "→ Data-first layout for 4:3 and smaller screens" } diff --git a/src/styles/main.css b/src/styles/main.css index ad8b710..eda3aa7 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -905,3 +905,105 @@ body::before { position: relative; z-index: 10000 !important; } + +/* ============================================ + RESPONSIVE: Phone & Small Screen Overrides + ============================================ */ + +/* Tablets and small screens (under 1024px) */ +@media (max-width: 1024px) { + .panel { + padding: 6px !important; + border-radius: 4px !important; + } + + .panel-header { + font-size: 11px !important; + padding: 4px 6px !important; + } +} + +/* Phone landscape and small tablets (under 768px) */ +@media (max-width: 768px) { + /* Force single-column on modern layout */ + .ohc-mobile-stack { + display: flex !important; + flex-direction: column !important; + grid-template-columns: unset !important; + } + + .panel { + padding: 4px !important; + margin-bottom: 2px !important; + border-radius: 3px !important; + } + + .panel-header { + font-size: 10px !important; + padding: 3px 6px !important; + } + + /* Prevent horizontal overflow */ + body { + overflow-x: hidden; + } +} + +/* Phone portrait (under 480px) */ +@media (max-width: 480px) { + /* Extra compact for phones */ + .panel { + padding: 3px !important; + font-size: 11px !important; + } + + .panel-header { + font-size: 9px !important; + } + + /* Reduce font sizes globally for very small screens */ + body { + font-size: 12px; + } +} + +/* Short screens (Pi 7" touchscreen at 480px height) */ +@media (max-height: 520px) { + .panel { + padding: 3px !important; + } + + .panel-header { + padding: 2px 4px !important; + font-size: 10px !important; + } +} + +/* Touch device improvements */ +@media (pointer: coarse) { + /* Larger tap targets for touchscreens */ + button { + min-height: 36px; + min-width: 36px; + } + + /* Prevent text selection on tap */ + .panel-header { + user-select: none; + -webkit-user-select: none; + } + + /* Scrollbar styling for touch */ + ::-webkit-scrollbar { + width: 4px; + } + + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + border-radius: 2px; + } +} From 969c9518b7cefa501fe5560b9d59dd8af17e6070 Mon Sep 17 00:00:00 2001 From: accius Date: Wed, 4 Feb 2026 01:05:52 -0500 Subject: [PATCH 5/7] bigger text in small screen support --- src/App.jsx | 128 ++++++++++++++++++++++++++-------------------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 8a5f05e..371fc88 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -590,15 +590,15 @@ const App = () => { justifyContent: 'space-between', background: 'var(--bg-panel)', borderBottom: '1px solid var(--border-color)', - padding: '4px 12px', - height: '44px', + padding: '6px 12px', + height: '52px', flexShrink: 0, - gap: '8px' + gap: '10px' }}> {/* Callsign */} { {/* UTC */}
- UTC - {utcTime} + UTC + {utcTime}
{/* Local */} @@ -623,12 +623,12 @@ const App = () => { onClick={handleTimeFormatToggle} title={`Click for ${use12Hour ? '24h' : '12h'} format`} > - LOC - {localTime} + LOC + {localTime}
{/* Solar Quick Stats */} -
+
SFI {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} @@ -703,7 +703,7 @@ const App = () => { {/* DATA SIDEBAR */}
{ overflow: 'hidden' }}> {/* Band Conditions Grid */} -
-
Band Conditions
-
+
+
Band Conditions
+
{(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)' }, @@ -726,50 +726,50 @@ const App = () => {
-
{band.band}
-
{band.condition}
+
{band.band}
+
{band.condition}
); })}
{/* MUF/LUF */} {propagation.data && ( -
- MUF {propagation.data.muf || '?'} - LUF {propagation.data.luf || '?'} +
+ MUF {propagation.data.muf || '?'} + LUF {propagation.data.luf || '?'}
)}
{/* Compact DX Cluster */}
-
- DX Cluster - {dxCluster.data?.length || 0} spots +
+ DX Cluster + {dxCluster.data?.length || 0} spots
-
+
{dxCluster.data?.slice(0, 30).map((spot, i) => (
setHoveredSpot(spot)} onMouseLeave={() => setHoveredSpot(null)} > - {parseFloat(spot.freq).toFixed(1)} - {spot.call} + {parseFloat(spot.freq).toFixed(1)} + {spot.call} {spot.time || '--'}
))} @@ -798,10 +798,10 @@ const App = () => { flexShrink: 0 }}> {/* Row 1: Callsign + Times */} -
+
{ > {config.callsign} -
+
-
UTC
-
{utcTime}
-
{utcDate}
+
UTC
+
{utcTime}
+
{utcDate}
-
Local
-
{localTime}
-
{localDate}
+
Local
+
{localTime}
+
{localDate}
@@ -834,10 +834,10 @@ const App = () => { style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border-color)', - padding: '4px 8px', + padding: '6px 10px', borderRadius: '4px', color: 'var(--text-secondary)', - fontSize: '12px', + fontSize: '14px', cursor: 'pointer' }} >⚙ @@ -846,17 +846,17 @@ const App = () => { style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border-color)', - padding: '4px 8px', + padding: '6px 10px', borderRadius: '4px', color: 'var(--text-secondary)', - fontSize: '12px', + fontSize: '14px', cursor: 'pointer' }} >⛶
{/* Row 2: Solar indices inline */} -
+
SFI {solarIndices?.data?.sfi?.current || spaceWeather?.data?.solarFlux || '--'} @@ -894,12 +894,12 @@ const App = () => { {/* BAND CONDITIONS - Full Width */}
-
+
{(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)' }, @@ -912,12 +912,12 @@ const App = () => { background: s.bg, border: `1px solid ${s.border}`, borderRadius: '4px', - padding: '4px 8px', + padding: '5px 10px', textAlign: 'center', - minWidth: '52px' + minWidth: '58px' }}> -
{band.band}
-
{band.condition}
+
{band.band}
+
{band.condition}
); })} @@ -954,10 +954,10 @@ const App = () => { bottom: '6px', left: '50%', transform: 'translateX(-50%)', - fontSize: '11px', + fontSize: '14px', color: 'var(--text-muted)', background: 'rgba(0,0,0,0.7)', - padding: '2px 8px', + padding: '3px 10px', borderRadius: '4px' }}> {deGrid} → {dxGrid} • Click map to set DX @@ -966,7 +966,7 @@ const App = () => { {/* Compact DX Cluster */}
{ flexDirection: 'column', overflow: 'hidden' }}> -
- DX Cluster - {dxCluster.data?.length || 0} +
+ DX Cluster + {dxCluster.data?.length || 0}
{dxCluster.data?.slice(0, 40).map((spot, i) => (
setHoveredSpot(spot)} onMouseLeave={() => setHoveredSpot(null)} > - {parseFloat(spot.freq).toFixed(1)} - {spot.call} + {parseFloat(spot.freq).toFixed(1)} + {spot.call} {spot.time || '--'}
))} From ba39f399b4c73fce8d7c745151eee541fa97d741 Mon Sep 17 00:00:00 2001 From: accius Date: Wed, 4 Feb 2026 01:15:11 -0500 Subject: [PATCH 6/7] move ticker --- src/App.jsx | 29 ++++++++++++++++++++++++++++- src/components/DXNewsTicker.jsx | 11 +++++++++-- src/components/WorldMap.jsx | 7 +++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 371fc88..013f5ff 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -17,7 +17,8 @@ import { SolarPanel, PropagationPanel, DXpeditionPanel, - PSKReporterPanel + PSKReporterPanel, + DXNewsTicker } from './components'; // Hooks @@ -698,6 +699,7 @@ const App = () => { showWSJTX={mapLayers.showWSJTX} onToggleSatellites={toggleSatellites} hoveredSpot={hoveredSpot} + hideOverlays={true} />
@@ -775,6 +777,18 @@ const App = () => { ))}
+ + {/* DX News - sidebar footer */} +
+ +
@@ -948,6 +962,7 @@ const App = () => { showWSJTX={mapLayers.showWSJTX} onToggleSatellites={toggleSatellites} hoveredSpot={hoveredSpot} + hideOverlays={true} />
{
))}
+ + {/* DX News - sidebar footer */} +
+ +
diff --git a/src/components/DXNewsTicker.jsx b/src/components/DXNewsTicker.jsx index edf72db..5129634 100644 --- a/src/components/DXNewsTicker.jsx +++ b/src/components/DXNewsTicker.jsx @@ -4,7 +4,7 @@ */ import React, { useState, useEffect, useRef } from 'react'; -export const DXNewsTicker = () => { +export const DXNewsTicker = ({ sidebar = false }) => { const [news, setNews] = useState([]); const [loading, setLoading] = useState(true); const tickerRef = useRef(null); @@ -57,7 +57,14 @@ export const DXNewsTicker = () => { return (
{ const mapRef = useRef(null); const mapInstanceRef = useRef(null); @@ -836,9 +837,10 @@ export const WorldMap = ({ )} {/* DX News Ticker - left side of bottom bar */} - + {!hideOverlays && } {/* Legend - right side */} + {!hideOverlays && (
☽ Moon
+ )}
); }; From cfc3f5cf4f85093f16d59268fa52d76ad5acb891 Mon Sep 17 00:00:00 2001 From: accius Date: Wed, 4 Feb 2026 01:21:41 -0500 Subject: [PATCH 7/7] legend in small screen --- src/App.jsx | 85 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 013f5ff..a24cf7f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -46,7 +46,8 @@ import { applyTheme, fetchServerConfig, calculateGridSquare, - calculateSunTimes + calculateSunTimes, + getBandColor } from './utils'; const App = () => { @@ -701,6 +702,40 @@ const App = () => { hoveredSpot={hoveredSpot} hideOverlays={true} /> + {/* Compact Band Legend */} +
+ {[ + { band: '160', color: '#ff6666' }, { band: '80', color: '#ff9966' }, + { band: '40', color: '#ffcc66' }, { band: '30', color: '#99ff66' }, + { band: '20', color: '#66ff99' }, { band: '17', color: '#66ffcc' }, + { band: '15', color: '#66ccff' }, { band: '12', color: '#6699ff' }, + { band: '10', color: '#9966ff' }, { band: '6', color: '#ff66ff' } + ].map(b => ( + {b.band} + ))} +
{/* DATA SIDEBAR */} @@ -760,7 +795,7 @@ const App = () => { style={{ padding: '3px 8px', display: 'grid', - gridTemplateColumns: '70px 1fr 42px', + gridTemplateColumns: '80px 1fr 52px', gap: '4px', borderBottom: '1px solid rgba(255,255,255,0.05)', cursor: 'pointer', @@ -770,9 +805,9 @@ const App = () => { onMouseEnter={() => setHoveredSpot(spot)} onMouseLeave={() => setHoveredSpot(null)} > - {parseFloat(spot.freq).toFixed(1)} + {parseFloat(spot.freq).toFixed(1)} {spot.call} - {spot.time || '--'} + {spot.time || '--'}
))}
@@ -966,7 +1001,7 @@ const App = () => { />
{ }}> {deGrid} → {dxGrid} • Click map to set DX
+ {/* Compact Band Legend */} +
+ {[ + { band: '160', color: '#ff6666' }, { band: '80', color: '#ff9966' }, + { band: '40', color: '#ffcc66' }, { band: '30', color: '#99ff66' }, + { band: '20', color: '#66ff99' }, { band: '17', color: '#66ffcc' }, + { band: '15', color: '#66ccff' }, { band: '12', color: '#6699ff' }, + { band: '10', color: '#9966ff' }, { band: '6', color: '#ff66ff' } + ].map(b => ( + {b.band} + ))} +
{/* Compact DX Cluster */} @@ -1000,7 +1069,7 @@ const App = () => { style={{ padding: '3px 8px', display: 'grid', - gridTemplateColumns: '65px 1fr 38px', + gridTemplateColumns: '75px 1fr 50px', gap: '4px', borderBottom: '1px solid rgba(255,255,255,0.05)', cursor: 'pointer', @@ -1010,9 +1079,9 @@ const App = () => { onMouseEnter={() => setHoveredSpot(spot)} onMouseLeave={() => setHoveredSpot(null)} > - {parseFloat(spot.freq).toFixed(1)} + {parseFloat(spot.freq).toFixed(1)} {spot.call} - {spot.time || '--'} + {spot.time || '--'}
))}