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 ( -
- {Object.entries(THEME_COLOR_CONFIG).map(([key, cfg]) => { - const Picker = cfg.alpha ? RgbaColorPicker : HexColorPicker; + <> + + +
+ {Object.entries(THEME_COLOR_CONFIG).map(([key, cfg]) => { + const value = customTheme[key]; + + return ( +
+ - return ( -
- - { - updateCustomVar(key, color); - }} - /> -
- ); - })} -
+ {cfg.alpha ? ( + { + const rgbaString = rgbaObjectToString(colorObj); + updateCustomVar(key, rgbaString); + }} + /> + ) : ( + { + updateCustomVar(key, hex); + }} + /> + )} +
+ ); + })} +
+ ); } diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index c2c15725..6d1d1e9f 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -37,7 +37,7 @@ export const SettingsPanel = ({ onToggleDXNews, wakeLockStatus, }) => { - const { theme, setTheme, customTheme, updateCustomVar } = useTheme(); + const { theme, setTheme, customTheme, updateCustomVar, resetCustomToDefault } = useTheme(); const [callsign, setCallsign] = useState(config?.callsign || ''); const [headerSize, setheaderSize] = useState(config?.headerSize || 1.0); @@ -2290,15 +2290,14 @@ export const SettingsPanel = ({ > {t('station.settings.theme')} - -
- {t('station.settings.theme.' + theme + '.describe')} -
+ {theme === 'custom' && customTheme && ( )} diff --git a/src/components/ThemeSelector.jsx b/src/components/ThemeSelector.jsx index 099a1c61..4802eb53 100644 --- a/src/components/ThemeSelector.jsx +++ b/src/components/ThemeSelector.jsx @@ -1,21 +1,108 @@ -import { useEffect } from 'react'; -import { setActiveThemeButton } from '../theme/themeUtils'; import { AVAILABLE_THEMES } from '../theme/themeConfig'; -export default function ThemeSelector({ id, theme, setTheme }) { - useEffect(() => { - setActiveThemeButton(theme); - }, []); - +export default function ThemeSelector({ + id = 'theme-selector-component', + className = 'theme-selector-component', + theme, + setTheme, + selectedTheme, + t, +}) { return ( - <> -
- {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, }; }