adds color themeing support to the site
This commit is contained in:
1 parent
bfd6c5bb9e
commit
ee79662e5d
37 files changed
+1046
-15
No files matched your search
@@ -4,6 +4,7 @@ import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { StationConfig } from '../interfaces/station-config';
|
||||
import { getAppConfig } from './app-config.service';
|
||||
import { ThemeService } from './theme.service';
|
||||
|
||||
/** Hardcoded defaults — used when the API is unavailable (dev fallback). */
|
||||
const DEFAULT_CONFIG: StationConfig = {
|
||||
@@ -31,6 +32,20 @@ const DEFAULT_CONFIG: StationConfig = {
|
||||
play_store_icon_url: '',
|
||||
play_store_url: '',
|
||||
app_store_embed_html: '',
|
||||
// Color theme defaults (match _variables.scss)
|
||||
color_primary: '#1a3a5c',
|
||||
color_primary_light: '#2a5a8c',
|
||||
color_primary_dark: '#0e2440',
|
||||
color_primary_muted: '#3a6a9c',
|
||||
color_accent: '#e87a2e',
|
||||
color_accent_light: '#f09a4e',
|
||||
color_accent_dark: '#c85a1e',
|
||||
color_accent_muted: '#f0a86a',
|
||||
color_background: '#faf6f0',
|
||||
color_text: '#3a3632',
|
||||
color_success: '#4a9e4f',
|
||||
color_danger: '#c83030',
|
||||
color_info: '#3a8abf',
|
||||
};
|
||||
|
||||
@Injectable({
|
||||
@@ -38,6 +53,7 @@ const DEFAULT_CONFIG: StationConfig = {
|
||||
})
|
||||
export class StationConfigService {
|
||||
private http = inject(HttpClient);
|
||||
private themeService = inject(ThemeService);
|
||||
private baseUrl = `${getAppConfig().apiBaseUrl}/api/station-config`;
|
||||
|
||||
readonly config = signal<StationConfig>(DEFAULT_CONFIG);
|
||||
@@ -47,6 +63,7 @@ export class StationConfigService {
|
||||
try {
|
||||
const data = await firstValueFrom(this.http.get<StationConfig>(this.baseUrl));
|
||||
this.config.set(data);
|
||||
this.themeService.apply(data);
|
||||
} catch {
|
||||
// Keep the default config if the API is unavailable.
|
||||
console.warn('StationConfigService: Failed to load config, using defaults.');
|
||||
@@ -59,6 +76,7 @@ export class StationConfigService {
|
||||
this.http.put<StationConfig>(this.baseUrl, payload),
|
||||
);
|
||||
this.config.set(data);
|
||||
this.themeService.apply(data);
|
||||
}
|
||||
|
||||
/** Upload an image file for station branding (admin only). Returns the relative URL. */
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { StationConfig } from '../interfaces/station-config';
|
||||
|
||||
/** CSS custom property names mapped from StationConfig color fields. */
|
||||
const COLOR_MAP: ReadonlyArray<{ field: keyof StationConfig; prop: string }> = [
|
||||
{ field: 'color_primary', prop: '--color-primary' },
|
||||
{ field: 'color_primary_light', prop: '--color-primary-light' },
|
||||
{ field: 'color_primary_dark', prop: '--color-primary-dark' },
|
||||
{ field: 'color_primary_muted', prop: '--color-primary-muted' },
|
||||
{ field: 'color_accent', prop: '--color-accent' },
|
||||
{ field: 'color_accent_light', prop: '--color-accent-light' },
|
||||
{ field: 'color_accent_dark', prop: '--color-accent-dark' },
|
||||
{ field: 'color_accent_muted', prop: '--color-accent-muted' },
|
||||
{ field: 'color_background', prop: '--color-cream' },
|
||||
{ field: 'color_text', prop: '--color-dark' },
|
||||
{ field: 'color_success', prop: '--color-success' },
|
||||
{ field: 'color_danger', prop: '--color-danger' },
|
||||
{ field: 'color_info', prop: '--color-info' },
|
||||
];
|
||||
|
||||
/** Build a gradient CSS value from three color stops. */
|
||||
function gradient(from: string, mid: string, to: string): string {
|
||||
return `linear-gradient(135deg, ${from} 0%, ${mid} 40%, ${to} 100%)`;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ThemeService {
|
||||
/**
|
||||
* Apply color theme from StationConfig as CSS custom properties on `:root`.
|
||||
* Also generates derived gradient variables.
|
||||
*/
|
||||
apply(config: StationConfig): void {
|
||||
const root = document.documentElement.style;
|
||||
|
||||
for (const { field, prop } of COLOR_MAP) {
|
||||
const value = (config as any)[field];
|
||||
if (value) {
|
||||
root.setProperty(prop, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Derived gradients
|
||||
root.setProperty(
|
||||
'--sky-gradient',
|
||||
gradient(
|
||||
config.color_primary_dark,
|
||||
config.color_primary,
|
||||
config.color_primary_light,
|
||||
),
|
||||
);
|
||||
root.setProperty(
|
||||
'--sunset-gradient',
|
||||
gradient(
|
||||
config.color_accent_dark,
|
||||
config.color_accent,
|
||||
config.color_accent_light,
|
||||
),
|
||||
);
|
||||
root.setProperty(
|
||||
'--mixed-gradient',
|
||||
`linear-gradient(135deg, ${config.color_primary_dark} 0%, ${config.color_primary} 50%, ${config.color_accent} 100%)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user