diff --git a/package-lock.json b/package-lock.json index 10c58e0..d015fbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,10 @@ "": { "name": "chakmate", "version": "1.0.0", + "dependencies": { + "@fontsource/noto-sans-kr": "^5.2.9", + "@fontsource/outfit": "^5.2.8" + }, "devDependencies": { "@tauri-apps/cli": "^2.0.0", "autoprefixer": "^10.4.20", @@ -395,6 +399,22 @@ "node": ">=12" } }, + "node_modules/@fontsource/noto-sans-kr": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-5.2.9.tgz", + "integrity": "sha512-pbF8F0rUzKkDmnk/KYGAbxjgW4TrIHdFKnV6OQ+kUC9ELCn5utrAURcEgMqq3J9SRX+FvjOz+aBCmfcC8/r7cw==", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/outfit": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/outfit/-/outfit-5.2.8.tgz", + "integrity": "sha512-rXC6g0MqD7cOBjht0bMqc43qM6lRqDLML9KXsmg9uykz0wLQhy8Z/ajrMG6iyoT3NJR+MYgU+OEHp7uHoTb+Yw==", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", diff --git a/package.json b/package.json index fa7a043..47d3488 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "preview": "vite preview", "tauri": "tauri" }, - "dependencies": {}, + "dependencies": { + "@fontsource/noto-sans-kr": "^5.2.9", + "@fontsource/outfit": "^5.2.8" + }, "devDependencies": { "@tauri-apps/cli": "^2.0.0", "autoprefixer": "^10.4.20", @@ -17,4 +20,4 @@ "tailwindcss": "^3.4.17", "vite": "^5.4.0" } -} \ No newline at end of file +} diff --git a/src/assets/icons.svg b/src/assets/icons.svg deleted file mode 100644 index c30460f..0000000 --- a/src/assets/icons.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/logo.svg b/src/assets/logo.svg similarity index 100% rename from assets/logo.svg rename to src/assets/logo.svg diff --git a/src/index.html b/src/index.html.bak similarity index 98% rename from src/index.html rename to src/index.html.bak index f9de983..d32e692 100644 --- a/src/index.html +++ b/src/index.html.bak @@ -4,9 +4,6 @@ Chakmate - 지능형 디지털 자산 관리 - - - @@ -455,6 +452,6 @@ 7일 스트릭 달성! - + \ No newline at end of file diff --git a/src/pages/scene_ai_classification.html b/src/pages/scene_ai_classification.html index 9734f3e..f44931d 100644 --- a/src/pages/scene_ai_classification.html +++ b/src/pages/scene_ai_classification.html @@ -4,9 +4,6 @@ AI 분류 - Chakmate - - -
- + 대시보드로 diff --git a/src/pages/scene_dashboard.html b/src/pages/scene_dashboard.html index 089f97c..13840de 100644 --- a/src/pages/scene_dashboard.html +++ b/src/pages/scene_dashboard.html @@ -4,9 +4,6 @@ 대시보드 - Chakmate - - - @@ -100,21 +97,22 @@ + \ No newline at end of file diff --git a/src/pages/scene_gamification.html b/src/pages/scene_gamification.html index 4686db0..76762f9 100644 --- a/src/pages/scene_gamification.html +++ b/src/pages/scene_gamification.html @@ -300,6 +300,7 @@
+ \ No newline at end of file diff --git a/src/pages/scene_onboarding.html b/src/pages/scene_onboarding.html index 057d3e1..bee381d 100644 --- a/src/pages/scene_onboarding.html +++ b/src/pages/scene_onboarding.html @@ -4,9 +4,6 @@ Chakmate - 지능형 디지털 자산 관리 - - - diff --git a/src/pages/scene_settings.html b/src/pages/scene_settings.html index 40c062d..88099c3 100644 --- a/src/pages/scene_settings.html +++ b/src/pages/scene_settings.html @@ -4,9 +4,6 @@ 설정 - Chakmate - - - @@ -34,10 +31,10 @@
- +

설정

@@ -121,6 +118,7 @@
+ \ No newline at end of file diff --git a/src/pages/scene_swipe.html b/src/pages/scene_swipe.html index 65ce3bd..7db6e36 100644 --- a/src/pages/scene_swipe.html +++ b/src/pages/scene_swipe.html @@ -4,9 +4,6 @@ 스와이프 모드 - Chakmate - - - @@ -27,10 +24,10 @@
- +

