From e4a4e6f0acb5259e4b763d993218a20eee638039 Mon Sep 17 00:00:00 2001 From: accius Date: Tue, 3 Feb 2026 00:19:47 -0500 Subject: [PATCH 1/3] new icons and tips --- src/components/BandConditionsPanel.jsx | 2 +- src/components/ContestPanel.jsx | 2 +- src/components/DXClusterPanel.jsx | 9 +- src/components/DXFilterManager.jsx | 4 +- src/components/DXpeditionPanel.jsx | 2 +- src/components/Header.jsx | 9 +- src/components/Icons.jsx | 170 +++++++++++++++++++++++++ src/components/LocationPanel.jsx | 2 +- src/components/POTAPanel.jsx | 5 +- src/components/PSKFilterManager.jsx | 2 +- src/components/PSKReporterPanel.jsx | 25 ++-- src/components/PropagationPanel.jsx | 6 +- src/components/SettingsPanel.jsx | 6 +- src/components/SolarPanel.jsx | 6 +- src/components/SpaceWeatherPanel.jsx | 2 +- src/components/WorldMap.jsx | 23 ++-- 16 files changed, 228 insertions(+), 47 deletions(-) create mode 100644 src/components/Icons.jsx diff --git a/src/components/BandConditionsPanel.jsx b/src/components/BandConditionsPanel.jsx index 732964d..92613bf 100644 --- a/src/components/BandConditionsPanel.jsx +++ b/src/components/BandConditionsPanel.jsx @@ -20,7 +20,7 @@ export const BandConditionsPanel = ({ data, loading }) => { return (
-
πŸ“‘ BAND CONDITIONS
+
βŒ‡ BAND CONDITIONS
{loading ? (
diff --git a/src/components/ContestPanel.jsx b/src/components/ContestPanel.jsx index 100e207..7b9a399 100644 --- a/src/components/ContestPanel.jsx +++ b/src/components/ContestPanel.jsx @@ -102,7 +102,7 @@ export const ContestPanel = ({ data, loading }) => { color: 'var(--accent-primary)', fontWeight: '700' }}> - πŸ† CONTESTS + βŠ› CONTESTS {liveCount > 0 && ( - 🌐 DX CLUSTER ● LIVE + DX CLUSTER ● LIVE
{spots.length}/{totalSpots || spots.length}
diff --git a/src/components/DXFilterManager.jsx b/src/components/DXFilterManager.jsx index bd3f218..377da25 100644 --- a/src/components/DXFilterManager.jsx +++ b/src/components/DXFilterManager.jsx @@ -416,7 +416,7 @@ export const DXFilterManager = ({ filters, onFilterChange, isOpen, onClose }) => }}>
- πŸ” DX Cluster Filters + ⊘ DX Cluster Filters
{getActiveFilterCount()} filters active @@ -462,7 +462,7 @@ export const DXFilterManager = ({ filters, onFilterChange, isOpen, onClose }) => - +
{/* Tab Content */} diff --git a/src/components/DXpeditionPanel.jsx b/src/components/DXpeditionPanel.jsx index a8806a9..f7060e6 100644 --- a/src/components/DXpeditionPanel.jsx +++ b/src/components/DXpeditionPanel.jsx @@ -24,7 +24,7 @@ export const DXpeditionPanel = ({ data, loading }) => { marginBottom: '6px', fontSize: '11px' }}> - 🌍 DXPEDITIONS + βŠ• DXPEDITIONS {data && ( {data.active > 0 && {data.active} active} diff --git a/src/components/Header.jsx b/src/components/Header.jsx index db8df60..72db51e 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -3,7 +3,7 @@ * Top bar with callsign, clocks, weather, and controls */ import React from 'react'; - +import { IconGear, IconExpand, IconShrink } from './Icons.jsx'; export const Header = ({ config, utcTime, @@ -146,7 +146,7 @@ export const Header = ({ whiteSpace: 'nowrap' }} > - βš™ Settings + Settings
diff --git a/src/components/Icons.jsx b/src/components/Icons.jsx new file mode 100644 index 0000000..00f3fb7 --- /dev/null +++ b/src/components/Icons.jsx @@ -0,0 +1,170 @@ +/** + * SVG Icons for OpenHamClock + * + * Cross-platform icons that render identically on all browsers and operating systems. + * Replaces emoji which render as tofu/boxes on Linux Chromium without emoji fonts. + * + * All icons accept: size (default 14), color (default 'currentColor'), style, className + */ +import React from 'react'; + +const defaults = { size: 14, color: 'currentColor' }; + +// Magnifying glass / Search / Filter +export const IconSearch = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + +); + +// Refresh / Reload +export const IconRefresh = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + + +); + +// Map +export const IconMap = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + +); + +// Gear / Settings +export const IconGear = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + +); + +// Globe / World +export const IconGlobe = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + +); + +// Satellite +export const IconSatellite = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + + +); + +// Antenna / Radio +export const IconAntenna = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + + + +); + +// Sun +export const IconSun = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + + + + + + + +); + +// Moon +export const IconMoon = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + +); + +// Trophy / Contest +export const IconTrophy = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + + + + +); + +// Tent / POTA / Camping +export const IconTent = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + +); + +// Earth / DXpedition +export const IconEarth = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + +); + +// Pin / Location +export const IconPin = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + +); + +// Tag / Label +export const IconTag = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + +); + +// Fullscreen expand +export const IconExpand = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + + +); + +// Fullscreen shrink +export const IconShrink = ({ size = defaults.size, color = defaults.color, ...props }) => ( + + + + + + +); + +export default { + IconSearch, IconRefresh, IconMap, IconGear, IconGlobe, IconSatellite, + IconAntenna, IconSun, IconMoon, IconTrophy, IconTent, IconEarth, + IconPin, IconTag, IconExpand, IconShrink, +}; diff --git a/src/components/LocationPanel.jsx b/src/components/LocationPanel.jsx index 26c2a5f..3dba44d 100644 --- a/src/components/LocationPanel.jsx +++ b/src/components/LocationPanel.jsx @@ -21,7 +21,7 @@ export const LocationPanel = ({ return (
-
πŸ“ LOCATIONS
+
β—Ž LOCATIONS
{/* DE Location */}
diff --git a/src/components/POTAPanel.jsx b/src/components/POTAPanel.jsx index 8e466bc..56ce3e6 100644 --- a/src/components/POTAPanel.jsx +++ b/src/components/POTAPanel.jsx @@ -14,9 +14,10 @@ export const POTAPanel = ({ data, loading, showOnMap, onToggleMap }) => { marginBottom: '6px', fontSize: '11px' }}> - πŸ•οΈ POTA ACTIVATORS + β–³ POTA ACTIVATORS
diff --git a/src/components/PSKFilterManager.jsx b/src/components/PSKFilterManager.jsx index ab5172a..e31a0b0 100644 --- a/src/components/PSKFilterManager.jsx +++ b/src/components/PSKFilterManager.jsx @@ -310,7 +310,7 @@ export const PSKFilterManager = ({ filters, onFilterChange, isOpen, onClose }) = }}>

- πŸ“‘ PSKReporter Filters + βŒ‡ PSKReporter Filters

{getActiveFilterCount()} filter{getActiveFilterCount() !== 1 ? 's' : ''} active diff --git a/src/components/PSKReporterPanel.jsx b/src/components/PSKReporterPanel.jsx index b73d7e4..bf18575 100644 --- a/src/components/PSKReporterPanel.jsx +++ b/src/components/PSKReporterPanel.jsx @@ -10,6 +10,7 @@ import React, { useState, useMemo } from 'react'; import { usePSKReporter } from '../hooks/usePSKReporter.js'; import { getBandColor } from '../utils/callsign.js'; +import { IconSearch, IconRefresh, IconMap } from './Icons.jsx'; const PSKReporterPanel = ({ callsign, @@ -182,10 +183,10 @@ const PSKReporterPanel = ({ }}> {/* Mode toggle */}
- -
@@ -198,14 +199,14 @@ const PSKReporterPanel = ({ {statusDot && ( {statusDot.char} )} - + }} title="Reconnect to PSKReporter"> )} @@ -241,8 +242,8 @@ const PSKReporterPanel = ({ {/* Map toggle (always visible) */} {handleMapToggle && ( - )}
@@ -252,19 +253,19 @@ const PSKReporterPanel = ({
{panelMode === 'psk' ? ( <> - - ) : ( <> - - @@ -283,7 +284,7 @@ const PSKReporterPanel = ({
) : error && !connected ? (
- Connection failed β€” tap πŸ”„ + Connection failed β€” tap refresh ↻
) : loading && filteredReports.length === 0 && pskFilterCount === 0 ? (
diff --git a/src/components/PropagationPanel.jsx b/src/components/PropagationPanel.jsx index 71e6396..c6bb226 100644 --- a/src/components/PropagationPanel.jsx +++ b/src/components/PropagationPanel.jsx @@ -34,7 +34,7 @@ export const PropagationPanel = ({ propagation, loading, bandConditions }) => { if (loading || !propagation) { return (
-
πŸ“‘ VOACAP
+
βŒ‡ VOACAP
Loading predictions...
@@ -81,7 +81,7 @@ export const PropagationPanel = ({ propagation, loading, bandConditions }) => {
- {viewMode === 'bands' ? 'πŸ“Š BAND CONDITIONS' : 'πŸ“‘ VOACAP'} + {viewMode === 'bands' ? 'β—« BAND CONDITIONS' : 'βŒ‡ VOACAP'} {hasRealData && viewMode !== 'bands' && ●} @@ -143,7 +143,7 @@ export const PropagationPanel = ({ propagation, loading, bandConditions }) => {
{hasRealData - ? `πŸ“‘ ${ionospheric?.source || 'ionosonde'}${ionospheric?.distance ? ` (${ionospheric.distance}km)` : ''}` + ? `βŒ‡ ${ionospheric?.source || 'ionosonde'}${ionospheric?.distance ? ` (${ionospheric.distance}km)` : ''}` : '⚑ estimated' } diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index b0795bf..58f7109 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -232,7 +232,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { fontFamily: 'JetBrains Mono, monospace' }} > - πŸ“‘ Station + βŒ‡ Station
@@ -592,7 +592,7 @@ export const SettingsPanel = ({ isOpen, onClose, config, onSave }) => { {/* Language */}
{LANGUAGES.map((lang) => ( diff --git a/src/components/SolarPanel.jsx b/src/components/SolarPanel.jsx index 6930d06..43c938e 100644 --- a/src/components/SolarPanel.jsx +++ b/src/components/SolarPanel.jsx @@ -7,7 +7,7 @@ import { getMoonPhase } from '../utils/geo.js'; const MODES = ['image', 'indices', 'xray', 'lunar']; const MODE_LABELS = { image: 'SOLAR', indices: 'SOLAR INDICES', xray: 'X-RAY FLUX', lunar: 'LUNAR' }; -const MODE_ICONS = { image: 'πŸ“Š', indices: 'πŸ“ˆ', xray: 'πŸŒ™', lunar: 'β˜€οΈ' }; +const MODE_ICONS = { image: 'β—«', indices: '⊞', xray: '☽', lunar: '☼' }; const MODE_TITLES = { image: 'Show solar indices', indices: 'Show X-ray flux', xray: 'Show lunar phase', lunar: 'Show solar image' }; // Flare class from flux value (W/mΒ²) @@ -389,13 +389,13 @@ export const SolarPanel = ({ solarIndices }) => {
-
πŸŒ‘ New
+
● New
{nextNew}
-
πŸŒ• Full
+
β—‹ Full
{nextFull}
diff --git a/src/components/SpaceWeatherPanel.jsx b/src/components/SpaceWeatherPanel.jsx index 7fa71fd..d0aaca1 100644 --- a/src/components/SpaceWeatherPanel.jsx +++ b/src/components/SpaceWeatherPanel.jsx @@ -15,7 +15,7 @@ export const SpaceWeatherPanel = ({ data, loading }) => { return (
-
β˜€οΈ SPACE WEATHER
+
☼ SPACE WEATHER
{loading ? (
diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index 4347b98..848e9d7 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -13,6 +13,7 @@ import { import { filterDXPaths, getBandColor } from '../utils/callsign.js'; import { getAllLayers } from '../plugins/layerRegistry.js'; +import { IconSatellite, IconTag, IconSun, IconMoon } from './Icons.jsx'; import PluginLayer from './PluginLayer.jsx'; import { DXNewsTicker } from './DXNewsTicker.jsx'; @@ -299,24 +300,24 @@ export const WorldMap = ({ const sunPos = getSunPosition(new Date()); const sunIcon = L.divIcon({ className: 'custom-marker sun-marker', - html: 'β˜€', + html: '☼', iconSize: [24, 24], iconAnchor: [12, 12] }); sunMarkerRef.current = L.marker([sunPos.lat, sunPos.lon], { icon: sunIcon }) - .bindPopup(`β˜€ Subsolar Point
${sunPos.lat.toFixed(2)}°, ${sunPos.lon.toFixed(2)}°`) + .bindPopup(`☼ Subsolar Point
${sunPos.lat.toFixed(2)}Β°, ${sunPos.lon.toFixed(2)}Β°`) .addTo(map); // Moon marker const moonPos = getMoonPosition(new Date()); const moonIcon = L.divIcon({ className: 'custom-marker moon-marker', - html: 'πŸŒ™', + html: '☽', iconSize: [24, 24], iconAnchor: [12, 12] }); moonMarkerRef.current = L.marker([moonPos.lat, moonPos.lon], { icon: moonIcon }) - .bindPopup(`πŸŒ™ Sublunar Point
${moonPos.lat.toFixed(2)}°, ${moonPos.lon.toFixed(2)}°`) + .bindPopup(`☽ Sublunar Point
${moonPos.lat.toFixed(2)}Β°, ${moonPos.lon.toFixed(2)}Β°`) .addTo(map); }, [deLocation, dxLocation]); @@ -480,14 +481,14 @@ export const WorldMap = ({ // Add satellite marker icon const icon = L.divIcon({ className: '', - html: `πŸ›° ${sat.name}`, + html: `β›Š ${sat.name}`, iconSize: null, iconAnchor: [0, 0] }); const marker = L.marker([sat.lat, sat.lon], { icon }) .bindPopup(` - πŸ›° ${sat.name}
+ β›Š ${sat.name}
@@ -777,6 +778,7 @@ export const WorldMap = ({ {onToggleSatellites && ( )} @@ -800,6 +802,7 @@ export const WorldMap = ({ {onToggleDXLabels && showDXPaths && ( )} @@ -865,8 +868,8 @@ export const WorldMap = ({ )}
- β˜€ Sun - πŸŒ™ Moon + ☼ Sun + ☽ Moon
From 109d733b1c3846b97c7c1605f8014ef9e63bb0e2 Mon Sep 17 00:00:00 2001 From: accius Date: Tue, 3 Feb 2026 00:27:54 -0500 Subject: [PATCH 2/3] fix fc issue and version --- src/components/Header.jsx | 11 +- src/hooks/useLocalWeather.js | 202 +++++++++++++++++++++-------------- 2 files changed, 125 insertions(+), 88 deletions(-) diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 72db51e..3545dc4 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -42,7 +42,7 @@ export const Header = ({ > {config.callsign} - v3.7.0 + {config.version && v{config.version}} {/* UTC Clock */} @@ -78,10 +78,11 @@ export const Header = ({ {/* Weather & Solar Stats */}
{localWeather?.data && (() => { - const t = localWeather.data.temp; - const unit = localWeather.data.tempUnit || 'F'; - const tempF = unit === 'C' ? Math.round(t * 9/5 + 32) : t; - const tempC = unit === 'F' ? Math.round((t - 32) * 5/9) : t; + // Always compute both F and C from the raw Celsius source + // This avoids Β±1Β° rounding drift when toggling units + const rawC = localWeather.data.rawTempC; + const tempF = Math.round(rawC * 9 / 5 + 32); + const tempC = Math.round(rawC); const windLabel = localWeather.data.windUnit || 'mph'; return (
diff --git a/src/hooks/useLocalWeather.js b/src/hooks/useLocalWeather.js index d0afd86..3746f16 100644 --- a/src/hooks/useLocalWeather.js +++ b/src/hooks/useLocalWeather.js @@ -1,6 +1,11 @@ /** * useLocalWeather Hook * Fetches detailed weather data from Open-Meteo API (free, no API key) + * + * Always fetches in metric (Celsius, km/h, mm) and converts client-side. + * This prevents rounding drift when toggling F↔C (the old approach refetched + * the API in the new unit, Math.round'd, then back-converted in the header, + * causing Β±1Β° drift each toggle). */ import { useState, useEffect } from 'react'; @@ -43,25 +48,31 @@ function windDirection(deg) { return dirs[Math.round(deg / 22.5) % 16]; } +// Conversion helpers β€” always from Celsius/metric base +const cToF = (c) => c * 9 / 5 + 32; +const kmhToMph = (k) => k * 0.621371; +const mmToInch = (mm) => mm * 0.0393701; +const kmToMi = (km) => km * 0.621371; + export const useLocalWeather = (location, tempUnit = 'F') => { - const [data, setData] = useState(null); + const [rawData, setRawData] = useState(null); const [loading, setLoading] = useState(true); + // Fetch always in metric β€” only depends on location, NOT tempUnit useEffect(() => { if (!location?.lat || !location?.lon) return; const fetchWeather = async () => { try { - const isMetric = tempUnit === 'C'; const params = [ `latitude=${location.lat}`, `longitude=${location.lon}`, 'current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,cloud_cover,pressure_msl,wind_speed_10m,wind_direction_10m,wind_gusts_10m,precipitation,uv_index,visibility,dew_point_2m,is_day', 'daily=temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max,weather_code,sunrise,sunset,uv_index_max,wind_speed_10m_max', 'hourly=temperature_2m,precipitation_probability,weather_code', - `temperature_unit=${isMetric ? 'celsius' : 'fahrenheit'}`, - `wind_speed_unit=${isMetric ? 'kmh' : 'mph'}`, - `precipitation_unit=${isMetric ? 'mm' : 'inch'}`, + 'temperature_unit=celsius', + 'wind_speed_unit=kmh', + 'precipitation_unit=mm', 'timezone=auto', 'forecast_days=3', 'forecast_hours=24', @@ -72,83 +83,8 @@ export const useLocalWeather = (location, tempUnit = 'F') => { if (!response.ok) throw new Error(`HTTP ${response.status}`); const result = await response.json(); - const current = result.current || {}; - const code = current.weather_code; - const weather = WEATHER_CODES[code] || { desc: 'Unknown', icon: '🌑️' }; - const daily = result.daily || {}; - const hourly = result.hourly || {}; - - // Build hourly forecast (next 24h in 3h intervals) - const hourlyForecast = []; - if (hourly.time && hourly.temperature_2m) { - for (let i = 0; i < Math.min(24, hourly.time.length); i += 3) { - const hCode = hourly.weather_code?.[i]; - const hWeather = WEATHER_CODES[hCode] || { desc: '', icon: '🌑️' }; - hourlyForecast.push({ - time: new Date(hourly.time[i]).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }), - temp: Math.round(hourly.temperature_2m[i]), - precipProb: hourly.precipitation_probability?.[i] || 0, - icon: hWeather.icon, - }); - } - } - - // Build daily forecast - const dailyForecast = []; - if (daily.time) { - for (let i = 0; i < Math.min(3, daily.time.length); i++) { - const dCode = daily.weather_code?.[i]; - const dWeather = WEATHER_CODES[dCode] || { desc: '', icon: '🌑️' }; - dailyForecast.push({ - date: new Date(daily.time[i] + 'T12:00:00').toLocaleDateString([], { weekday: 'short' }), - high: Math.round(daily.temperature_2m_max?.[i] || 0), - low: Math.round(daily.temperature_2m_min?.[i] || 0), - precipProb: daily.precipitation_probability_max?.[i] || 0, - precipSum: daily.precipitation_sum?.[i] || 0, - icon: dWeather.icon, - desc: dWeather.desc, - windMax: Math.round(daily.wind_speed_10m_max?.[i] || 0), - uvMax: daily.uv_index_max?.[i] || 0, - }); - } - } - - setData({ - // Current conditions - temp: Math.round(current.temperature_2m || 0), - feelsLike: Math.round(current.apparent_temperature || 0), - description: weather.desc, - icon: weather.icon, - humidity: Math.round(current.relative_humidity_2m || 0), - dewPoint: Math.round(current.dew_point_2m || 0), - pressure: current.pressure_msl ? current.pressure_msl.toFixed(1) : null, - cloudCover: current.cloud_cover || 0, - windSpeed: Math.round(current.wind_speed_10m || 0), - windDir: windDirection(current.wind_direction_10m), - windDirDeg: current.wind_direction_10m || 0, - windGusts: Math.round(current.wind_gusts_10m || 0), - precipitation: current.precipitation || 0, - uvIndex: current.uv_index || 0, - visibility: current.visibility - ? isMetric - ? (current.visibility / 1000).toFixed(1) // meters to km - : (current.visibility / 1609.34).toFixed(1) // meters to miles - : null, - isDay: current.is_day === 1, - weatherCode: code, - // Today's highs/lows - todayHigh: daily.temperature_2m_max?.[0] ? Math.round(daily.temperature_2m_max[0]) : null, - todayLow: daily.temperature_2m_min?.[0] ? Math.round(daily.temperature_2m_min[0]) : null, - // Forecasts - hourly: hourlyForecast, - daily: dailyForecast, - // Timezone - timezone: result.timezone || '', - // Units - tempUnit: isMetric ? 'C' : 'F', - windUnit: isMetric ? 'km/h' : 'mph', - visUnit: isMetric ? 'km' : 'mi', - }); + // Store raw metric values β€” conversion happens at read time + setRawData(result); } catch (err) { console.error('Weather error:', err); } finally { @@ -159,7 +95,107 @@ export const useLocalWeather = (location, tempUnit = 'F') => { fetchWeather(); const interval = setInterval(fetchWeather, 15 * 60 * 1000); // 15 minutes return () => clearInterval(interval); - }, [location?.lat, location?.lon, tempUnit]); + }, [location?.lat, location?.lon]); // Note: no tempUnit dependency + + // Convert raw API data to display data based on current tempUnit + // This runs on every render where tempUnit changes β€” instant, no API call + const data = (() => { + if (!rawData) return null; + + const isMetric = tempUnit === 'C'; + const current = rawData.current || {}; + const daily = rawData.daily || {}; + const hourly = rawData.hourly || {}; + const code = current.weather_code; + const weather = WEATHER_CODES[code] || { desc: 'Unknown', icon: '🌑️' }; + + // Temperature conversion (raw is always Celsius) + const convTemp = (c) => c == null ? null : Math.round(isMetric ? c : cToF(c)); + // Wind conversion (raw is always km/h) + const convWind = (k) => k == null ? null : Math.round(isMetric ? k : kmhToMph(k)); + + // Build hourly forecast (next 24h in 3h intervals) + const hourlyForecast = []; + if (hourly.time && hourly.temperature_2m) { + for (let i = 0; i < Math.min(24, hourly.time.length); i += 3) { + const hCode = hourly.weather_code?.[i]; + const hWeather = WEATHER_CODES[hCode] || { desc: '', icon: '🌑️' }; + hourlyForecast.push({ + time: new Date(hourly.time[i]).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }), + temp: convTemp(hourly.temperature_2m[i]), + precipProb: hourly.precipitation_probability?.[i] || 0, + icon: hWeather.icon, + }); + } + } + + // Build daily forecast + const dailyForecast = []; + if (daily.time) { + for (let i = 0; i < Math.min(3, daily.time.length); i++) { + const dCode = daily.weather_code?.[i]; + const dWeather = WEATHER_CODES[dCode] || { desc: '', icon: '🌑️' }; + dailyForecast.push({ + date: new Date(daily.time[i] + 'T12:00:00').toLocaleDateString([], { weekday: 'short' }), + high: convTemp(daily.temperature_2m_max?.[i]), + low: convTemp(daily.temperature_2m_min?.[i]), + precipProb: daily.precipitation_probability_max?.[i] || 0, + precipSum: isMetric + ? (daily.precipitation_sum?.[i] || 0) + : parseFloat(mmToInch(daily.precipitation_sum?.[i] || 0).toFixed(2)), + icon: dWeather.icon, + desc: dWeather.desc, + windMax: convWind(daily.wind_speed_10m_max?.[i]), + uvMax: daily.uv_index_max?.[i] || 0, + }); + } + } + + // Raw Celsius values for Header's dual F/C display + const rawTempC = current.temperature_2m || 0; + + return { + // Current conditions (converted to user's preferred unit) + temp: convTemp(current.temperature_2m), + feelsLike: convTemp(current.apparent_temperature), + description: weather.desc, + icon: weather.icon, + humidity: Math.round(current.relative_humidity_2m || 0), + dewPoint: convTemp(current.dew_point_2m), + pressure: current.pressure_msl ? current.pressure_msl.toFixed(1) : null, + cloudCover: current.cloud_cover || 0, + windSpeed: convWind(current.wind_speed_10m), + windDir: windDirection(current.wind_direction_10m), + windDirDeg: current.wind_direction_10m || 0, + windGusts: convWind(current.wind_gusts_10m), + precipitation: isMetric + ? (current.precipitation || 0) + : parseFloat(mmToInch(current.precipitation || 0).toFixed(2)), + uvIndex: current.uv_index || 0, + visibility: current.visibility + ? isMetric + ? (current.visibility / 1000).toFixed(1) // meters to km + : kmToMi(current.visibility / 1000).toFixed(1) // meters to km to miles + : null, + isDay: current.is_day === 1, + weatherCode: code, + // Today's highs/lows + todayHigh: convTemp(daily.temperature_2m_max?.[0]), + todayLow: convTemp(daily.temperature_2m_min?.[0]), + // Forecasts + hourly: hourlyForecast, + daily: dailyForecast, + // Timezone + timezone: rawData.timezone || '', + // Units (for display labels) + tempUnit: isMetric ? 'C' : 'F', + windUnit: isMetric ? 'km/h' : 'mph', + visUnit: isMetric ? 'km' : 'mi', + // Raw Celsius for Header's dual display (avoids double-rounding) + rawTempC, + rawFeelsLikeC: current.apparent_temperature || 0, + }; + })(); return { data, loading }; }; From bf00ffda3cf7958138040d337e2727041996221c Mon Sep 17 00:00:00 2001 From: accius Date: Tue, 3 Feb 2026 00:32:55 -0500 Subject: [PATCH 3/3] color issues --- src/components/WorldMap.jsx | 4 ++-- src/utils/config.js | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index 848e9d7..11e8843 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -859,8 +859,8 @@ export const WorldMap = ({
)}
- ● DE - ● DX + ● DE + ● DX
{showPOTA && (
diff --git a/src/utils/config.js b/src/utils/config.js index 27edc4d..a3f965d 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -116,6 +116,11 @@ export const loadConfig = () => { // Mark if config needs setup (no callsign set anywhere) config.configIncomplete = (config.callsign === 'N0CALL' || !config.locator); + // Always inject version from server (not a user preference β€” server is source of truth) + if (serverConfig?.version) { + config.version = serverConfig.version; + } + return config; };
Mode:${sat.mode || 'Unknown'}
Alt:${sat.alt} km