// localStorage-backed settings store for the standalone Web-Host.
//
// Holds a mutable settings object in memory and persists it on
// saveSettings(). The plugin-shim adapts this onto a Plugin-shaped object
// for the engine controller (see plugin-shim.ts).
import type { ScreensaverSettings } from '../engine/data/defaults';
import { DEFAULT_SCREENSAVER } from '../engine/data/defaults';
const STORAGE_KEY = 'kuro-screensaver:settings';
const LEGACY_KEY = 'kuro-animation:settings'; // pre-rename (commit dcf4b7f) — migrated on load/save
export function deepMerge<T extends object>(base: T, overrides: any): T {
if (!overrides || typeof overrides !== 'object') return base;
const out: any = Array.isArray(base) ? [...(base as any)] : { ...base };
for (const k of Object.keys(overrides)) {
const bv = (base as any)[k];
const ov = overrides[k];
if (bv && typeof bv === 'object' && !Array.isArray(bv) && ov && typeof ov === 'object' && !Array.isArray(ov)) {
out[k] = deepMerge(bv, ov);
} else {
out[k] = ov;
}
}
return out as T;
}
function loadFromStorage(): ScreensaverSettings {
try {
const raw = localStorage.getItem(STORAGE_KEY) ?? localStorage.getItem(LEGACY_KEY);
if (!raw) return structuredClone(DEFAULT_SCREENSAVER);
const parsed = JSON.parse(raw);
// Merge user overrides over defaults so newly-added settings always get a value.
return deepMerge(structuredClone(DEFAULT_SCREENSAVER), parsed);
} catch {
return structuredClone(DEFAULT_SCREENSAVER);
}
}
export interface WebHostOptions {
vaultKanji?: string; // e.g. '黒' for the HUD
activePreset?: string; // for colorMode 'kuro-auto'
overrides?: Partial<ScreensaverSettings>; // applied once at startup
}
export class WebHost {
private settings: ScreensaverSettings;
constructor(private options: WebHostOptions = {}) {
this.settings = loadFromStorage();
if (options.overrides) {
this.settings = deepMerge(this.settings, options.overrides);
}
}
getSettings(): ScreensaverSettings {
return this.settings;
}
saveSettings(settings: ScreensaverSettings): void {
this.settings = settings;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
localStorage.removeItem(LEGACY_KEY); // one-time migration off the old project name
} catch {
// Quota exceeded or storage disabled — fail silently. Stats are non-critical.
}
}
getActivePreset(): string | undefined {
return this.options.activePreset;
}
getVaultKanji(): string {
return this.options.vaultKanji ?? '';
}
}