스와이프 모드

+
diff --git a/src/public/logo.svg b/src/public/logo.svg new file mode 100644 index 0000000..380967a --- /dev/null +++ b/src/public/logo.svg @@ -0,0 +1,56 @@ + + + + + Puzzle CI Logo + + + + + diff --git a/src/scripts/dashboard.js b/src/scripts/dashboard.js index 27cc525..1591faa 100644 --- a/src/scripts/dashboard.js +++ b/src/scripts/dashboard.js @@ -1,8 +1,8 @@ document.addEventListener('DOMContentLoaded', () => { const state = { - streak: parseInt(localStorage.getItem('chackly_streak') || '7'), - filesOrganized: parseInt(localStorage.getItem('chackly_files_organized') || '248'), - foldersManaged: parseInt(localStorage.getItem('chackly_folders') || '12') + streak: AppState.getStreak(), + filesOrganized: AppState.getFilesOrganized(), + foldersManaged: AppState.getFoldersManaged() }; const suggestions = [ diff --git a/src/scripts/gamification.js b/src/scripts/gamification.js index 391e8f8..34d7a70 100644 --- a/src/scripts/gamification.js +++ b/src/scripts/gamification.js @@ -1,36 +1,5 @@ document.addEventListener('DOMContentLoaded', () => { - // Data Management - const STORAGE_KEY = 'chackly_gamification'; - - const defaultData = { - streak: 12, - weeklyProgress: [true, true, true, true, false, false, false], - achievements: [ - { id: 'first_sort', name: '첫 정리', icon: '🏆', unlocked: true, tier: 'gold', requirement: '첫 정리 세션 완료' }, - { id: 'week_warrior', name: '주간 챌린저', icon: '🌟', unlocked: true, tier: 'gold', requirement: '7일 연속 완료' }, - { id: 'streak_7', name: '7일 스트릭', icon: '🔥', unlocked: true, tier: 'silver', requirement: '7일 스트릭 유지' }, - { id: 'diamond', name: '다이아몬드', icon: '💎', unlocked: true, tier: 'gold', requirement: '30일 스트릭 유지' }, - { id: 'organizer', name: '정리 달인', icon: '📦', unlocked: false, tier: 'silver', requirement: '100개 파일 정리' }, - { id: 'minimalist', name: '미니멀리스트', icon: '✨', unlocked: false, tier: 'bronze', requirement: '50개 파일 삭제' }, - { id: 'speed_demon', name: '스피드 데몬', icon: '⚡', unlocked: false, tier: 'gold', requirement: '하루에 50개 파일 정리' }, - { id: 'collector', name: '수집가', icon: '🗂️', unlocked: false, tier: 'silver', requirement: '5개 커스텀 폴더 생성' } - ], - habitReminderEnabled: true, - lastUpdated: new Date().toISOString() - }; - - function loadData() { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) { - return { ...defaultData, ...JSON.parse(stored) }; - } - return defaultData; - } - - function saveData(data) { - data.lastUpdated = new Date().toISOString(); - localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); - } + let gamificationData = AppState.getGamificationData(); // UI Elements const streakCount = document.getElementById('streakCount'); @@ -173,37 +142,37 @@ document.addEventListener('DOMContentLoaded', () => { // Initialize page function init() { - const data = loadData(); + gamificationData = AppState.getGamificationData(); // Animate streak count - animateCount(streakCount, data.streak); + animateCount(streakCount, gamificationData.streak); // Streak icon animation - if (data.streak > 0) { + if (gamificationData.streak > 0) { streakIcon.classList.add('animate'); setTimeout(() => streakIcon.classList.remove('animate'), 1000); } // Update streak message - if (data.streak > 0) { + if (gamificationData.streak > 0) { streakMessage.textContent = motivationalMessages[Math.floor(Math.random() * motivationalMessages.length)]; } // Animate progress bar - const completedDays = data.weeklyProgress.filter(Boolean).length; + const completedDays = gamificationData.weeklyProgress.filter(Boolean).length; setTimeout(() => { progressFill.style.width = (completedDays / 7 * 100) + '%'; }, 300); daysCompleted.textContent = completedDays; // Render week grid - renderWeekGrid(data.weeklyProgress); + renderWeekGrid(gamificationData.weeklyProgress); // Render achievements - renderAchievements(data.achievements); + renderAchievements(gamificationData.achievements); // Set habit toggle state - habitToggle.checked = data.habitReminderEnabled; + habitToggle.checked = gamificationData.habitReminderEnabled; // Random daily tip const randomTip = dailyTips[Math.floor(Math.random() * dailyTips.length)]; @@ -212,8 +181,8 @@ document.addEventListener('DOMContentLoaded', () => { // Habit toggle handler habitToggle.addEventListener('change', (e) => { - data.habitReminderEnabled = e.target.checked; - saveData(data); + gamificationData.habitReminderEnabled = e.target.checked; + AppState.setGamificationData(gamificationData); showToast(e.target.checked ? '🔔' : '🔕', e.target.checked ? 'Reminders enabled!' : 'Reminders disabled'); }); @@ -223,14 +192,14 @@ document.addEventListener('DOMContentLoaded', () => { if (achievement && !achievement.classList.contains('unlocked')) { // Simulate unlock for demo const achId = achievement.dataset.id; - const achData = data.achievements.find(a => a.id === achId); + const achData = gamificationData.achievements.find(a => a.id === achId); if (achData) { achData.unlocked = true; - saveData(data); + AppState.setGamificationData(gamificationData); animateBadgeUnlock(achId); triggerConfetti(); showToast(achData.icon, `${achData.name} unlocked!`); - renderAchievements(data.achievements); + renderAchievements(gamificationData.achievements); } } }); diff --git a/src/scripts/settings.js b/src/scripts/settings.js index 922f64d..9713d29 100644 --- a/src/scripts/settings.js +++ b/src/scripts/settings.js @@ -1,7 +1,7 @@ document.addEventListener('DOMContentLoaded', () => { const state = { - theme: localStorage.getItem('chackly_theme') || 'light', - settings: JSON.parse(localStorage.getItem('chackly_settings') || '{"notifications":true,"habits":false,"autoOrganize":false,"confirmDelete":true}') + theme: AppState.getTheme(), + settings: AppState.getSettings() }; function initToggles() { @@ -24,9 +24,8 @@ document.addEventListener('DOMContentLoaded', () => { }); option.classList.remove('border-transparent'); option.classList.add('border-primary'); + AppState.setTheme(option.dataset.theme); state.theme = option.dataset.theme; - localStorage.setItem('chackly_theme', state.theme); - document.documentElement.setAttribute('data-theme', state.theme); }); }); @@ -44,7 +43,7 @@ document.addEventListener('DOMContentLoaded', () => { autoOrganize: document.getElementById('toggle-autoOrganize')?.classList.contains('active'), confirmDelete: document.getElementById('toggle-confirmDelete')?.classList.contains('active') }; - localStorage.setItem('chackly_settings', JSON.stringify(settings)); + AppState.setSettings(settings); } document.getElementById('export-data')?.addEventListener('click', () => { diff --git a/src/scripts/shared/state.js b/src/scripts/shared/state.js new file mode 100644 index 0000000..060e8e0 --- /dev/null +++ b/src/scripts/shared/state.js @@ -0,0 +1,106 @@ +/** + * AppState - Shared state management module + * Handles all localStorage interactions for the Chakmate app + */ + +const AppState = { + // ============ Theme ============ + getTheme() { + return localStorage.getItem('chackly_theme') || 'light'; + }, + + setTheme(theme) { + localStorage.setItem('chackly_theme', theme); + document.documentElement.setAttribute('data-theme', theme); + }, + + // ============ Settings ============ + getSettings() { + const defaults = { + notifications: true, + habits: false, + autoOrganize: false, + confirmDelete: true + }; + try { + return { ...defaults, ...JSON.parse(localStorage.getItem('chackly_settings') || '{}') }; + } catch { + return defaults; + } + }, + + setSettings(settings) { + localStorage.setItem('chackly_settings', JSON.stringify(settings)); + }, + + // ============ Gamification ============ + getGamificationData() { + const defaultData = { + streak: 12, + weeklyProgress: [true, true, true, true, false, false, false], + achievements: [ + { id: 'first_sort', name: '첫 정리', icon: '🏆', unlocked: true, tier: 'gold', requirement: '첫 정리 세션 완료' }, + { id: 'week_warrior', name: '주간 챌린저', icon: '🌟', unlocked: true, tier: 'gold', requirement: '7일 연속 완료' }, + { id: 'streak_7', name: '7일 스트릭', icon: '🔥', unlocked: true, tier: 'silver', requirement: '7일 스트릭 유지' }, + { id: 'diamond', name: '다이아몬드', icon: '💎', unlocked: true, tier: 'gold', requirement: '30일 스트릭 유지' }, + { id: 'organizer', name: '정리 달인', icon: '📦', unlocked: false, tier: 'silver', requirement: '100개 파일 정리' }, + { id: 'minimalist', name: '미니멀리스트', icon: '✨', unlocked: false, tier: 'bronze', requirement: '50개 파일 삭제' }, + { id: 'speed_demon', name: '스피드 데몬', icon: '⚡', unlocked: false, tier: 'gold', requirement: '하루에 50개 파일 정리' }, + { id: 'collector', name: '수집가', icon: '🗂️', unlocked: false, tier: 'silver', requirement: '5개 커스텀 폴더 생성' } + ], + habitReminderEnabled: true, + lastUpdated: new Date().toISOString() + }; + try { + const stored = localStorage.getItem('chackly_gamification'); + if (stored) { + return { ...defaultData, ...JSON.parse(stored) }; + } + } catch { + // ignore + } + return defaultData; + }, + + setGamificationData(data) { + data.lastUpdated = new Date().toISOString(); + localStorage.setItem('chackly_gamification', JSON.stringify(data)); + }, + + // ============ Dashboard Stats ============ + getStreak() { + return parseInt(localStorage.getItem('chackly_streak') || '7'); + }, + + setStreak(count) { + localStorage.setItem('chackly_streak', count.toString()); + }, + + getFilesOrganized() { + return parseInt(localStorage.getItem('chackly_files_organized') || '248'); + }, + + setFilesOrganized(count) { + localStorage.setItem('chackly_files_organized', count.toString()); + }, + + getFoldersManaged() { + return parseInt(localStorage.getItem('chackly_folders') || '12'); + }, + + setFoldersManaged(count) { + localStorage.setItem('chackly_folders', count.toString()); + }, + + // ============ Initialization ============ + init() { + // Apply saved theme on page load + const theme = this.getTheme(); + document.documentElement.setAttribute('data-theme', theme); + } +}; + +// Auto-initialize when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + AppState.init(); +}); \ No newline at end of file diff --git a/src/styles/main.css b/src/styles/main.css index aefdd53..6fb86a8 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -1,3 +1,12 @@ +@import '@fontsource/noto-sans-kr/300.css'; +@import '@fontsource/noto-sans-kr/400.css'; +@import '@fontsource/noto-sans-kr/500.css'; +@import '@fontsource/noto-sans-kr/700.css'; +@import '@fontsource/outfit/400.css'; +@import '@fontsource/outfit/500.css'; +@import '@fontsource/outfit/600.css'; +@import '@fontsource/outfit/700.css'; + @tailwind base; @tailwind components; @tailwind utilities; diff --git a/vite.config.js b/vite.config.js index 2a9ab69..4bba3eb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,7 +8,6 @@ export default defineConfig({ emptyOutDir: true, rollupOptions: { input: { - main: resolve(__dirname, 'src/index.html'), onboarding: resolve(__dirname, 'src/pages/scene_onboarding.html'), dashboard: resolve(__dirname, 'src/pages/scene_dashboard.html'), swipe: resolve(__dirname, 'src/pages/scene_swipe.html'), @@ -16,9 +15,16 @@ export default defineConfig({ 'ai-classification': resolve(__dirname, 'src/pages/scene_ai_classification.html'), gamification: resolve(__dirname, 'src/pages/scene_gamification.html'), visualization: resolve(__dirname, 'src/pages/scene_visualization.html'), + }, + output: { + assetFileNames: (assetInfo) => { + if (assetInfo.name === 'logo.svg') return 'assets/[name][extname]'; + return 'assets/[name]-[hash][extname]'; + } } } }, + publicDir: 'public', server: { port: 5173, strictPort: true