diff --git a/src/components/CustomThemeEditor.jsx b/src/components/CustomThemeEditor.jsx
index 07805be9..5f26e384 100644
--- a/src/components/CustomThemeEditor.jsx
+++ b/src/components/CustomThemeEditor.jsx
@@ -1,27 +1,55 @@
import { HexColorPicker, RgbaColorPicker } from 'react-colorful';
import { THEME_COLOR_CONFIG } from '../theme/themeConfig';
-import { useTranslation } from 'react-i18next';
-
-export default function CustomThemeEditor({ id, customTheme, updateCustomVar }) {
- const { t } = useTranslation();
+import { rgbaStringToObject, rgbaObjectToString } from '../theme/colorUtils';
+export default function CustomThemeEditor({
+ id = 'custom-theme-editor-component',
+ customTheme,
+ updateCustomVar,
+ resetCustomToDefault,
+ t,
+}) {
return (
-
- {t('station.settings.theme.' + theme + '.describe')}
-
+
- {Object.entries(AVAILABLE_THEMES).map(([key, t]) => (
-
- ))}
+
+
+
+
{t('station.settings.theme.' + theme + '.describe')}
+
+
+
+ {t('station.settings.theme')}: {AVAILABLE_THEMES[theme].label}
+
+
+
+ {t('station.settings.theme.custom.--text-secondary')} / {t('station.settings.theme.custom.--bg-secondary')}
+
+
+
+ {t('station.settings.theme.custom.--text-primary')} / {t('station.settings.theme.custom.--bg-panel')}
+
+
+ {t('station.settings.theme.custom.--text-muted')} / {t('station.settings.theme.custom.--bg-panel')}
+
+
+
+
+
+ {t('station.settings.theme.custom.--text-muted')} / {t('station.settings.theme.custom.--bg-secondary')}
+
+
+
+ -
+ {t('station.settings.theme.custom.--accent-amber')}
+
+ -
+ {t('station.settings.theme.custom.--accent-amber-dim')}
+
+ -
+ {t('station.settings.theme.custom.--accent-green')}
+
+ -
+ {t('station.settings.theme.custom.--accent-green-dim')}
+
+ -
+ {t('station.settings.theme.custom.--accent-red')}
+
+ -
+ {t('station.settings.theme.custom.--accent-blue')}
+
+ -
+ {t('station.settings.theme.custom.--accent-cyan')}
+
+ -
+ {t('station.settings.theme.custom.--accent-purple')}
+
+
+
+
- >
+
);
}
diff --git a/src/lang/ca.json b/src/lang/ca.json
index 5a1d95cb..5c441f22 100644
--- a/src/lang/ca.json
+++ b/src/lang/ca.json
@@ -77,7 +77,6 @@
"contest.panel.time.live.minutes": "{{minutes}}m restants",
"contest.panel.time.startsIn": "Comença en {{hours}}h",
"contest.panel.title": "⊛ CONCURSOS",
-
"dxClusterPanel.filterTooltip": "Filtrar spots DX per banda, mode o continent",
"dxClusterPanel.filtersButton": "Filtres",
"dxClusterPanel.live": "EN VIU",
@@ -91,24 +90,19 @@
"dxClusterPanel.relativeTime": "fa {{minutes}}m ({{time}})",
"dxClusterPanel.spotter": "de {{spotter}}",
"dxClusterPanel.title": "CLÚSTER DX",
-
"plugins.layers.aurora.description": "Predicció de probabilitat d’aurores NOAA OVATION (30 min)",
"plugins.layers.aurora.name": "Predicció d’aurores",
-
"plugins.layers.earthquakes.description": "Dades sísmiques en viu de l’USGS (M2.5+ de les últimes 24 hores)",
"plugins.layers.earthquakes.name": "Terratrèmols",
"plugins.layers.earthquakes.viewDetails": "Veure detalls →",
-
"plugins.layers.floods.description": "Inundacions i tempestes severes actives arreu del món via NASA EONET",
"plugins.layers.floods.name": "Inundacions i Tempestes",
-
"plugins.layers.grayline.description": "Terminador dia/nit amb zones de crepuscle",
"plugins.layers.grayline.enhancedDx": "Zona DX millorada",
"plugins.layers.grayline.name": "Línia Grisa",
"plugins.layers.grayline.showTwilight": "Mostrar zones de crepuscle",
"plugins.layers.grayline.title": "Línia Grisa",
"plugins.layers.grayline.utcTime": "HORA UTC",
-
"plugins.layers.lightning.avgIntensity": "Intensitat mitjana:",
"plugins.layers.lightning.description": "Llamps en temps real arreu del món (30 min)",
"plugins.layers.lightning.fresh": "Recent (<1 min):",
@@ -119,7 +113,6 @@
"plugins.layers.lightning.title": "Activitat elèctrica",
"plugins.layers.lightning.total": "Total (30 min):",
"plugins.layers.lightning.updates": "Actualitzat cada 30s",
-
"plugins.layers.rbn.allBands": "Totes les bandes",
"plugins.layers.rbn.avgSnr": "SNR mitjà",
"plugins.layers.rbn.band": "Banda:",
@@ -134,7 +127,6 @@
"plugins.layers.rbn.title": "RBN",
"plugins.layers.wildfires.description": "Incendis forestals actius arreu del món via detecció satel·lital NASA EONET",
"plugins.layers.wildfires.name": "Incendis forestals",
-
"plugins.layers.wspr.allBands": "Totes les bandes",
"plugins.layers.wspr.animation": "Animació",
"plugins.layers.wspr.band": "Banda:",
@@ -166,11 +158,9 @@
"plugins.layers.wspr.txStations": "Estacions TX:",
"plugins.layers.wspr.veryWeak": "Molt dèbil (< -20 dB)",
"plugins.layers.wspr.weak": "Dèbil (-20 a -10 dB)",
-
"plugins.layers.wxradar.attribution": "Dades meteorològiques © Iowa State University Mesonet",
"plugins.layers.wxradar.description": "Superposició del radar meteorològic NEXRAD per a Amèrica del Nord",
"plugins.layers.wxradar.name": "Radar meteorològic",
-
"propagation.day": "Dia",
"propagation.estimated": "estimat",
"propagation.geomag": "Geomag",
@@ -189,10 +179,8 @@
"propagation.view.toggle": "clica per canviar",
"pskReporterPanel.map.hide": "Amagar spots al mapa",
"pskReporterPanel.map.show": "Mostrar spots al mapa",
-
"pskReporterPanel.mode.pskTooltip": "Informes de recepció per Internet via PSKReporter.info",
"pskReporterPanel.mode.wsjtxTooltip": "Decodificacions WSJT-X locals via relé UDP",
-
"pskReporterPanel.psk.connecting": "Connectant...",
"pskReporterPanel.psk.connectionFailed": "Connexió fallida, cal refrescar?",
"pskReporterPanel.psk.filterTooltip": "Filtrar spots per banda, mode o quadrícula",
@@ -201,16 +189,13 @@
"pskReporterPanel.psk.refreshTooltip": "Reconnectar a PSKReporter",
"pskReporterPanel.psk.setCallsign": "Configura el teu indicatiu a Configuració per veure informes",
"pskReporterPanel.psk.waitingForSpots": "Esperant spots... (TX per veure informes)",
-
"pskReporterPanel.tabs.heard": "Escoltat ({{count}})",
"pskReporterPanel.tabs.heardTooltip": "▲ Estacions que escolten el teu senyal",
"pskReporterPanel.tabs.hearing": "Escoltant ({{count}})",
"pskReporterPanel.tabs.hearingTooltip": "▼ Estacions que escoltes",
-
"pskReporterPanel.time.hours": "{{hours}}h",
"pskReporterPanel.time.minutes": "{{minutes}}m",
"pskReporterPanel.time.now": "ara",
-
"pskReporterPanel.wsjtx.decodes": "Decodificacions ({{count}})",
"pskReporterPanel.wsjtx.decodingTooltip": "Decodificacions WSJT-X en viu",
"pskReporterPanel.wsjtx.downloadRelay": "Descarrega l’agent relé per al teu PC:",
@@ -219,18 +204,14 @@
"pskReporterPanel.wsjtx.listening": "Escoltant...",
"pskReporterPanel.wsjtx.noDecodesFiltered": "Cap decodificació coincideix amb el filtre",
"pskReporterPanel.wsjtx.noQsos": "Encara no hi ha QSOs registrats",
-
"pskReporterPanel.wsjtx.platformLinux": "🐧 Linux",
"pskReporterPanel.wsjtx.platformMac": "🍎 Mac",
"pskReporterPanel.wsjtx.platformWindows": "🪟 Windows",
-
"pskReporterPanel.wsjtx.qsos": "QSOs ({{count}})",
"pskReporterPanel.wsjtx.qsosTooltip": "QSOs registrats des de WSJT-X",
-
"pskReporterPanel.wsjtx.relayConnected": "Relé connectat",
"pskReporterPanel.wsjtx.relayHint": "Les decodificacions WSJT-X apareixeran aquí quan l’estació estigui activa",
"pskReporterPanel.wsjtx.requiresNode": "Requereix Node.js 🟢 Executa l’script i després inicia WSJT-X",
-
"pskReporterPanel.wsjtx.udpAddress": "Adreça: 127.0.0.1 🟢 Port: {{port}}",
"pskReporterPanel.wsjtx.udpPath": "A WSJT-X: Settings → Reporting → UDP Server",
"pskReporterPanel.wsjtx.waiting": "Esperant WSJT-X...",
@@ -255,9 +236,7 @@
"station.settings.dx.option3": "DXWatch",
"station.settings.dx.option4": "Auto (provar totes les fonts)",
"station.settings.dx.title": "Font del Clúster DX",
-
"station.settings.headerSize": "Mida del teu indicatiu",
-
"station.settings.language": "Idioma",
"station.settings.language.de": "Deutsch",
"station.settings.language.en": "English",
@@ -273,16 +252,13 @@
"station.settings.language.ru": "Русский",
"station.settings.language.ka": "ქართული",
"station.settings.language.ms": "Melayu",
-
"station.settings.latitude": "Latitud",
"station.settings.longitude": "Longitud",
"station.settings.locator": "Quadrícula (o introdueix Lat/Lon a sota)",
"station.settings.locator.placeholder": "FN20nc",
-
"station.settings.layers.noLayers": "No hi ha capes de mapa disponibles",
"station.settings.layers.opacity": "Opacitat",
"station.settings.layers.title": "Capes del mapa",
-
"station.settings.layout": "Disseny",
"station.settings.layout.classic": "Clàssic",
"station.settings.layout.classic.describe": "→ Disseny estil HamClock original",
@@ -296,13 +272,10 @@
"station.settings.layout.reset.confirm": "Restablir el disseny dels panells al predeterminat?",
"station.settings.layout.tablet": "Tauleta",
"station.settings.layout.tablet.describe": "→ Optimitzat per a pantalles amples de 7–10\" (16:9)",
-
"station.settings.mouseZoom": "Sensibilitat de la roda del ratolí",
"station.settings.mouseZoom.describeMax": "Més",
"station.settings.mouseZoom.describeMin": "Menys",
-
"station.settings.power": "Potència (W)",
-
"station.settings.rigControl.autoMode": "Mode automàtic",
"station.settings.rigControl.autoMode.hint": "Canvia a CW/SSB/Dades segons el pla de banda en sintonitzar",
"station.settings.rigControl.enabled": "Habilitar integració Hamlib",
@@ -311,18 +284,15 @@
"station.settings.rigControl.title": "📻 Control de ràdio",
"station.settings.rigControl.tuneEnabled": "Clica per sintonitzar",
"station.settings.rigControl.tuneEnabled.hint": "Fer clic als spots sintonitza la ràdio",
-
"station.settings.satellites.belowHorizon": "✗ Sota l’horitzó",
"station.settings.satellites.clear": "Netejar",
"station.settings.satellites.selectAll": "Seleccionar-ho tot",
"station.settings.satellites.selectedCount": "{{count}} satèl·lit(s) seleccionat(s)",
"station.settings.satellites.showAll": "Mostrant tots els satèl·lits (sense filtre)",
"station.settings.satellites.visible": "✓ Visible",
-
"station.settings.tab1.title": "⌇ Estació",
"station.settings.tab2.title": "⊞ Capes del mapa",
"station.settings.tab3.title": "⛊ Satèl·lits",
-
"station.settings.theme": "TEMA",
"station.settings.theme.dark": "Fosc",
"station.settings.theme.dark.describe": "→ Tema fosc modern (predeterminat)",
@@ -332,26 +302,44 @@
"station.settings.theme.light.describe": "→ Tema clar per a ús diürn",
"station.settings.theme.retro": "Retro",
"station.settings.theme.retro.describe": "→ Estil retro Windows anys 90",
-
+ "station.settings.theme.custom": "Personalitzat",
+ "station.settings.theme.custom.describe": "→ Un tema configurable",
+ "station.settings.theme.custom.--bg-primary": "Fons principal",
+ "station.settings.theme.custom.--bg-secondary": "Fons secundari",
+ "station.settings.theme.custom.--bg-tertiary": "Fons terciari",
+ "station.settings.theme.custom.--bg-panel": "Fons del panell",
+ "station.settings.theme.custom.--border-color": "Color de la vora",
+ "station.settings.theme.custom.--text-primary": "Text principal",
+ "station.settings.theme.custom.--text-secondary": "Text secundari",
+ "station.settings.theme.custom.--text-muted": "Text apagat",
+ "station.settings.theme.custom.--map-ocean": "Mapa de l'oceà",
+ "station.settings.theme.custom.--accent-amber": "Accent ambre",
+ "station.settings.theme.custom.--accent-amber-dim": "Accent ambre (atenuat)",
+ "station.settings.theme.custom.--accent-green": "Accent verd",
+ "station.settings.theme.custom.--accent-green-dim": "Accent verd (atenuat)",
+ "station.settings.theme.custom.--accent-red": "Accent vermell",
+ "station.settings.theme.custom.--accent-blue": "Accent blau",
+ "station.settings.theme.custom.--accent-cyan": "Accent cian",
+ "station.settings.theme.custom.--accent-purple": "Accent morat",
+ "station.settings.theme.reset": "Restableix el tema personalitzat als colors predeterminats",
+ "station.settings.theme.reset.confirm": "Vols restablir el tema personalitzat als valors predeterminats? Això substituirà els teus colors personalitzats actuals. Aquesta acció no es pot desfer.",
+ "station.settings.theme.sampleButtonText": "Botó",
"station.settings.timezone": "Zona horària",
"station.settings.timezone.auto": "Auto (predeterminat del navegador)",
"station.settings.timezone.currentDefault": " S’està usant el predeterminat del navegador.",
"station.settings.timezone.describe": "Ajusta-ho si l’hora local es mostra incorrectament (p. ex. igual que UTC). Navegadors de privacitat com Librewolf poden falsejar la zona horària.",
-
"station.settings.timezone.group.africa": "Àfrica",
"station.settings.timezone.group.asiaPacific": "Àsia i Pacífic",
"station.settings.timezone.group.europe": "Europa",
"station.settings.timezone.group.northAmerica": "Amèrica del Nord",
"station.settings.timezone.group.other": "Altres",
"station.settings.timezone.group.southAmerica": "Amèrica del Sud",
-
"station.settings.tip.env": "💡 Consell: Per a una configuració permanent, copia
.env.example a
.env i configura CALLSIGN i LOCATOR",
"station.settings.title": "⚙ Configuració de l’estació",
"station.settings.useLocation": "📍 Usar la meva ubicació actual",
"station.settings.useLocation.error1": "No s’ha pogut obtenir la ubicació. Introdueix-la manualment.",
"station.settings.useLocation.error2": "La geolocalització no és compatible amb el teu navegador.",
"station.settings.welcome": "👋 Benvingut/da a OpenHamClock!",
-
"weather.clouds": "☁️ Núvols",
"weather.condition.0": "Serè",
"weather.condition.1": "Majoritàriament serè",
@@ -381,7 +369,6 @@
"weather.condition.95": "Tempesta",
"weather.condition.96": "Tempesta amb calamarsa lleu",
"weather.condition.99": "Tempesta amb calamarsa intensa",
-
"weather.dewPoint": "🌡️ Punt de rosada",
"weather.error.busy": "Servei meteorològic ocupat",
"weather.error.loading": "Carregant el temps...",
diff --git a/src/lang/de.json b/src/lang/de.json
index 90b89f8d..241524b6 100644
--- a/src/lang/de.json
+++ b/src/lang/de.json
@@ -342,6 +342,9 @@
"station.settings.theme.custom.--accent-blue": "Blauer Akzent",
"station.settings.theme.custom.--accent-cyan": "Cyanfarbener Akzent",
"station.settings.theme.custom.--accent-purple": "Lila Akzent",
+ "station.settings.theme.reset": "Benutzerdefiniertes Design auf Standardfarben zurücksetzen",
+ "station.settings.theme.reset.confirm": "Benutzerdefiniertes Design auf Standardfarben zurücksetzen? Dadurch werden Ihre aktuellen benutzerdefinierten Farben überschrieben. Dieser Vorgang kann nicht rückgängig gemacht werden.",
+ "station.settings.theme.sampleButtonText": "Taste",
"station.settings.timezone": "Zeitzone",
"station.settings.timezone.auto": "Auto (Browser-Standard)",
"station.settings.timezone.currentDefault": " Browser-Standard wird verwendet.",
diff --git a/src/lang/en.json b/src/lang/en.json
index bcfd6316..2084e8e0 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -98,6 +98,9 @@
"station.settings.theme.custom.--accent-blue": "Blue Accent",
"station.settings.theme.custom.--accent-cyan": "Cyan Accent",
"station.settings.theme.custom.--accent-purple": "Purple Accent",
+ "station.settings.theme.reset": "Reset Custom Theme to Default Colors",
+ "station.settings.theme.reset.confirm": "Reset custom theme to defaults? This will replace your current custom colors. This cannot be undone.",
+ "station.settings.theme.sampleButtonText": "Button",
"station.settings.timezone": "🕐 Timezone",
"station.settings.timezone.describe": "Set this if your local time shows incorrectly (e.g. same as UTC). Privacy browsers like Librewolf may spoof your timezone.",
"station.settings.title": "Station Settings",
diff --git a/src/lang/es.json b/src/lang/es.json
index c800fbdd..c3b73c23 100644
--- a/src/lang/es.json
+++ b/src/lang/es.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "Acento azul",
"station.settings.theme.custom.--accent-cyan": "Acento cian",
"station.settings.theme.custom.--accent-purple": "Acento morado",
+ "station.settings.theme.reset": "Restablecer el tema personalizado a los colores predeterminados",
+ "station.settings.theme.reset.confirm": "¿Restablecer el tema personalizado a los valores predeterminados? Esto reemplazará tus colores personalizados actuales. Esta acción no se puede deshacer.",
+ "station.settings.theme.sampleButtonText": "Botón",
"station.settings.timezone": "Zona horaria",
"station.settings.timezone.auto": "Auto (predeterminado del navegador)",
"station.settings.timezone.currentDefault": " Usando el predeterminado del navegador.",
diff --git a/src/lang/fr.json b/src/lang/fr.json
index ce2afadd..69e20358 100644
--- a/src/lang/fr.json
+++ b/src/lang/fr.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "Bleu",
"station.settings.theme.custom.--accent-cyan": "Cyan",
"station.settings.theme.custom.--accent-purple": "Violet",
+ "station.settings.theme.reset": "Réinitialiser le thème personnalisé aux couleurs par défaut",
+ "station.settings.theme.reset.confirm": "Réinitialiser le thème personnalisé aux couleurs par défaut ? Cette action remplacera vos couleurs personnalisées actuelles. Elle est irréversible.",
+ "station.settings.theme.sampleButtonText": "Bouton",
"station.settings.timezone": "🕐 Fuseau horaire",
"station.settings.timezone.auto": "Auto (par défaut navigateur)",
"station.settings.timezone.currentDefault": " Utilisation du réglage navigateur.",
diff --git a/src/lang/it.json b/src/lang/it.json
index 567faa3b..c25c6b10 100644
--- a/src/lang/it.json
+++ b/src/lang/it.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "Accento blu",
"station.settings.theme.custom.--accent-cyan": "Accento ciano",
"station.settings.theme.custom.--accent-purple": "Accento viola",
+ "station.settings.theme.reset": "Ripristina i colori predefiniti del tema personalizzato",
+ "station.settings.theme.reset.confirm": "Ripristinare i colori predefiniti del tema personalizzato? Questa operazione sostituirà i colori personalizzati correnti. Questa operazione non può essere annullata.",
+ "station.settings.theme.sampleButtonText": "Pulsante",
"station.settings.timezone": "Fuso orario",
"station.settings.timezone.auto": "Auto (predefinito browser)",
"station.settings.timezone.currentDefault": " Attualmente uso il predefinito del browser.",
diff --git a/src/lang/ja.json b/src/lang/ja.json
index ac8151e0..54cfa176 100644
--- a/src/lang/ja.json
+++ b/src/lang/ja.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "ブルーアクセント",
"station.settings.theme.custom.--accent-cyan": "シアンアクセント",
"station.settings.theme.custom.--accent-purple": "パープルアクセント",
+ "station.settings.theme.reset": "カスタムテーマをデフォルトカラーにリセット",
+ "station.settings.theme.reset.confirm": "カスタムテーマをデフォルトにリセットしますか?これにより、現在のカスタムカラーが置き換えられます。この操作は元に戻せません。",
+ "station.settings.theme.sampleButtonText": "ボタン",
"station.settings.timezone": "🕐 タイムゾーン",
"station.settings.timezone.auto": "自動 (ブラウザのデフォルト)",
"station.settings.timezone.currentDefault": " 現在ブラウザのデフォルトを使用中。",
diff --git a/src/lang/ka.json b/src/lang/ka.json
index 096abf70..9b476612 100644
--- a/src/lang/ka.json
+++ b/src/lang/ka.json
@@ -87,6 +87,9 @@
"station.settings.theme.custom.--accent-blue": "ლურჯი აქცენტი",
"station.settings.theme.custom.--accent-cyan": "ცისფერი აქცენტი",
"station.settings.theme.custom.--accent-purple": "იისფერი აქცენტი",
+ "station.settings.theme.reset": "მორგებული თემის ნაგულისხმევ ფერებზე დაბრუნება",
+ "station.settings.theme.reset.confirm": "გსურთ მორგებული თემის ნაგულისხმევ ფერებზე დაბრუნება? ეს ჩაანაცვლებს თქვენს მიმდინარე მორგებულ ფერებს. ამ მოქმედების გაუქმება შეუძლებელია.",
+ "station.settings.theme.sampleButtonText": "ღილაკი",
"station.settings.timezone": "🕐 დროის სარტყელი",
"station.settings.timezone.describe": "დააყენეთ, თუ ადგილობრივი დრო არასწორად ჩანს (მაგ. UTC-ს ემთხვევა). კონფიდენციალურობის ბრაუზერები შეიძლება ცვლიდნენ დროის სარტყელს.",
"station.settings.title": "სადგურის პარამეტრები",
diff --git a/src/lang/ko.json b/src/lang/ko.json
index d1535e27..43c88779 100644
--- a/src/lang/ko.json
+++ b/src/lang/ko.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "파란색 강조 색상",
"station.settings.theme.custom.--accent-cyan": "청록색 강조 색상",
"station.settings.theme.custom.--accent-purple": "보라색 강조 색상",
+ "station.settings.theme.reset": "사용자 지정 테마를 기본 색상으로 초기화하시겠습니까?",
+ "station.settings.theme.reset.confirm": "사용자 지정 테마를 기본 색상으로 초기화하면 현재 사용자 지정 색상이 변경됩니다. 이 작업은 되돌릴 수 없습니다.",
+ "station.settings.theme.sampleButtonText": "단추",
"station.settings.timezone": "🕐 시간대",
"station.settings.timezone.auto": "자동 (브라우저 기본값)",
"station.settings.timezone.currentDefault": " 현재 브라우저 기본값 사용 중.",
diff --git a/src/lang/ms.json b/src/lang/ms.json
index 97dbbbe4..7ffcf704 100644
--- a/src/lang/ms.json
+++ b/src/lang/ms.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "Aksen Biru",
"station.settings.theme.custom.--accent-cyan": "Aksen Sian",
"station.settings.theme.custom.--accent-purple": "Aksen Ungu",
+ "station.settings.theme.reset": "Tetapkan Semula Tema Tersuai kepada Warna Lalai",
+ "station.settings.theme.reset.confirm": "Tetapkan semula tema tersuai kepada lalai? Ini akan menggantikan warna tersuai semasa anda. Ini tidak boleh dibuat asal.",
+ "station.settings.theme.sampleButtonText": "Butang",
"station.settings.timezone": "🕐 Zon Waktu",
"station.settings.timezone.auto": "Auto (lalai pelayar)",
"station.settings.timezone.currentDefault": " Sedang menggunakan lalai pelayar.",
diff --git a/src/lang/nl.json b/src/lang/nl.json
index 6aa15e62..b8e5af58 100644
--- a/src/lang/nl.json
+++ b/src/lang/nl.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "Blauw accent",
"station.settings.theme.custom.--accent-cyan": "Cyaan accent",
"station.settings.theme.custom.--accent-purple": "Paars accent",
+ "station.settings.theme.reset": "Aangepast thema terugzetten naar standaardkleuren",
+ "station.settings.theme.reset.confirm": "Aangepast thema terugzetten naar standaardinstellingen? Hiermee worden uw huidige aangepaste kleuren vervangen. Deze actie kan niet ongedaan worden gemaakt.",
+ "station.settings.theme.sampleButtonText": "Knop",
"station.settings.timezone": "Tijdzone",
"station.settings.timezone.auto": "Auto (browserstandaard)",
"station.settings.timezone.currentDefault": " Browserstandaard wordt gebruikt.",
diff --git a/src/lang/pt.json b/src/lang/pt.json
index 5aaff644..dd7e9859 100644
--- a/src/lang/pt.json
+++ b/src/lang/pt.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "Destaque Azul",
"station.settings.theme.custom.--accent-cyan": "Destaque Ciano",
"station.settings.theme.custom.--accent-purple": "Destaque Roxo",
+ "station.settings.theme.reset": "Redefinir tema personalizado para as cores padrão",
+ "station.settings.theme.reset.confirm": "Redefinir tema personalizado para as cores padrão? Isso substituirá suas cores personalizadas atuais. Essa ação não pode ser desfeita.",
+ "station.settings.theme.sampleButtonText": "Botão",
"station.settings.timezone": "Fuso horário",
"station.settings.timezone.auto": "Auto (padr?o do navegador)",
"station.settings.timezone.currentDefault": " Usando o padr?o do navegador.",
diff --git a/src/lang/ru.json b/src/lang/ru.json
index 484b1669..77b8d8a5 100644
--- a/src/lang/ru.json
+++ b/src/lang/ru.json
@@ -87,6 +87,9 @@
"station.settings.theme.custom.--accent-blue": "Синий акцент",
"station.settings.theme.custom.--accent-cyan": "Бирюзовый акцент",
"station.settings.theme.custom.--accent-purple": "Фиолетовый акцент",
+ "station.settings.theme.reset": "Сбросить пользовательскую тему до цветов по умолчанию",
+ "station.settings.theme.reset.confirm": "Сбросить пользовательскую тему до цветов по умолчанию? Это заменит ваши текущие пользовательские цвета. Это действие необратимо.",
+ "station.settings.theme.sampleButtonText": "Кнопка",
"station.settings.timezone": "🕐 Часовой пояс",
"station.settings.timezone.describe": "Установите, если местное время отображается неправильно (например, совпадает с UTC). Браузеры конфиденциальности могут подменять часовой пояс.",
"station.settings.title": "Настройки станции",
diff --git a/src/lang/sl.json b/src/lang/sl.json
index 87bde91d..1deb5929 100644
--- a/src/lang/sl.json
+++ b/src/lang/sl.json
@@ -337,6 +337,9 @@
"station.settings.theme.custom.--accent-blue": "Moder poudarek",
"station.settings.theme.custom.--accent-cyan": "Cianov poudarek",
"station.settings.theme.custom.--accent-purple": "Vijoličen poudarek",
+ "station.settings.theme.reset": "Ponastavitev teme po meri na privzete barve",
+ "station.settings.theme.reset.confirm": "Želite ponastaviti temo po meri na privzete nastavitve? To bo nadomestilo vaše trenutne barve po meri. Tega ni mogoče razveljaviti.",
+ "station.settings.theme.sampleButtonText": "Gumb",
"station.settings.timezone": "Časovni pas",
"station.settings.timezone.auto": "Samodejno (privzeto v brskalniku)",
"station.settings.timezone.currentDefault": " Uporablja se privzeta nastavitev brskalnika.",
diff --git a/src/lang/zh.json b/src/lang/zh.json
index 9e9787a0..3676958b 100644
--- a/src/lang/zh.json
+++ b/src/lang/zh.json
@@ -69,6 +69,28 @@
"station.settings.theme.light.describe": "→ 适用于日间使用的浅色主题",
"station.settings.theme.retro": "怀旧",
"station.settings.theme.retro.describe": "→ 90年代 Windows 风格",
+ "station.settings.theme.custom": "自定义",
+ "station.settings.theme.custom.describe": "→ 可配置主题",
+ "station.settings.theme.custom.--bg-primary": "主背景",
+ "station.settings.theme.custom.--bg-secondary": "次背景",
+ "station.settings.theme.custom.--bg-tertiary": "三级背景",
+ "station.settings.theme.custom.--bg-panel": "面板背景",
+ "station.settings.theme.custom.--border-color": "边框颜色",
+ "station.settings.theme.custom.--text-primary": "主文本",
+ "station.settings.theme.custom.--text-secondary": "次文本",
+ "station.settings.theme.custom.--text-muted": "低亮度文本",
+ "station.settings.theme.custom.--map-ocean": "海洋地图",
+ "station.settings.theme.custom.--accent-amber": "琥珀色点缀",
+ "station.settings.theme.custom.--accent-amber-dim": "琥珀色点缀(暗)",
+ "station.settings.theme.custom.--accent-green": "绿色点缀",
+ "station.settings.theme.custom.--accent-green-dim": "绿色点缀(暗)",
+ "station.settings.theme.custom.--accent-red": "红色点缀",
+ "station.settings.theme.custom.--accent-blue": "蓝色点缀",
+ "station.settings.theme.custom.--accent-cyan": "青色点缀",
+ "station.settings.theme.custom.--accent-purple": "紫色点缀",
+ "station.settings.theme.reset": "将自定义主题重置为默认颜色",
+ "station.settings.theme.reset.confirm": "将自定义主题重置为默认设置?这将替换您当前的自定义颜色。此操作无法撤销。",
+ "station.settings.theme.sampleButtonText": "按钮",
"station.settings.timezone": "🕐 时区",
"station.settings.timezone.describe": "如果您的本地时间显示错误请手动设置。某些隐私浏览器可能会伪造时区。",
"station.settings.title": "电台设置",
diff --git a/src/styles/main.css b/src/styles/main.css
index eb012b85..1341486f 100644
--- a/src/styles/main.css
+++ b/src/styles/main.css
@@ -625,24 +625,31 @@ body::before {
.text-amber {
color: var(--accent-amber);
}
+
.text-green {
color: var(--accent-green);
}
+
.text-red {
color: var(--accent-red);
}
+
.text-blue {
color: var(--accent-blue);
}
+
.text-cyan {
color: var(--accent-cyan);
}
+
.text-muted {
color: var(--text-muted);
}
+
.text-primary {
color: var(--text-primary);
}
+
.text-secondary {
color: var(--text-secondary);
}
@@ -650,6 +657,7 @@ body::before {
.font-mono {
font-family: 'JetBrains Mono', monospace;
}
+
.font-display {
font-family: 'Orbitron', monospace;
}
@@ -657,43 +665,144 @@ body::before {
.bg-panel {
background: var(--bg-panel);
}
+
.bg-primary {
background: var(--bg-primary);
}
+
.bg-secondary {
background: var(--bg-secondary);
}
+
.bg-tertiary {
background: var(--bg-tertiary);
}
/* Theme controls */
-#theme-selector-component .theme-select-button {
+.theme-selector-component {
+ display: flex;
+ grid-column: 1 / -1;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1em;
+}
+
+#custom-theme-editor-component {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 8px;
+}
+
+.theme-selector-control {
+ flex: 1;
+ flex-basis: 350px;
+ box-sizing: border-box;
+}
+
+.theme-selector-control select {
+ width: 100%;
padding: 10px;
background: var(--bg-tertiary);
+ color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
+ font-family: 'JetBrains Mono', monospace;
+ cursor: pointer;
+}
+
+.theme-selector-control .theme-description {
+ font-size: 11px;
+ color: var(--text-muted);
+ margin-top: 6px;
+}
+
+.theme-selector-preview {
+ flex: 1;
+ flex-basis: 350px;
+ box-sizing: border-box;
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ padding: 1em 2em 2em 2em;
+}
+
+.theme-selector-preview h2 {
+ color: var(--text-primary);
+ text-transform: capitalize;
+ margin-bottom: 0.5em;
+}
+
+.theme-selector-preview .preview-element {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ padding: 1em;
+ border-radius: 6px;
+ margin: 0;
+}
+
+.theme-selector-preview h4 {
color: var(--text-secondary);
- font-size: 12px;
+}
+
+.theme-selector-preview .preview-panel {
+ background: var(--bg-panel);
+ border: 1px solid var(--border-color);
+ padding: 1em;
+ margin: 1em 0;
+ border-radius: 6px;
+}
+
+.theme-selector-preview .preview-panel:last-child {
+ margin-bottom: 0;
+}
+
+.theme-selector-preview .preview-panel ul {
+ margin: 0 1em;
+}
+
+.theme-selector-preview .preview-panel ul li {
+ margin: 0 0.25em;
+}
+
+.theme-selector-preview h4 {
+ color: var(--text-primary);
+}
+
+.theme-selector-preview button {
+ display: inline-block;
+ padding: 1em;
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
cursor: pointer;
- font-weight: 400;
}
-#theme-selector-component .theme-select-button span.icon {
- text-shadow: 0 0 5px #000;
+.theme-selector-preview button.btn-primary {
+ margin-right: 1em;
+ background: var(--accent-amber);
+ color: var(--text-primary);
}
-#theme-selector-component .theme-select-button.active {
- border-color: var(--accent-amber);
- background-color: var(--accent-amber);
- color: #000;
- font-weight: 600;
+.theme-selector-preview button.btn-secondary {
+ background: var(--bg-tertiary);
+ color: var(--text-secondary);
}
-#custom-theme-editor-component {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 8px;
+.theme-selector-preview p.muted-text {
+ color: var(--text-muted);
+ margin: 1em 0;
+}
+
+.reset-theme-button {
+ min-width: 100%;
+ margin: 2em 0;
+ padding: 10px;
+ background: var(--bg-tertiary);
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ color: var(--text-secondary);
+ font-size: 12px;
+ cursor: pointer;
+ font-weight: 400;
}
@media screen and (max-width: 768px) {
@@ -726,11 +835,6 @@ body::before {
display: block;
}
-/* Hide the hue slider for variables that have color names */
-#custom-theme-editor-component .hue-locked .react-colorful__hue {
- display: none;
-}
-
/* ============================================
WSPR PLUGIN ANIMATIONS (v1.3.0)
============================================ */
diff --git a/src/styles/themes.css b/src/styles/themes.css
index 9b7d3c59..4fb67412 100644
--- a/src/styles/themes.css
+++ b/src/styles/themes.css
@@ -20,7 +20,6 @@
--accent-cyan: #00ddff;
--accent-purple: #aa66ff;
--map-ocean: #0a0e14;
- /* --scanline-opacity: 0.02; */
}
/* ============================================
@@ -44,7 +43,6 @@
--accent-cyan: #0099bb;
--accent-purple: #7744cc;
--map-ocean: #f0f4f8;
- /* --scanline-opacity: 0; */
}
/* ============================================
@@ -68,7 +66,6 @@
--accent-cyan: #00ffff;
--accent-purple: #ff00ff;
--map-ocean: #000008;
- /* --scanline-opacity: 0.05; */
}
/* ============================================
@@ -96,5 +93,4 @@
--title-bar: linear-gradient(90deg, #000080, #1084d0);
--title-bar-text: #ffffff;
--map-ocean: #000080;
- /* --scanline-opacity: 0; */
}
diff --git a/src/theme/colorUtils.js b/src/theme/colorUtils.js
new file mode 100644
index 00000000..6f5b27c1
--- /dev/null
+++ b/src/theme/colorUtils.js
@@ -0,0 +1,25 @@
+export function rgbaStringToObject(str) {
+ if (!str) return { r: 0, g: 0, b: 0, a: 1 };
+
+ const match = str.match(/rgba?\(([^)]+)\)/);
+ if (!match) return { r: 0, g: 0, b: 0, a: 1 };
+
+ const parts = match[1].split(',').map((p) => p.trim());
+
+ return {
+ r: parseInt(parts[0], 10),
+ g: parseInt(parts[1], 10),
+ b: parseInt(parts[2], 10),
+ a: parts[3] !== undefined ? parseFloat(parts[3]) : 1,
+ };
+}
+
+export function rgbaObjectToString({ r, g, b, a }) {
+ /* in case we get a malformed object, adjust it to be valid */
+ r = Math.max(0, Math.min(255, r));
+ g = Math.max(0, Math.min(255, g));
+ b = Math.max(0, Math.min(255, b));
+ a = Math.max(0, Math.min(1, a ?? 1));
+
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+}
diff --git a/src/theme/themeConfig.js b/src/theme/themeConfig.js
index aed2b508..83e714f5 100644
--- a/src/theme/themeConfig.js
+++ b/src/theme/themeConfig.js
@@ -1,29 +1,31 @@
export const THEME_COLOR_CONFIG = {
- '--bg-primary': { alpha: false, hueRestrict: null },
- '--bg-secondary': { alpha: false, hueRestrict: null },
- '--bg-tertiary': { alpha: false, hueRestrict: null },
- '--bg-panel': { alpha: false, hueRestrict: null },
- '--border-color': { alpha: true, hueRestrict: null },
- '--text-primary': { alpha: false, hueRestrict: null },
- '--text-secondary': { alpha: false, hueRestrict: null },
- '--text-muted': { alpha: false, hueRestrict: null },
- '--map-ocean': { alpha: false, hueRestrict: null },
- '--accent-amber': { alpha: false, hueRestrict: 45 },
- '--accent-amber-dim': { alpha: false, hueRestrict: 45 },
- '--accent-green': { alpha: false, hueRestrict: 120 },
- '--accent-green-dim': { alpha: false, hueRestrict: 120 },
- '--accent-red': { alpha: false, hueRestrict: 0 },
- '--accent-blue': { alpha: false, hueRestrict: 240 },
- '--accent-cyan': { alpha: false, hueRestrict: 180 },
- '--accent-purple': { alpha: false, hueRestrict: 277 },
+ '--bg-primary': { alpha: false },
+ '--bg-secondary': { alpha: false },
+ '--bg-tertiary': { alpha: false },
+ '--bg-panel': { alpha: true },
+ '--border-color': { alpha: true },
+ '--text-primary': { alpha: false },
+ '--text-secondary': { alpha: false },
+ '--text-muted': { alpha: true },
+ '--map-ocean': { alpha: false },
+ '--accent-amber': { alpha: false },
+ '--accent-amber-dim': { alpha: true },
+ '--accent-green': { alpha: false },
+ '--accent-green-dim': { alpha: true },
+ '--accent-red': { alpha: false },
+ '--accent-blue': { alpha: false },
+ '--accent-cyan': { alpha: false },
+ '--accent-purple': { alpha: false },
};
export const THEME_VARS = Object.keys(THEME_COLOR_CONFIG);
export const AVAILABLE_THEMES = {
- dark: { label: 'Dark', icon: '🌙' },
- light: { label: 'Light', icon: '☀️' },
- legacy: { label: 'Legacy', icon: '💻' },
- retro: { label: 'Retro', icon: '🪟' },
- custom: { label: 'Custom', icon: '🎨' },
+ dark: { label: 'Dark' },
+ light: { label: 'Light' },
+ legacy: { label: 'Legacy' },
+ retro: { label: 'Retro' },
+ custom: { label: 'Custom' },
};
+
+export const DEFAULT_THEME = 'dark';
diff --git a/src/theme/themeUtils.js b/src/theme/themeUtils.js
index 21dd7eff..62381837 100644
--- a/src/theme/themeUtils.js
+++ b/src/theme/themeUtils.js
@@ -1,18 +1,4 @@
import { THEME_VARS } from './themeConfig';
-
-/* Add "active" class to theme button that is selected */
-export function setActiveThemeButton(selectorString) {
- const allThemeButtons = document.querySelectorAll('.theme-select-button');
- allThemeButtons.forEach((element) => {
- element.classList.remove('active');
- });
-
- const activeButton = document.querySelector('.' + selectorString + '-theme-select-button');
- if (activeButton) {
- activeButton.classList.add('active');
- }
-}
-
/* Read CSS variables from the active theme */
export function readCssVariables() {
const styles = getComputedStyle(document.documentElement);
@@ -21,6 +7,7 @@ export function readCssVariables() {
/* Apply a theme object to :root */
export function applyCustomTheme(themeVars) {
+ document.documentElement.removeAttribute('data-theme');
Object.entries(themeVars).forEach(([key, value]) => {
document.documentElement.style.setProperty(key, value);
});
@@ -31,3 +18,17 @@ export function applyPrebuiltTheme(themeName) {
document.documentElement.removeAttribute('style'); // clears custom overrides
document.documentElement.setAttribute('data-theme', themeName);
}
+
+/* get a theme's styles */
+export function getThemeStyles(themeName) {
+ document.documentElement.removeAttribute('style');
+ document.documentElement.setAttribute('data-theme', themeName);
+
+ const styles = getComputedStyle(document.documentElement);
+ return Array.from(styles)
+ .filter((name) => name.startsWith('--'))
+ .reduce((acc, name) => {
+ acc[name] = styles.getPropertyValue(name).trim();
+ return acc;
+ }, {});
+}
diff --git a/src/theme/useTheme.js b/src/theme/useTheme.js
index b38d1878..f2746e41 100644
--- a/src/theme/useTheme.js
+++ b/src/theme/useTheme.js
@@ -1,18 +1,19 @@
import { useEffect, useState } from 'react';
import { loadConfig, saveConfig } from './themeStorage';
-import { setActiveThemeButton, readCssVariables, applyCustomTheme, applyPrebuiltTheme } from './themeUtils';
+import { DEFAULT_THEME } from '../theme/themeConfig';
+import { getThemeStyles, readCssVariables, applyCustomTheme, applyPrebuiltTheme } from './themeUtils';
export function useTheme() {
const config = loadConfig();
- const [theme, setTheme] = useState(config.theme || 'dark');
+ const [theme, setTheme] = useState(config.theme || DEFAULT_THEME);
const [customTheme, setCustomTheme] = useState(config.customTheme || null);
/* Initial load */
useEffect(() => {
if (!config.customTheme) {
- const defaults = readCssVariables(); // from dark theme
- saveConfig({ theme: 'dark', customTheme: defaults });
+ const defaults = readCssVariables(); // from default theme
+ saveConfig({ theme: DEFAULT_THEME, customTheme: defaults });
setCustomTheme(defaults);
}
@@ -23,6 +24,21 @@ export function useTheme() {
}
}, []);
+ /* Custom Theme reset */
+ function resetCustomToDefault() {
+ const defaultStyles = getThemeStyles(DEFAULT_THEME);
+
+ setCustomTheme(defaultStyles);
+ applyCustomTheme(defaultStyles);
+
+ saveConfig({
+ theme: 'custom',
+ customTheme: defaultStyles,
+ });
+
+ setTheme('custom');
+ }
+
/* Theme switching */
useEffect(() => {
if (theme === 'custom') {
@@ -31,8 +47,6 @@ export function useTheme() {
applyPrebuiltTheme(theme);
}
saveConfig({ theme });
-
- setActiveThemeButton(theme);
}, [theme]);
/* Custom edits */
@@ -48,5 +62,6 @@ export function useTheme() {
setTheme,
customTheme,
updateCustomVar,
+ resetCustomToDefault,
};
}