kimu-core 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +30 -0
- package/.gitattributes +11 -0
- package/.github/FUNDING.yml +8 -0
- package/.github/copilot-instructions.md +103 -0
- package/.github/kimu-copilot-instructions.md +3779 -0
- package/.github/workflows/deploy-demo.yml +39 -0
- package/AUTHORS.md +20 -0
- package/CHANGELOG.md +20 -0
- package/CODE_GUIDELINES.md +165 -0
- package/CODE_OF_CONDUCT.md +47 -0
- package/CONTRIBUTING.md +62 -0
- package/FUNDING.md +31 -0
- package/ISSUE_GUIDELINES.md +74 -0
- package/LICENSE +17 -0
- package/LICENSE.it.md +17 -0
- package/MPL-2.0.txt +373 -0
- package/NOTICE +65 -0
- package/README-KIMU.md +40 -0
- package/README.it.md +208 -0
- package/README.md +266 -0
- package/SECURITY.md +64 -0
- package/docs/get-started-en.md +207 -0
- package/docs/images/icon.svg +64 -0
- package/docs/images/logo_kimu.png +0 -0
- package/docs/index.md +29 -0
- package/env/dev.config.json +6 -0
- package/env/local.config.json +6 -0
- package/env/prod.config.json +6 -0
- package/env/staging.config.json +6 -0
- package/env/test.config.json +4 -0
- package/icon.svg +10 -0
- package/logo_kimu.png +0 -0
- package/package.json +79 -0
- package/public/favicon.svg +64 -0
- package/public/logo_kimu.svg +1 -0
- package/scripts/build-all-config.js +59 -0
- package/scripts/build-all-core.js +65 -0
- package/scripts/build-all-extensions.js +64 -0
- package/scripts/build-all-modules.js +99 -0
- package/scripts/build-extension.js +60 -0
- package/scripts/clear-kimu-build.js +31 -0
- package/scripts/generate-kimu-build-config.js +79 -0
- package/scripts/install-module.js +162 -0
- package/scripts/list-modules.js +109 -0
- package/scripts/minify-css-assets.js +82 -0
- package/scripts/remove-module.js +122 -0
- package/scripts/utils/fix-imports.js +85 -0
- package/src/assets/index.css +43 -0
- package/src/assets/kimu-style.css +84 -0
- package/src/assets/style.css +116 -0
- package/src/config/kimu-base-config.json +5 -0
- package/src/core/index.ts +47 -0
- package/src/core/kimu-app.ts +76 -0
- package/src/core/kimu-asset-manager.ts +167 -0
- package/src/core/kimu-component-element.ts +325 -0
- package/src/core/kimu-component.ts +33 -0
- package/src/core/kimu-engine.ts +188 -0
- package/src/core/kimu-extension-manager.ts +281 -0
- package/src/core/kimu-global-styles.ts +136 -0
- package/src/core/kimu-module-manager.ts +69 -0
- package/src/core/kimu-module.ts +21 -0
- package/src/core/kimu-path-config.ts +127 -0
- package/src/core/kimu-reactive.ts +196 -0
- package/src/core/kimu-render.ts +91 -0
- package/src/core/kimu-store.ts +147 -0
- package/src/core/kimu-types.ts +65 -0
- package/src/extensions/.gitkeep +0 -0
- package/src/extensions/extensions-manifest.json +13 -0
- package/src/extensions/kimu-home/component.ts +80 -0
- package/src/extensions/kimu-home/lang/en.json +5 -0
- package/src/extensions/kimu-home/lang/it.json +5 -0
- package/src/extensions/kimu-home/style.css +61 -0
- package/src/extensions/kimu-home/view.html +51 -0
- package/src/index.html +26 -0
- package/src/main.ts +68 -0
- package/src/modules/.gitkeep +0 -0
- package/src/modules/README.md +79 -0
- package/src/modules/i18n/README.it.md +63 -0
- package/src/modules/i18n/README.md +63 -0
- package/src/modules/i18n/kimu-global-lang.ts +26 -0
- package/src/modules/i18n/kimu-i18n-service.ts +108 -0
- package/src/modules/i18n/manifest.json +22 -0
- package/src/modules/i18n/module.ts +39 -0
- package/src/modules/modules-manifest.json +12 -0
- package/src/modules-repository/README.md +108 -0
- package/src/modules-repository/api-axios/CHANGELOG.md +48 -0
- package/src/modules-repository/api-axios/QUICK-REFERENCE.md +178 -0
- package/src/modules-repository/api-axios/README.md +304 -0
- package/src/modules-repository/api-axios/api-axios-service.ts +355 -0
- package/src/modules-repository/api-axios/examples.ts +293 -0
- package/src/modules-repository/api-axios/index.ts +19 -0
- package/src/modules-repository/api-axios/interfaces.ts +71 -0
- package/src/modules-repository/api-axios/module.ts +41 -0
- package/src/modules-repository/api-core/CHANGELOG.md +42 -0
- package/src/modules-repository/api-core/QUICK-REFERENCE.md +192 -0
- package/src/modules-repository/api-core/README.md +435 -0
- package/src/modules-repository/api-core/api-core-service.ts +289 -0
- package/src/modules-repository/api-core/examples.ts +432 -0
- package/src/modules-repository/api-core/index.ts +8 -0
- package/src/modules-repository/api-core/interfaces.ts +83 -0
- package/src/modules-repository/api-core/module.ts +30 -0
- package/src/modules-repository/event-bus/README.md +273 -0
- package/src/modules-repository/event-bus/event-bus-service.ts +176 -0
- package/src/modules-repository/event-bus/module.ts +30 -0
- package/src/modules-repository/i18n/README.it.md +63 -0
- package/src/modules-repository/i18n/README.md +63 -0
- package/src/modules-repository/i18n/kimu-global-lang.ts +26 -0
- package/src/modules-repository/i18n/kimu-i18n-service.ts +108 -0
- package/src/modules-repository/i18n/manifest.json +22 -0
- package/src/modules-repository/i18n/module.ts +39 -0
- package/src/modules-repository/notification/README.md +423 -0
- package/src/modules-repository/notification/module.ts +30 -0
- package/src/modules-repository/notification/notification-service.ts +436 -0
- package/src/modules-repository/router/README.it.md +39 -0
- package/src/modules-repository/router/README.md +39 -0
- package/src/modules-repository/router/manifest.json +21 -0
- package/src/modules-repository/router/module.ts +23 -0
- package/src/modules-repository/router/router.ts +144 -0
- package/src/modules-repository/state/README.md +409 -0
- package/src/modules-repository/state/module.ts +30 -0
- package/src/modules-repository/state/state-service.ts +296 -0
- package/src/modules-repository/theme/README.md +267 -0
- package/src/modules-repository/theme/module.ts +30 -0
- package/src/modules-repository/theme/pre-build.js +40 -0
- package/src/modules-repository/theme/theme-service.ts +389 -0
- package/src/modules-repository/theme/themes/theme-cherry-blossom.css +78 -0
- package/src/modules-repository/theme/themes/theme-cozy.css +111 -0
- package/src/modules-repository/theme/themes/theme-cyberpunk.css +150 -0
- package/src/modules-repository/theme/themes/theme-dark.css +79 -0
- package/src/modules-repository/theme/themes/theme-forest.css +171 -0
- package/src/modules-repository/theme/themes/theme-gold.css +100 -0
- package/src/modules-repository/theme/themes/theme-high-contrast.css +126 -0
- package/src/modules-repository/theme/themes/theme-lava.css +101 -0
- package/src/modules-repository/theme/themes/theme-lavender.css +90 -0
- package/src/modules-repository/theme/themes/theme-light.css +79 -0
- package/src/modules-repository/theme/themes/theme-matrix.css +103 -0
- package/src/modules-repository/theme/themes/theme-midnight.css +81 -0
- package/src/modules-repository/theme/themes/theme-nord.css +94 -0
- package/src/modules-repository/theme/themes/theme-ocean.css +84 -0
- package/src/modules-repository/theme/themes/theme-retro80s.css +343 -0
- package/src/modules-repository/theme/themes/theme-sunset.css +62 -0
- package/src/modules-repository/theme/themes-config.d.ts +27 -0
- package/src/modules-repository/theme/themes-config.json +213 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +33 -0
- package/vite.config.ts +99 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Service for KIMU-Core
|
|
3
|
+
*
|
|
4
|
+
* Provides theme management with CSS-based themes, custom CSS variables,
|
|
5
|
+
* persistent storage, and system preference detection.
|
|
6
|
+
*
|
|
7
|
+
* @module ThemeService
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { KimuGlobalStyles } from '../../core/kimu-global-styles';
|
|
12
|
+
import themesConfig from './themes-config.json';
|
|
13
|
+
|
|
14
|
+
export type ThemeMode = 'light' | 'dark' | 'ocean' | 'cozy' | 'auto' | string;
|
|
15
|
+
|
|
16
|
+
export interface ThemeColors {
|
|
17
|
+
background: string;
|
|
18
|
+
backgroundEnd: string;
|
|
19
|
+
text: string;
|
|
20
|
+
border: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CssTheme {
|
|
24
|
+
name: string;
|
|
25
|
+
mode: 'light' | 'dark';
|
|
26
|
+
cssPath: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
icon?: string;
|
|
29
|
+
colors?: ThemeColors;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ThemeChangeListener = (themeName: string, mode: ThemeMode) => void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* ThemeService - Centralized theme management with CSS-based themes
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import { themeService } from './modules/theme/theme-service';
|
|
40
|
+
*
|
|
41
|
+
* // Set theme mode
|
|
42
|
+
* themeService.setMode('dark');
|
|
43
|
+
* themeService.setMode('ocean');
|
|
44
|
+
* themeService.setMode('cozy');
|
|
45
|
+
*
|
|
46
|
+
* // Register a custom CSS theme
|
|
47
|
+
* themeService.registerCssTheme({
|
|
48
|
+
* name: 'custom',
|
|
49
|
+
* mode: 'dark',
|
|
50
|
+
* cssPath: '/themes/custom.css'
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // Listen to theme changes
|
|
54
|
+
* themeService.onChange((themeName, mode) => {
|
|
55
|
+
* console.log('Theme changed:', themeName);
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export class ThemeService {
|
|
60
|
+
private currentMode: ThemeMode = 'auto';
|
|
61
|
+
private currentThemeName: string = 'light';
|
|
62
|
+
private listeners: Set<ThemeChangeListener> = new Set();
|
|
63
|
+
private storageKey = 'kimu-theme-mode';
|
|
64
|
+
private systemPreferenceQuery?: MediaQueryList;
|
|
65
|
+
private cssThemes: Map<string, CssTheme> = new Map();
|
|
66
|
+
private debugMode: boolean = true; // Enable debug by default for development
|
|
67
|
+
private activeThemeLink?: HTMLLinkElement;
|
|
68
|
+
|
|
69
|
+
constructor() {
|
|
70
|
+
console.log('[Theme] ThemeService initializing...');
|
|
71
|
+
this.initDefaultThemes();
|
|
72
|
+
this.loadFromStorage();
|
|
73
|
+
this.initSystemPreference();
|
|
74
|
+
this.applyTheme();
|
|
75
|
+
console.log('[Theme] ThemeService initialized. Current mode:', this.currentMode);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Initialize default themes from configuration file
|
|
80
|
+
*/
|
|
81
|
+
private initDefaultThemes(): void {
|
|
82
|
+
// Theme CSS files are in public/themes/ folder
|
|
83
|
+
// In development: Vite serves public/ from root → /themes/
|
|
84
|
+
// In production: Vite copies public/ to dist/ → /dist/themes/
|
|
85
|
+
const basePath = this.getBasePath();
|
|
86
|
+
|
|
87
|
+
const themesPath = basePath === '/'
|
|
88
|
+
? '/themes/' // Development: public/themes/ → /themes/
|
|
89
|
+
: `${basePath}themes/`; // Production: public/themes/ → /dist/themes/
|
|
90
|
+
|
|
91
|
+
// Load themes from configuration file
|
|
92
|
+
if (themesConfig && themesConfig.themes) {
|
|
93
|
+
themesConfig.themes.forEach(theme => {
|
|
94
|
+
this.cssThemes.set(theme.name, {
|
|
95
|
+
name: theme.name,
|
|
96
|
+
mode: theme.mode as 'light' | 'dark',
|
|
97
|
+
cssPath: `${themesPath}${theme.cssPath}`,
|
|
98
|
+
description: theme.description,
|
|
99
|
+
icon: theme.icon,
|
|
100
|
+
colors: theme.colors
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (this.debugMode) {
|
|
105
|
+
console.log('[Theme] Initialized themes from config with base path:', basePath);
|
|
106
|
+
console.log('[Theme] Themes path:', themesPath);
|
|
107
|
+
console.log('[Theme] Loaded themes:', Array.from(this.cssThemes.keys()));
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
console.warn('[Theme] No themes configuration found, using empty theme list');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get base path for theme files
|
|
116
|
+
*/
|
|
117
|
+
private getBasePath(): string {
|
|
118
|
+
// In development with Vite, files are served from root
|
|
119
|
+
// In production, files are in /dist/
|
|
120
|
+
if (typeof window !== 'undefined') {
|
|
121
|
+
// Check if we're running in production (built files)
|
|
122
|
+
const scripts = document.getElementsByTagName('script');
|
|
123
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
124
|
+
const src = scripts[i].src;
|
|
125
|
+
// If we find a script in /assets/ or with hash, we're in production
|
|
126
|
+
if (src.includes('/assets/') || src.match(/\-[a-zA-Z0-9]{8}\.(js|css)$/)) {
|
|
127
|
+
return '/dist/';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Development mode (Vite serves from root)
|
|
132
|
+
return '/';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Enable or disable debug mode
|
|
137
|
+
*/
|
|
138
|
+
setDebugMode(enabled: boolean): void {
|
|
139
|
+
this.debugMode = enabled;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Set theme mode
|
|
144
|
+
*/
|
|
145
|
+
setMode(mode: ThemeMode, persist: boolean = true): void {
|
|
146
|
+
if (this.debugMode) {
|
|
147
|
+
console.log('[Theme] Setting mode:', mode);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.currentMode = mode;
|
|
151
|
+
|
|
152
|
+
if (persist) {
|
|
153
|
+
this.saveToStorage();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// updateTheme() will handle loading the CSS and notifying listeners
|
|
157
|
+
this.updateTheme();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get current theme mode
|
|
162
|
+
*/
|
|
163
|
+
getMode(): ThemeMode {
|
|
164
|
+
return this.currentMode;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get current theme name
|
|
169
|
+
*/
|
|
170
|
+
getCurrentThemeName(): string {
|
|
171
|
+
return this.currentThemeName;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get effective mode (resolves 'auto' to actual theme)
|
|
176
|
+
*/
|
|
177
|
+
getEffectiveMode(): string {
|
|
178
|
+
if (this.currentMode === 'auto') {
|
|
179
|
+
const systemPref = this.getSystemPreference();
|
|
180
|
+
return systemPref;
|
|
181
|
+
}
|
|
182
|
+
return this.currentMode;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Toggle between light and dark mode
|
|
187
|
+
*/
|
|
188
|
+
toggle(): void {
|
|
189
|
+
const effectiveMode = this.getEffectiveMode();
|
|
190
|
+
const currentTheme = this.cssThemes.get(effectiveMode);
|
|
191
|
+
|
|
192
|
+
if (!currentTheme) {
|
|
193
|
+
this.setMode('light');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Toggle between light and dark modes
|
|
198
|
+
if (currentTheme.mode === 'light') {
|
|
199
|
+
this.setMode('dark');
|
|
200
|
+
} else {
|
|
201
|
+
this.setMode('light');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Register a custom CSS theme
|
|
207
|
+
*/
|
|
208
|
+
registerCssTheme(theme: CssTheme): void {
|
|
209
|
+
this.cssThemes.set(theme.name, theme);
|
|
210
|
+
|
|
211
|
+
if (this.debugMode) {
|
|
212
|
+
console.log('[Theme] Registered CSS theme:', theme.name);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get all available theme names
|
|
218
|
+
*/
|
|
219
|
+
getAvailableThemes(): string[] {
|
|
220
|
+
return Array.from(this.cssThemes.keys());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get theme info by name
|
|
225
|
+
*/
|
|
226
|
+
getThemeInfo(name: string): CssTheme | undefined {
|
|
227
|
+
return this.cssThemes.get(name);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Listen to theme changes
|
|
232
|
+
*/
|
|
233
|
+
onChange(callback: ThemeChangeListener): void {
|
|
234
|
+
this.listeners.add(callback);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Remove theme change listener
|
|
239
|
+
*/
|
|
240
|
+
offChange(callback: ThemeChangeListener): void {
|
|
241
|
+
this.listeners.delete(callback);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Update theme based on current mode
|
|
246
|
+
*/
|
|
247
|
+
private updateTheme(): void {
|
|
248
|
+
const effectiveMode = this.getEffectiveMode();
|
|
249
|
+
const theme = this.cssThemes.get(effectiveMode);
|
|
250
|
+
|
|
251
|
+
if (!theme) {
|
|
252
|
+
console.error(`[Theme] Theme "${effectiveMode}" not found`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.currentThemeName = theme.name;
|
|
257
|
+
this.loadCssTheme(theme);
|
|
258
|
+
|
|
259
|
+
// Notify listeners after theme is loaded
|
|
260
|
+
this.notifyListeners();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Load CSS theme file
|
|
265
|
+
*/
|
|
266
|
+
private loadCssTheme(theme: CssTheme): void {
|
|
267
|
+
// Remove existing theme link from main document
|
|
268
|
+
if (this.activeThemeLink) {
|
|
269
|
+
this.activeThemeLink.remove();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Create new link element for main document
|
|
273
|
+
const link = document.createElement('link');
|
|
274
|
+
link.rel = 'stylesheet';
|
|
275
|
+
link.href = theme.cssPath;
|
|
276
|
+
link.id = 'kimu-theme';
|
|
277
|
+
link.setAttribute('data-theme', theme.name);
|
|
278
|
+
|
|
279
|
+
// Wait for CSS to load before applying (optional, for smoother transitions)
|
|
280
|
+
link.onload = () => {
|
|
281
|
+
if (this.debugMode) {
|
|
282
|
+
console.log('[Theme] CSS theme loaded:', theme.name, theme.cssPath);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
link.onerror = () => {
|
|
287
|
+
console.error('[Theme] Failed to load CSS theme:', theme.name, theme.cssPath);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Add to document head
|
|
291
|
+
document.head.appendChild(link);
|
|
292
|
+
this.activeThemeLink = link;
|
|
293
|
+
|
|
294
|
+
// Register theme CSS as global style for all extensions
|
|
295
|
+
// This will make the theme available in all shadow roots
|
|
296
|
+
KimuGlobalStyles.registerGlobalStyle('kimu-theme-active', theme.cssPath);
|
|
297
|
+
|
|
298
|
+
// Update theme in all currently loaded extensions
|
|
299
|
+
// This ensures that existing extensions get the new theme immediately
|
|
300
|
+
KimuGlobalStyles.updateStyleInAllExtensions('kimu-theme-active').catch(error => {
|
|
301
|
+
console.error('[Theme] Failed to update theme in extensions:', error);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (this.debugMode) {
|
|
305
|
+
console.log('[Theme] Loading CSS theme:', theme.name, theme.cssPath);
|
|
306
|
+
console.log('[Theme] Registered theme as global style for all extensions');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Apply current theme
|
|
312
|
+
*/
|
|
313
|
+
private applyTheme(): void {
|
|
314
|
+
this.updateTheme();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Initialize system preference detection
|
|
319
|
+
*/
|
|
320
|
+
private initSystemPreference(): void {
|
|
321
|
+
if (typeof window === 'undefined' || !window.matchMedia) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.systemPreferenceQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
326
|
+
|
|
327
|
+
// Listen for system preference changes
|
|
328
|
+
this.systemPreferenceQuery.addEventListener('change', (e) => {
|
|
329
|
+
if (this.currentMode === 'auto') {
|
|
330
|
+
if (this.debugMode) {
|
|
331
|
+
console.log('[Theme] System preference changed:', e.matches ? 'dark' : 'light');
|
|
332
|
+
}
|
|
333
|
+
this.updateTheme();
|
|
334
|
+
this.notifyListeners();
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get system color scheme preference
|
|
341
|
+
*/
|
|
342
|
+
private getSystemPreference(): 'light' | 'dark' {
|
|
343
|
+
if (!this.systemPreferenceQuery) {
|
|
344
|
+
return 'light';
|
|
345
|
+
}
|
|
346
|
+
return this.systemPreferenceQuery.matches ? 'dark' : 'light';
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Save theme mode to localStorage
|
|
351
|
+
*/
|
|
352
|
+
private saveToStorage(): void {
|
|
353
|
+
try {
|
|
354
|
+
localStorage.setItem(this.storageKey, this.currentMode);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.error('[Theme] Failed to save to localStorage:', error);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Load theme mode from localStorage
|
|
362
|
+
*/
|
|
363
|
+
private loadFromStorage(): void {
|
|
364
|
+
try {
|
|
365
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
366
|
+
if (stored) {
|
|
367
|
+
this.currentMode = stored as ThemeMode;
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error('[Theme] Failed to load from localStorage:', error);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Notify all listeners of theme change
|
|
376
|
+
*/
|
|
377
|
+
private notifyListeners(): void {
|
|
378
|
+
this.listeners.forEach(listener => {
|
|
379
|
+
try {
|
|
380
|
+
listener(this.currentThemeName, this.currentMode);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.error('[Theme] Error in listener:', error);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Export singleton instance
|
|
389
|
+
export const themeService = new ThemeService();
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cherry Blossom Theme - Delicate pink sakura colors
|
|
3
|
+
* A soft and elegant theme inspired by Japanese cherry blossoms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
:root {
|
|
7
|
+
/* Main colors */
|
|
8
|
+
--kimu-background: linear-gradient(135deg, #FFE5EC 0%, #FFD6E0 50%, #FFC2D4 100%);
|
|
9
|
+
--kimu-surface: rgba(255, 194, 212, 0.2);
|
|
10
|
+
--kimu-surface-elevated: rgba(255, 194, 212, 0.3);
|
|
11
|
+
|
|
12
|
+
/* Text colors */
|
|
13
|
+
--kimu-text-primary: #6B2E4F;
|
|
14
|
+
--kimu-text-secondary: #8B4A6F;
|
|
15
|
+
--kimu-text-tertiary: #A86B8F;
|
|
16
|
+
--kimu-text-disabled: #C28BAF;
|
|
17
|
+
|
|
18
|
+
/* Border colors */
|
|
19
|
+
--kimu-border: #FFB3C6;
|
|
20
|
+
--kimu-border-light: #FFCCD9;
|
|
21
|
+
--kimu-divider: rgba(255, 179, 198, 0.3);
|
|
22
|
+
|
|
23
|
+
/* Accent colors */
|
|
24
|
+
--kimu-primary: #FF9EBB;
|
|
25
|
+
--kimu-primary-dark: #FF85AA;
|
|
26
|
+
--kimu-primary-light: #FFB3C6;
|
|
27
|
+
--kimu-secondary: #FFAAC2;
|
|
28
|
+
--kimu-accent: #FFC2D4;
|
|
29
|
+
|
|
30
|
+
/* State colors */
|
|
31
|
+
--kimu-success: #98D8C8;
|
|
32
|
+
--kimu-warning: #FFD93D;
|
|
33
|
+
--kimu-error: #FF6B9D;
|
|
34
|
+
--kimu-info: #C8A2D0;
|
|
35
|
+
|
|
36
|
+
/* Shadow */
|
|
37
|
+
--kimu-shadow: rgba(255, 158, 187, 0.3);
|
|
38
|
+
--kimu-shadow-strong: rgba(255, 158, 187, 0.5);
|
|
39
|
+
|
|
40
|
+
/* Component colors */
|
|
41
|
+
--kimu-input-bg: #FFFFFF;
|
|
42
|
+
--kimu-input-border: #FFB3C6;
|
|
43
|
+
--kimu-button-bg: #FF9EBB;
|
|
44
|
+
--kimu-button-hover: #FF85AA;
|
|
45
|
+
--kimu-card-bg: rgba(255, 255, 255, 0.9);
|
|
46
|
+
|
|
47
|
+
/* Font */
|
|
48
|
+
--kimu-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Body background with gradient */
|
|
52
|
+
body {
|
|
53
|
+
background: linear-gradient(135deg, #FFE5EC 0%, #FFD6E0 50%, #FFC2D4 100%);
|
|
54
|
+
background-attachment: fixed;
|
|
55
|
+
color: var(--kimu-text-primary);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Petal animation effect */
|
|
59
|
+
@keyframes petal-fall {
|
|
60
|
+
0% {
|
|
61
|
+
transform: translateY(-10px) rotate(0deg);
|
|
62
|
+
opacity: 0;
|
|
63
|
+
}
|
|
64
|
+
50% {
|
|
65
|
+
opacity: 0.5;
|
|
66
|
+
}
|
|
67
|
+
100% {
|
|
68
|
+
transform: translateY(100vh) rotate(360deg);
|
|
69
|
+
opacity: 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Soft glow on hover */
|
|
74
|
+
button:hover,
|
|
75
|
+
a:hover {
|
|
76
|
+
box-shadow: 0 0 15px rgba(255, 158, 187, 0.6);
|
|
77
|
+
transition: all 0.3s ease;
|
|
78
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KIMU Cozy Theme
|
|
3
|
+
* Warm, feminine and cozy theme with soft colors
|
|
4
|
+
*
|
|
5
|
+
* This file OVERRIDES the default CSS variables defined in style.css
|
|
6
|
+
* When this theme is loaded, these values take precedence.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
/* Primary colors - Soft pinks and purples */
|
|
11
|
+
--kimu-primary: #ec4899;
|
|
12
|
+
--kimu-primary-light: #f472b6;
|
|
13
|
+
--kimu-primary-dark: #db2777;
|
|
14
|
+
--kimu-text-on-primary: #ffffff;
|
|
15
|
+
|
|
16
|
+
/* Accent colors - Warm coral/peach */
|
|
17
|
+
--kimu-accent: #fb923c;
|
|
18
|
+
--kimu-accent-light: #fdba74;
|
|
19
|
+
--kimu-accent-dark: #f97316;
|
|
20
|
+
--kimu-text-on-accent: #ffffff;
|
|
21
|
+
|
|
22
|
+
/* Background colors - Soft creams and beiges */
|
|
23
|
+
--kimu-background: #fef3f2;
|
|
24
|
+
--kimu-background-alt: #fff1f0;
|
|
25
|
+
--kimu-surface: #ffffff;
|
|
26
|
+
|
|
27
|
+
/* Text colors - Warm darks */
|
|
28
|
+
--kimu-text-primary: #431407;
|
|
29
|
+
--kimu-text-secondary: #78350f;
|
|
30
|
+
--kimu-text-disabled: #d97706;
|
|
31
|
+
|
|
32
|
+
/* Semantic colors */
|
|
33
|
+
--kimu-success: #86efac;
|
|
34
|
+
--kimu-success-light: #bbf7d0;
|
|
35
|
+
--kimu-success-dark: #4ade80;
|
|
36
|
+
|
|
37
|
+
--kimu-warning: #fbbf24;
|
|
38
|
+
--kimu-warning-light: #fde047;
|
|
39
|
+
--kimu-warning-dark: #f59e0b;
|
|
40
|
+
|
|
41
|
+
--kimu-error: #f87171;
|
|
42
|
+
--kimu-error-light: #fca5a5;
|
|
43
|
+
--kimu-error-dark: #ef4444;
|
|
44
|
+
|
|
45
|
+
--kimu-info: #93c5fd;
|
|
46
|
+
--kimu-info-light: #bfdbfe;
|
|
47
|
+
--kimu-info-dark: #60a5fa;
|
|
48
|
+
|
|
49
|
+
/* Borders and dividers */
|
|
50
|
+
--kimu-border: #fed7d7;
|
|
51
|
+
--kimu-divider: #fecaca;
|
|
52
|
+
|
|
53
|
+
/* Shadows - Soft pink tint */
|
|
54
|
+
--kimu-shadow: rgba(236, 72, 153, 0.1);
|
|
55
|
+
--kimu-shadow-light: rgba(236, 72, 153, 0.05);
|
|
56
|
+
--kimu-shadow-medium: rgba(236, 72, 153, 0.15);
|
|
57
|
+
--kimu-shadow-heavy: rgba(236, 72, 153, 0.25);
|
|
58
|
+
|
|
59
|
+
/* Additional UI colors */
|
|
60
|
+
--kimu-card-bg: #fffbeb;
|
|
61
|
+
--kimu-card-border: #fed7aa;
|
|
62
|
+
--kimu-input-bg: #ffffff;
|
|
63
|
+
--kimu-input-border: #fecaca;
|
|
64
|
+
--kimu-input-focus: #ec4899;
|
|
65
|
+
|
|
66
|
+
/* Navigation */
|
|
67
|
+
--kimu-nav-bg: #fef3c7;
|
|
68
|
+
--kimu-nav-text: #78350f;
|
|
69
|
+
--kimu-nav-active: #ec4899;
|
|
70
|
+
|
|
71
|
+
/* Footer */
|
|
72
|
+
--kimu-footer-bg: #fff7ed;
|
|
73
|
+
--kimu-footer-text: #c2410c;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Smooth transitions for theme changes */
|
|
77
|
+
* {
|
|
78
|
+
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Cozy gradient background */
|
|
82
|
+
body {
|
|
83
|
+
background: linear-gradient(135deg, #fef3f2 0%, #fff1f0 50%, #fffbeb 100%);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Soft rounded corners for cozy feel */
|
|
87
|
+
.kimu-card,
|
|
88
|
+
.kimu-button,
|
|
89
|
+
.kimu-input {
|
|
90
|
+
border-radius: 16px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Subtle texture overlay (optional) */
|
|
94
|
+
body::before {
|
|
95
|
+
content: '';
|
|
96
|
+
position: fixed;
|
|
97
|
+
top: 0;
|
|
98
|
+
left: 0;
|
|
99
|
+
right: 0;
|
|
100
|
+
bottom: 0;
|
|
101
|
+
background-image:
|
|
102
|
+
repeating-linear-gradient(
|
|
103
|
+
45deg,
|
|
104
|
+
transparent,
|
|
105
|
+
transparent 10px,
|
|
106
|
+
rgba(255, 255, 255, 0.03) 10px,
|
|
107
|
+
rgba(255, 255, 255, 0.03) 20px
|
|
108
|
+
);
|
|
109
|
+
pointer-events: none;
|
|
110
|
+
z-index: -1;
|
|
111
|
+
}
|