Refactor: ES module architecture, tauri-plugin-store, unified design

Major changes:
- ES module system: main.js as central hub, shared modules
- tauri-plugin-store for persistent storage (dialog, fs plugins added)
- tauri-plugin-dialog for folder selection
- tauri-plugin-fs for real file system scanning
- Removed MOCK_FILES/FILE_TREE - real file scanner implemented
- All pages use unified page-header + page-content layout
- Custom titlebar with window controls
- Onboarding: 4-step flow with folder selection (Downloads default)
- CSS: unified design system, .hidden utility class added
- SVG icons throughout (no emoji)

Note: onboarding.js downloadDir() has runtime issue - needs fix
This commit is contained in:
2026-06-01 04:41:19 +09:00
parent 1ef6cb1379
commit 13328e6a8b
45 changed files with 2137 additions and 7430 deletions

View File

@@ -0,0 +1 @@
/home/yenru0/.gemini/config/projects/3d5a0d25-0ec6-40c0-8030-0ea115f57ff0.json

22
.gitignore vendored
View File

@@ -4,11 +4,6 @@ node_modules/
# Build output
dist/
# Tauri
src-tauri/target/
src-tauri/gen/schemas
src-tauri/Cargo.lock
# Environment files
.env
.env.*
@@ -34,3 +29,20 @@ Thumbs.db
# Sisyphus session files
.sisyphus/
# Vite
.vite/
# TailwindCSS / PostCSS
tailwind.config.js
postcss.config.js
*.config.js
# Tauri
src-tauri/target/
src-tauri/gen/schemas
src-tauri/Cargo.lock
src-tauri/.tauriignore
# Debug
*.tsbuildinfo

65
AGENTS.md Normal file
View File

@@ -0,0 +1,65 @@
# CLAUDE.md
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

View File

@@ -1,71 +0,0 @@
# Chakmate Migration Review
**Date:** 2026-05-19
**Migration:** Vanilla HTML → Vite + Tauri
**Status:** ✅ Migration Complete - Issues Fixed
---
## Summary
| Category | Status | Notes |
|----------|--------|-------|
| HTML Files | ✅ Fixed | Added missing CSS styles to visualization page |
| JavaScript | ✅ Good | No critical issues |
| Configuration | ✅ Fixed | identifier changed from `.app` to `.desktop` |
| Build | ✅ Verified | All 5 pages build successfully |
---
## 1. Frontend Issues Fixed
### ✅ visualization.html CSS Missing (FIXED)
- **Problem:** Migrated file was missing ~1100 lines of CSS from original
- **Solution:** Extracted CSS from original `scene_visualization.html` (lines 67-1509) and appended to `main.css`
- **CSS Added:**
- CSS variable definitions (`:root`, dark mode)
- Animations (`fadeInUp`, `fadeIn`, `checkBounce`)
- All component styles (`.app`, `.header`, `.comparison-*`, `.tree-*`, `.preview-*`, `.action-bar`, etc.)
- **Verification:** `npm run build` passes, CSS bundle grew from 36.5KB to 59.99KB
### ⚠️ Tauri Build Still Blocked
- **Issue:** `libdbus-1-dev` system dependency not installed
- **Solution:** Run `sudo apt-get install libdbus-1-dev` then `npm run tauri build`
---
## 2. Configuration Updates
### ✅ tauri.conf.json identifier (FIXED)
- **Before:** `com.chakmate.app` (conflicts with macOS bundle extension)
- **After:** `com.chakmate.desktop`
---
## 3. Build Verification
```
npm run build → ✅ SUCCESS
├── dist/index.html (28.82 kB)
├── dist/pages/scene_swipe.html (6.22 kB)
├── dist/pages/scene_ai_classification.html (35.63 kB)
├── dist/pages/scene_gamification.html (10.06 kB)
├── dist/pages/scene_visualization.html (25.22 kB)
├── dist/assets/main.css (59.99 kB) ← Increased from 36.5KB after CSS fix
└── dist/assets/*.js (bundled modules)
```
---
## 4. Remaining Tasks
| Task | Status | Notes |
|------|--------|-------|
| Install libdbus-1-dev | ⏳ Pending | System dependency for Tauri build |
| Run `npm run tauri build` | ⏳ Pending | Generates .exe/.app |
---
## Migration Quality: 98%
The migration is complete. CSS issue was identified and fixed. Tauri build requires system dependency installation.

View File

@@ -1,933 +0,0 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chakmate - 지능형 디지털 자산 관리</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="icon-layers" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</symbol>
<symbol id="icon-shield-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="M9 12l2 2 4-4"/>
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"/>
<path d="M12 5l7 7-7 7"/>
</symbol>
<symbol id="icon-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</symbol>
<symbol id="icon-fire" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z"/>
<path d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z"/>
</symbol>
<symbol id="icon-cog" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 15a3 3 0 100-6 3 3 0 000 6z"/>
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>
</symbol>
<symbol id="icon-document" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
<path d="M14 2v6h6"/>
<path d="M16 13H8"/>
<path d="M16 17H8"/>
<path d="M10 9H8"/>
</symbol>
<symbol id="icon-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
</symbol>
<symbol id="icon-bolt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</symbol>
<symbol id="icon-light-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
</symbol>
<symbol id="icon-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
</symbol>
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5"/>
<path d="M12 19l-7-7 7-7"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18l6-6-6-6"/>
</symbol>
<symbol id="icon-x-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6L6 18"/>
<path d="M6 6l12 12"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 6L9 17l-5-5"/>
</symbol>
<symbol id="icon-photo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<polyline points="21 15 16 10 5 21"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
<path d="M7 10l5 5 5-5"/>
<path d="M12 15V3"/>
</symbol>
<symbol id="icon-undo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 10h10a5 5 0 015 5v0a5 5 0 01-5 5H3"/>
<path d="M3 10l4-4"/>
<path d="M3 10l4 4"/>
</symbol>
</svg>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: { DEFAULT: '#3b82f6', light: '#60a5fa', dark: '#1d4ed8' },
secondary: { DEFAULT: '#06b6d4', light: '#22d3ee' },
accent: { DEFAULT: '#0ea5e9', warn: '#38bdf8', danger: '#f472b6' },
surface: { primary: '#f8fafc', secondary: '#e2e8f0', card: '#ffffff' },
text: { primary: '#0f172a', secondary: '#475569', muted: '#94a3b8' },
},
fontFamily: {
sans: ['Noto Sans KR', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
display: ['Outfit', 'sans-serif'],
},
boxShadow: {
'sm': '0 2px 8px rgba(14, 165, 233, 0.06)',
'md': '0 4px 20px rgba(14, 165, 233, 0.08)',
'lg': '0 8px 40px rgba(14, 165, 233, 0.12)',
'glow': '0 0 30px rgba(14, 165, 233, 0.25)',
'blue': '0 4px 20px rgba(59, 130, 246, 0.3)',
},
animation: {
'float': 'float 3s ease-in-out infinite',
'fade-slide-up': 'fadeSlideUp 0.5s ease forwards',
'slide-in': 'slideIn 0.5s ease forwards',
'bounce-in': 'bounceIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards',
'pulse-slow': 'pulse 2s ease-in-out infinite',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
fadeSlideUp: {
from: { opacity: '0', transform: 'translateY(20px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
slideIn: {
from: { opacity: '0', transform: 'translateY(30px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
bounceIn: {
'0%': { transform: 'scale(0)' },
'50%': { transform: 'scale(1.1)' },
'100%': { transform: 'scale(1)' },
},
pulse: {
'0%, 100%': { opacity: '0.5', transform: 'scale(1)' },
'50%': { opacity: '1', transform: 'scale(1.1)' },
},
},
},
},
}
</script>
<style>
[data-theme="dark"] {
--bg-primary: #0a0e1a;
--bg-secondary: #111827;
--bg-card: #1e293b;
--bg-overlay: rgba(14, 165, 233, 0.12);
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.6);
--shadow-glow: 0 0 30px rgba(14, 165, 233, 0.3);
}
[data-theme="dark"] body { background: var(--bg-primary); color: var(--text-primary); }
[data-theme="dark"] .bg-primary { background: var(--bg-primary); }
[data-theme="dark"] .bg-secondary { background: var(--bg-secondary); }
[data-theme="dark"] .bg-card { background: var(--bg-card); }
[data-theme="dark"] .bg-overlay { background: var(--bg-overlay); }
[data-theme="dark"] .text-primary { color: var(--text-primary); }
[data-theme="dark"] .text-secondary { color: var(--text-secondary); }
[data-theme="dark"] .text-muted { color: var(--text-muted); }
[data-theme="dark"] .shadow-sm { box-shadow: var(--shadow-sm); }
[data-theme="dark"] .shadow-md { box-shadow: var(--shadow-md); }
[data-theme="dark"] .shadow-lg { box-shadow: var(--shadow-lg); }
[data-theme="dark"] .shadow-glow { box-shadow: var(--shadow-glow); }
[data-theme="dark"] .border-secondary { border-color: var(--bg-secondary); }
[data-theme="dark"] .bg-primary { background-color: var(--primary); }
/* Onboarding step-dot active state */
.step-dot.active {
width: 32px;
background-color: #3b82f6;
}
/* Onboarding step content visibility */
.onboarding-step {
display: none;
}
.onboarding-step.active {
display: block;
}
/* Screen visibility */
.screen {
display: none;
}
.screen.active {
display: block;
}
/* Button styles */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1rem 2rem;
border-radius: 9999px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.25s ease;
}
.btn-primary {
background: linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%);
color: white;
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(59, 130, 246, 0.4);
}
.btn-secondary {
background: #e2e8f0;
color: #0f172a;
}
.btn-icon {
width: 44px;
height: 44px;
padding: 0;
border-radius: 12px;
}
</style>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<!-- Onboarding Screen -->
<div id="onboarding" class="screen active">
<div class="max-w-[420px] w-full text-center flex flex-col items-center justify-center min-h-screen p-4 gap-4 lg:flex-row lg:max-w-full lg:justify-center lg:gap-8">
<div class="flex flex-col items-center justify-center lg:flex-shrink-0">
<div class="logo w-64 h-64 mx-auto mb-6 flex items-center justify-center animate-float lg:w-48 lg:h-48">
<img src="assets/logo.svg" alt="Chakmate Logo" class="w-full h-full object-contain">
</div>
<h1 class="brand-name font-display text-4xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent mb-2 lg:text-3xl">Chakmate</h1>
<p class="tagline text-text-secondary text-base mb-10 lg:mb-0">지능형 디지털 자산 관리 솔루션</p>
</div>
<div class="flex flex-col items-center gap-4 lg:flex-col">
<div class="steps-indicator flex gap-2 justify-center mb-8 lg:mb-0">
<div class="step-dot active w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="1"></div>
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="2"></div>
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="3"></div>
</div>
<!-- Step 1: Welcome -->
<div class="onboarding-step active" data-step="1">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-layers"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">파일 정리, 지능적으로</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">AI가 파일 사용 패턴을 분석하여 당신만의 최적화된 폴더 구조를 제안합니다. 더 이상 수동 정리는 필요 없습니다.</p>
<div class="privacy-badge inline-flex items-center gap-2 bg-accent text-white px-4 py-2 rounded-full text-sm font-medium mb-6">
<svg class="w-4 h-4 stroke-white fill-none"><use href="#icon-shield-check"></use></svg>
<span>기기 내 단독 처리 • 완벽한 개인정보 보호</span>
</div>
</div>
<!-- Step 2: Swipe -->
<div class="onboarding-step hidden" data-step="2">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-arrow-right"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">스와이프로 직관적으로</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">오른쪽 스와이프로 보관, 왼쪽 스와이프로 삭제. 카드形式的 인터페이스로 누구나 쉽게 파일을 정리할 수 있습니다.</p>
</div>
<!-- Step 3: Gamification -->
<div class="onboarding-step hidden" data-step="3">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-star"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">습관 형성의 재미</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">매일 정리하고 스트릭을 쌓아보세요. 업적 배지를 수집하고 디지털 공간을 항상 청결하게 유지하세요.</p>
</div>
<div class="nav-buttons flex gap-4 justify-center mt-8">
<button class="btn btn-primary" id="onboarding-next">
<span>계속</span>
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-right"></use></svg>
</button>
</div>
</div>
</div>
</div>
<!-- Dashboard Screen -->
<div id="dashboard" class="screen">
<div class="dashboard-container w-full min-h-screen flex flex-col">
<header class="header flex items-center justify-between p-4 lg:p-6 bg-surface-card shadow-sm sticky top-0 z-50">
<div class="header-left flex items-center gap-3">
<div class="header-logo w-20 h-20 flex items-center justify-center">
<img src="assets/logo.svg" alt="Chakmate Logo" class="w-full h-full object-contain">
</div>
<h1 class="header-title font-display text-xl font-semibold">Chakmate</h1>
</div>
<div class="header-right flex items-center gap-3">
<div class="streak-badge flex items-center gap-2 bg-gradient-to-r from-accent-warn to-amber-500 text-white px-4 py-2 rounded-full font-semibold text-sm" id="streak-display">
<svg class="w-[18px] h-[18px] stroke-current fill-none"><use href="#icon-fire"></use></svg>
<span id="streak-count">7</span>
</div>
<button class="btn btn-icon btn-secondary" id="settings-btn">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-cog"></use></svg>
</button>
</div>
</header>
<main class="main-content flex-1 p-6 grid grid-cols-1 lg:grid-cols-[1fr_380px] gap-6 max-w-[1400px] mx-auto w-full">
<div class="left-panel">
<div class="stats-grid grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<div class="stat-card bg-surface-card rounded-[20px] p-5 shadow-sm hover:-translate-y-1 hover:shadow-md transition-all duration-250 animate-slide-in">
<div class="stat-icon w-12 h-12 rounded-[12px] flex items-center justify-center mb-3 bg-primary/10">
<svg class="w-6 h-6 stroke-primary fill-none"><use href="#icon-document"></use></svg>
</div>
<div class="stat-value font-display text-3xl font-bold text-text-primary mb-1" id="files-organized">248</div>
<div class="stat-label text-text-secondary text-sm">정리된 파일</div>
</div>
<div class="stat-card bg-surface-card rounded-[20px] p-5 shadow-sm hover:-translate-y-1 hover:shadow-md transition-all duration-250 animate-slide-in" style="animation-delay: 0.1s">
<div class="stat-icon w-12 h-12 rounded-[12px] flex items-center justify-center mb-3 bg-pink-500/10">
<svg class="w-6 h-6 stroke-secondary fill-none"><use href="#icon-folder"></use></svg>
</div>
<div class="stat-value font-display text-3xl font-bold text-text-primary mb-1" id="folders-managed">12</div>
<div class="stat-label text-text-secondary text-sm">관리 중인 폴더</div>
</div>
<div class="stat-card bg-surface-card rounded-[20px] p-5 shadow-sm hover:-translate-y-1 hover:shadow-md transition-all duration-250 animate-slide-in" style="animation-delay: 0.2s">
<div class="stat-icon w-12 h-12 rounded-[12px] flex items-center justify-center mb-3 bg-emerald-500/10">
<svg class="w-6 h-6 stroke-accent-warn fill-none stroke-2"><use href="#icon-arrow-right"></use></svg>
</div>
<div class="stat-value font-display text-3xl font-bold text-text-primary mb-1" id="current-streak">7일</div>
<div class="stat-label text-text-secondary text-sm">현재 스트릭</div>
</div>
</div>
<div class="file-view bg-surface-card rounded-[28px] shadow-md p-6 min-h-[400px]">
<div class="file-view-header flex items-center justify-between mb-5">
<h2 class="file-view-title text-xl flex items-center gap-2 font-display">
<svg class="w-6 h-6 stroke-primary fill-none"><use href="#icon-folder"></use></svg>
파일 구조
</h2>
<button class="btn btn-ghost" id="view-all-btn">전체 보기</button>
</div>
<div class="tree-view flex flex-col gap-2" id="file-tree"></div>
</div>
</div>
<div class="suggestions-panel flex flex-col gap-5">
<div class="panel-card bg-surface-card rounded-[28px] shadow-md p-6">
<div class="panel-header flex items-center gap-3 mb-5">
<div class="panel-icon w-11 h-11 rounded-[12px] flex items-center justify-center bg-gradient-to-br from-primary to-primary-light">
<svg class="w-[22px] h-[22px] stroke-white fill-none"><use href="#icon-light-bulb"></use></svg>
</div>
<div>
<h3 class="panel-title text-lg font-display">AI 구조 제안</h3>
<p class="panel-subtitle text-sm text-text-secondary">파일 패턴 분석 결과</p>
</div>
</div>
<div class="suggestion-list flex flex-col gap-3" id="suggestion-list"></div>
<button class="btn btn-primary apply-btn w-full mt-4" id="apply-suggestions">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-right"></use></svg>
<span>적용하기</span>
</button>
</div>
<div class="panel-card bg-surface-card rounded-[28px] shadow-md p-6">
<div class="panel-header flex items-center gap-3 mb-5">
<div class="panel-icon w-11 h-11 rounded-[12px] flex items-center justify-center bg-gradient-to-br from-secondary to-secondary-light">
<svg class="w-[22px] h-[22px] stroke-white fill-none"><use href="#icon-bolt"></use></svg>
</div>
<div>
<h3 class="panel-title text-lg font-display">빠른 실행</h3>
<p class="panel-subtitle text-sm text-text-secondary">개별 Scene 실행</p>
</div>
</div>
<div class="quick-actions flex flex-col gap-3">
<a href="scene_swipe.html" class="quick-action-btn flex items-center gap-4 p-3 bg-surface-secondary rounded-[12px] text-text-primary hover:bg-overlay hover:translate-x-1 transition-all duration-150 no-underline">
<div class="quick-action-icon w-11 h-11 rounded-[12px] flex items-center justify-center flex-shrink-0 bg-gradient-to-br from-primary to-primary-dark">
<svg class="w-[22px] h-[22px] stroke-white fill-none"><use href="#icon-arrow-right"></use></svg>
</div>
<div class="quick-action-info flex-1 flex flex-col gap-0.5">
<span class="quick-action-title font-semibold text-sm">스와이프 모드</span>
<span class="quick-action-desc text-xs text-text-secondary">스와이프로 파일 정리</span>
</div>
<svg class="quick-action-arrow w-5 h-5 stroke-text-muted fill-none"><use href="#icon-chevron-right"></use></svg>
</a>
<a href="scene_ai_classification.html" class="quick-action-btn flex items-center gap-4 p-3 bg-surface-secondary rounded-[12px] text-text-primary hover:bg-overlay hover:translate-x-1 transition-all duration-150 no-underline">
<div class="quick-action-icon w-11 h-11 rounded-[12px] flex items-center justify-center flex-shrink-0 bg-gradient-to-br from-secondary to-secondary-light">
<svg class="w-[22px] h-[22px] stroke-white fill-none"><use href="#icon-light-bulb"></use></svg>
</div>
<div class="quick-action-info flex-1 flex flex-col gap-0.5">
<span class="quick-action-title font-semibold text-sm">AI 분류</span>
<span class="quick-action-desc text-xs text-text-secondary">AI 분석 및 패턴 인식</span>
</div>
<svg class="quick-action-arrow w-5 h-5 stroke-text-muted fill-none"><use href="#icon-chevron-right"></use></svg>
</a>
<a href="scene_visualization.html" class="quick-action-btn flex items-center gap-4 p-3 bg-surface-secondary rounded-[12px] text-text-primary hover:bg-overlay hover:translate-x-1 transition-all duration-150 no-underline">
<div class="quick-action-icon w-11 h-11 rounded-[12px] flex items-center justify-center flex-shrink-0 bg-gradient-to-br from-accent to-emerald-500">
<svg class="w-[22px] h-[22px] stroke-white fill-none"><use href="#icon-folder"></use></svg>
</div>
<div class="quick-action-info flex-1 flex flex-col gap-0.5">
<span class="quick-action-title font-semibold text-sm">구조 제안</span>
<span class="quick-action-desc text-xs text-text-secondary">폴더 구조 시각화</span>
</div>
<svg class="quick-action-arrow w-5 h-5 stroke-text-muted fill-none"><use href="#icon-chevron-right"></use></svg>
</a>
<a href="scene_gamification.html" class="quick-action-btn flex items-center gap-4 p-3 bg-surface-secondary rounded-[12px] text-text-primary hover:bg-overlay hover:translate-x-1 transition-all duration-150 no-underline">
<div class="quick-action-icon w-11 h-11 rounded-[12px] flex items-center justify-center flex-shrink-0 bg-gradient-to-br from-accent-warn to-amber-500">
<svg class="w-[22px] h-[22px] stroke-white fill-none"><use href="#icon-fire"></use></svg>
</div>
<div class="quick-action-info flex-1 flex flex-col gap-0.5">
<span class="quick-action-title font-semibold text-sm">내 진행 상황</span>
<span class="quick-action-desc text-xs text-text-secondary">스트릭 및 업적</span>
</div>
<svg class="quick-action-arrow w-5 h-5 stroke-text-muted fill-none"><use href="#icon-chevron-right"></use></svg>
</a>
</div>
</div>
<div class="panel-card bg-surface-card rounded-[28px] shadow-md p-6">
<div class="panel-header flex items-center gap-3 mb-5">
<div class="panel-icon w-11 h-11 rounded-[12px] flex items-center justify-center bg-gradient-to-br from-primary to-primary-light">
<svg class="w-[22px] h-[22px] stroke-white fill-none"><use href="#icon-star"></use></svg>
</div>
<div>
<h3 class="panel-title text-lg font-display">업적 배지</h3>
<p class="panel-subtitle text-sm text-text-secondary">4/12 달성</p>
</div>
</div>
<div class="achievements-grid grid grid-cols-4 gap-3" id="achievements-grid"></div>
</div>
</div>
</main>
</div>
</div>
<!-- Swipe Screen -->
<div id="swipe-screen" class="screen">
<button class="btn btn-icon btn-secondary back-btn fixed top-4 left-4 z-[100]" id="swipe-back">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-left"></use></svg>
</button>
<div class="swipe-container max-w-[400px] w-full flex flex-col items-center">
<div class="swipe-header text-center mb-6">
<h2 class="swipe-title text-xl mb-2 font-display">파일 정리하기</h2>
<p class="swipe-subtitle text-text-secondary">스와이프로 파일을 보관하거나 삭제하세요</p>
</div>
<div class="swipe-progress flex items-center gap-4 mb-8 w-full">
<div class="progress-bar flex-1 h-1.5 bg-surface-secondary rounded-full overflow-hidden">
<div class="progress-fill h-full bg-gradient-to-r from-primary to-secondary rounded-full transition-all duration-250" id="swipe-progress-fill" style="width: 60%"></div>
</div>
<span class="progress-text text-sm text-text-secondary whitespace-nowrap"><span id="swipe-current">4</span>/<span id="swipe-total">10</span></span>
</div>
<div class="card-stack relative w-full aspect-square max-h-[400px] mb-8" id="card-stack">
<div class="swipe-card absolute inset-0 bg-surface-card rounded-[28px] shadow-lg flex flex-col p-6 transition-transform duration-300 cursor-grab select-none overflow-hidden" id="current-card">
<span class="swipe-label delete absolute top-6 right-6 py-2 px-4 rounded-[12px] font-display font-bold text-xl uppercase bg-red-500/90 text-white rotate-12 opacity-0 transition-opacity duration-150 pointer-events-none">삭제</span>
<span class="swipe-label keep absolute top-6 left-6 py-2 px-4 rounded-[12px] font-display font-bold text-xl uppercase bg-emerald-500/90 text-white -rotate-12 opacity-0 transition-opacity duration-150 pointer-events-none">보관</span>
<div class="card-file-icon image w-20 h-20 mx-auto my-6 rounded-[20px] flex items-center justify-center bg-pink-500/15" id="card-icon">
<svg class="w-12 h-12 stroke-current fill-none stroke-[1.5]"><use href="#icon-photo"></use></svg>
</div>
<div class="card-info text-center flex-1 flex flex-col justify-center">
<h3 class="card-filename font-display text-xl font-semibold mb-2 break-words" id="card-filename">IMG_20240115_123456.jpg</h3>
<p class="card-meta text-text-secondary text-sm mb-4" id="card-meta">2024년 1월 15일 • 3.2 MB</p>
<div class="card-suggestion inline-flex items-center gap-2 bg-surface-secondary px-4 py-2 rounded-full text-sm text-text-secondary mx-auto" id="card-suggestion">
<svg class="w-4 h-4 stroke-primary fill-none"><use href="#icon-folder"></use></svg>
<span>📷 사진 폴더로 이동</span>
</div>
</div>
</div>
</div>
<div class="swipe-actions flex justify-center gap-8 mb-6">
<button class="swipe-action delete w-[72px] h-[72px] rounded-full flex items-center justify-center cursor-pointer transition-all duration-500 bg-surface-card shadow-md hover:scale-110 active:scale-95 text-accent-danger border-none" id="swipe-delete">
<svg class="w-8 h-8 stroke-current fill-none stroke-2"><use href="#icon-trash"></use></svg>
</button>
<button class="swipe-action undo w-14 h-14 rounded-full flex items-center justify-center cursor-pointer transition-all duration-500 bg-surface-card shadow-md hover:scale-110 active:scale-95 text-accent-warn border-none hidden" id="swipe-undo">
<svg class="w-6 h-6 stroke-current fill-none stroke-2"><use href="#icon-undo"></use></svg>
</button>
<button class="swipe-action keep w-[72px] h-[72px] rounded-full flex items-center justify-center cursor-pointer transition-all duration-500 bg-surface-card shadow-md hover:scale-110 active:scale-95 text-accent border-none" id="swipe-keep">
<svg class="w-8 h-8 stroke-current fill-none stroke-2"><use href="#icon-arrow-right"></use></svg>
</button>
</div>
<div class="swipe-hints flex justify-between w-full max-w-[300px] text-text-muted text-sm">
<div class="swipe-hint flex items-center gap-2">
<svg class="w-[18px] h-[18px] stroke-current fill-none"><use href="#icon-arrow-left"></use></svg>
<span>삭제</span>
</div>
<div class="swipe-hint flex items-center gap-2">
<span>보관</span>
<svg class="w-[18px] h-[18px] stroke-accent-warn fill-none stroke-2"><use href="#icon-arrow-right"></use></svg>
</div>
</div>
</div>
</div>
<!-- AI Suggestion Screen -->
<div id="ai-suggestion" class="screen">
<button class="btn btn-icon btn-secondary back-btn fixed top-4 left-4 z-[100]" id="ai-back">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-left"></use></svg>
</button>
<div class="ai-container max-w-[900px] w-full">
<div class="ai-header text-center mb-8">
<h2 class="ai-title text-3xl mb-3 font-display">AI 구조 제안</h2>
<p class="ai-subtitle text-text-secondary text-lg">분석된 파일 패턴을 기반으로 최적화된 폴더 구조를 제안합니다</p>
</div>
<div class="comparison-view grid grid-cols-1 md:grid-cols-[1fr_auto_1fr] gap-6 items-start mb-8">
<div class="comparison-panel bg-surface-card rounded-[28px] shadow-md p-6">
<div class="comparison-header flex items-center gap-3 mb-5 pb-4 border-b border-surface-secondary">
<span class="comparison-badge before px-3 py-1 rounded-full text-xs font-bold uppercase bg-red-500/15 text-accent-danger">현재</span>
<span>현재 폴더 구조</span>
</div>
<div class="comparison-tree flex flex-col gap-2" id="before-tree"></div>
</div>
<div class="comparison-arrow hidden md:flex items-center justify-center pt-16">
<svg class="w-12 h-12 stroke-primary fill-none animate-pulse-slow"><use href="#icon-arrow-right"></use></svg>
</div>
<div class="comparison-panel bg-surface-card rounded-[28px] shadow-md p-6">
<div class="comparison-header flex items-center gap-3 mb-5 pb-4 border-b border-surface-secondary">
<span class="comparison-badge after px-3 py-1 rounded-full text-xs font-bold uppercase bg-emerald-500/15 text-accent">제안</span>
<span>정리된 구조</span>
</div>
<div class="comparison-tree flex flex-col gap-2" id="after-tree"></div>
</div>
</div>
<div class="ai-actions flex justify-center gap-4">
<button class="btn btn-secondary" id="ai-cancel">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-x-mark"></use></svg>
<span>취소</span>
</button>
<button class="btn btn-primary" id="ai-apply">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-check"></use></svg>
<span>구조 적용하기</span>
</button>
</div>
</div>
</div>
<!-- Settings Screen -->
<div id="settings" class="screen">
<button class="btn btn-icon btn-secondary back-btn fixed top-4 left-4 z-[100]" id="settings-back">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-left"></use></svg>
</button>
<div class="settings-container max-w-[600px] w-full">
<div class="settings-header mb-8">
<h2 class="settings-title text-3xl mb-2 font-display">설정</h2>
<p class="settings-subtitle text-text-secondary">앱 환경 사용자 지정</p>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">알림</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">정리 알림</div>
<div class="setting-desc text-sm text-text-secondary">매일 일정한 시간에 파일 정리를 안내합니다</div>
</div>
<div class="toggle active w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-notifications"></div>
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">습관 형성 알림</div>
<div class="setting-desc text-sm text-text-secondary">정리 습관 형성을 돕는 동기를 부여합니다</div>
</div>
<div class="toggle w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-habits"></div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">정리 습관</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">자동 정리</div>
<div class="setting-desc text-sm text-text-secondary">자동으로 파일을 제안된 구조로 정리합니다</div>
</div>
<div class="toggle w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-auto-organize"></div>
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">삭제 전 확인</div>
<div class="setting-desc text-sm text-text-secondary">파일 삭제 전에 확인 메시지를 표시합니다</div>
</div>
<div class="toggle active w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-confirm-delete"></div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">테마</h3>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">외관</div>
<div class="setting-desc text-sm text-text-secondary">라이트 또는 다크 모드를 선택하세요</div>
</div>
<div class="theme-selector flex gap-3">
<div class="theme-option light active w-12 h-12 rounded-[12px] cursor-pointer border-[3px] border-transparent transition-all duration-250 hover:scale-105 flex items-center justify-center bg-gradient-to-br from-[#faf9fb] to-[#f3f2f7]" data-theme="light">
<svg class="w-5 h-5 stroke-text-primary fill-none"><use href="#icon-sun"></use></svg>
</div>
<div class="theme-option dark w-12 h-12 rounded-[12px] cursor-pointer border-[3px] border-transparent transition-all duration-250 hover:scale-105 flex items-center justify-center bg-gradient-to-br from-[#0f0e17] to-[#252336]" data-theme="dark">
<svg class="w-5 h-5 stroke-text-primary fill-none"><use href="#icon-moon"></use></svg>
</div>
</div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">데이터</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">데이터 내보내기</div>
<div class="setting-desc text-sm text-text-secondary">정리된 파일 구조를 JSON으로 내보냅니다</div>
</div>
<button class="btn btn-secondary" id="export-data">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-download"></use></svg>
<span>내보내기</span>
</button>
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">데이터 초기화</div>
<div class="setting-desc text-sm text-text-secondary">모든 데이터를 초기 상태로 되돌립니다</div>
</div>
<button class="btn btn-secondary text-accent-danger" id="reset-data">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-trash"></use></svg>
<span>초기화</span>
</button>
</div>
</div>
</div>
</div>
<!-- Success Modal -->
<div class="modal-overlay fixed inset-0 bg-black/50 flex items-center justify-center z-[1000] opacity-0 invisible transition-all duration-250" id="success-modal">
<div class="modal bg-surface-card rounded-[28px] p-8 max-w-[400px] w-[90%] text-center scale-90 transition-transform duration-500" style="transform: scale(0.9)">
<div class="modal-icon w-20 h-20 mx-auto mb-5 bg-gradient-to-br from-accent to-emerald-500 rounded-full flex items-center justify-center">
<svg class="w-10 h-10 stroke-white fill-none"><use href="#icon-check"></use></svg>
</div>
<h3 class="modal-title text-2xl mb-3 font-display">정리 완료!</h3>
<p class="modal-desc text-text-secondary mb-6">파일 정리가 성공적으로 완료되었습니다.</p>
<button class="btn btn-primary" id="modal-close">
<span>계속하기</span>
</button>
</div>
</div>
<!-- Toast -->
<div class="toast fixed bottom-8 left-1/2 -translate-x-1/2 translate-y-[100px] bg-surface-card px-6 py-4 rounded-full shadow-lg flex items-center gap-3 z-[500] transition-transform duration-500" id="toast">
<div class="toast-icon w-8 h-8 rounded-full flex items-center justify-center bg-amber-500/15 text-accent-warn">
<svg class="w-[18px] h-[18px] stroke-current fill-none text-orange-500"><use href="#icon-fire"></use></svg>
</div>
<span class="toast-message font-medium" id="toast-message">7일 스트릭 달성!</span>
</div>
<script>
const state = {
currentScreen: 'onboarding',
onboardingStep: 1,
streak: parseInt(localStorage.getItem('chackly_streak') || '7'),
filesOrganized: parseInt(localStorage.getItem('chackly_files_organized') || '248'),
foldersManaged: parseInt(localStorage.getItem('chackly_folders') || '12'),
swipeIndex: 0,
swipeHistory: [],
theme: localStorage.getItem('chackly_theme') || 'light',
settings: JSON.parse(localStorage.getItem('chackly_settings') || '{"notifications":true,"habits":false,"autoOrganize":false,"confirmDelete":true}')
};
const mockFiles = [
{ name: 'IMG_20240115_123456.jpg', type: 'image', size: '3.2 MB', date: '2024년 1월 15일', folder: '📷 사진' },
{ name: 'report_Q4_2023.pdf', type: 'pdf', size: '1.8 MB', date: '2024년 1월 10일', folder: '📄 문서' },
{ name: 'vacation_video.mp4', type: 'video', size: '156 MB', date: '2024년 1월 8일', folder: '🎬 영상' },
{ name: 'presentation.pptx', type: 'doc', size: '5.4 MB', date: '2024년 1월 5일', folder: '📄 문서' },
{ name: 'screenshot_2024.png', type: 'image', size: '890 KB', date: '2024년 1월 3일', folder: '📷 사진' },
{ name: 'meeting_notes.docx', type: 'doc', size: '245 KB', date: '2023년 12월 28일', folder: '📄 문서' },
{ name: 'family_photo.jpg', type: 'image', size: '4.1 MB', date: '2023년 12월 25일', folder: '📷 사진' },
{ name: 'project_files.zip', type: 'archive', size: '45 MB', date: '2023년 12월 20일', folder: '📦 압축' },
{ name: 'music_playlist.m3u', type: 'doc', size: '12 KB', date: '2023년 12월 15일', folder: '🎵 음악' },
{ name: 'budget_2024.xlsx', type: 'doc', size: '567 KB', date: '2023년 12월 10일', folder: '📊 스프레드시트' }
];
const fileTree = [
{ name: '📁 문서', type: 'folder', children: [
{ name: '📄 report_Q4_2023.pdf', type: 'pdf', meta: '1.8 MB' },
{ name: '📄 presentation.pptx', type: 'ppt', meta: '5.4 MB' },
{ name: '📄 meeting_notes.docx', type: 'doc', meta: '245 KB' }
]},
{ name: '📁 사진', type: 'folder', children: [
{ name: '🖼️ IMG_20240115.jpg', type: 'image', meta: '3.2 MB' },
{ name: '🖼️ screenshot_2024.png', type: 'image', meta: '890 KB' },
{ name: '🖼️ family_photo.jpg', type: 'image', meta: '4.1 MB' }
]},
{ name: '📁 영상', type: 'folder', children: [
{ name: '🎬 vacation_video.mp4', type: 'video', meta: '156 MB' }
]},
{ name: '📁 압축', type: 'folder', children: [
{ name: '📦 project_files.zip', type: 'archive', meta: '45 MB' }
]}
];
const suggestions = [
{ name: '📷 사진 → 2024', detail: '2개 파일 이동', selected: false },
{ name: '📄 문서 → quarterly', detail: '3개 파일 이동', selected: true },
{ name: '📦 압축 → 보관', detail: '1개 파일 이동', selected: false }
];
const achievements = [
{ name: '첫 정리', icon: '🏆', color: 'gold', unlocked: true },
{ name: '7일 스트릭', icon: '🔥', color: 'gold', unlocked: true },
{ name: '파일 마스터', icon: '📁', color: 'silver', unlocked: true },
{ name: '정리의 달인', icon: '⭐', color: 'silver', unlocked: true },
{ name: '30일 스트릭', icon: '💎', color: 'gold', unlocked: false },
{ name: 'AI 활용자', icon: '🤖', color: 'bronze', unlocked: false },
{ name: '조직력왕', icon: '👑', color: 'silver', unlocked: false },
{ name: '창의命名', icon: '💡', color: 'bronze', unlocked: false }
];
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screenId).classList.add('active');
state.currentScreen = screenId;
}
function updateOnboardingStep(step) {
document.querySelectorAll('.step-dot').forEach((dot, i) => {
const isActive = i + 1 === step;
dot.classList.toggle('active', isActive);
dot.classList.toggle('bg-primary', isActive);
dot.classList.toggle('bg-text-muted', !isActive);
dot.style.width = isActive ? '32px' : '8px';
});
document.querySelectorAll('.onboarding-step').forEach(s => {
const isActive = parseInt(s.dataset.step) === step;
s.classList.toggle('active', isActive);
s.classList.toggle('hidden', !isActive);
});
state.onboardingStep = step;
}
function renderFileTree() {
const container = document.getElementById('file-tree');
container.innerHTML = fileTree.map(item => `
<div class="tree-item folder flex items-center gap-3 p-3 bg-accent/10 rounded-[12px] cursor-pointer hover:bg-overlay hover:translate-x-1 transition-all duration-150">
<div class="tree-icon folder-icon w-8 h-8 rounded-[8px] flex items-center justify-center flex-shrink-0 bg-primary/15 text-primary">
<svg class="w-[18px] h-[18px] stroke-current fill-none stroke-2"><use href="#icon-folder"></use></svg>
</div>
<div class="tree-item-info flex-1 min-w-0">
<div class="tree-item-name font-medium truncate">${item.name}</div>
<div class="tree-item-meta text-xs text-text-muted">${item.children.length}개 항목</div>
</div>
</div>
<div class="tree-children ml-8 flex flex-col gap-2">
${item.children.map(child => `
<div class="tree-item flex items-center gap-3 p-3 bg-surface-secondary rounded-[12px] cursor-pointer hover:bg-overlay hover:translate-x-1 transition-all duration-150">
<div class="tree-icon ${child.type === 'image' ? 'image-icon' : child.type === 'video' ? 'image-icon' : 'doc-icon'} w-8 h-8 rounded-[8px] flex items-center justify-center flex-shrink-0 ${child.type === 'image' ? 'bg-pink-500/15 text-secondary' : child.type === 'video' ? 'bg-purple-500/15 text-purple-500' : 'bg-amber-500/15 text-accent-warn'}">
<svg class="w-[18px] h-[18px] stroke-current fill-none stroke-2"><use href="#icon-document"></use></svg>
</div>
<div class="tree-item-info flex-1 min-w-0">
<div class="tree-item-name font-medium truncate">${child.name}</div>
<div class="tree-item-meta text-xs text-text-muted">${child.meta}</div>
</div>
</div>
`).join('')}
</div>
`).join('');
}
function renderSuggestions() {
const container = document.getElementById('suggestion-list');
container.innerHTML = suggestions.map((s, i) => `
<div class="suggestion-item flex items-center gap-3 p-3 bg-surface-secondary rounded-[12px] cursor-pointer border-2 border-transparent transition-all duration-150 ${s.selected ? 'bg-primary/10 border-primary' : 'hover:bg-overlay hover:border-primary-light'}" data-index="${i}">
<div class="suggestion-checkbox w-6 h-6 border-2 ${s.selected ? 'bg-primary border-primary' : 'border-text-muted'} rounded-[8px] flex items-center justify-center flex-shrink-0 transition-all duration-150">
<svg class="w-3.5 h-3.5 stroke-white fill-none ${s.selected ? 'opacity-100' : 'opacity-0'} transition-opacity duration-150"><use href="#icon-check"></use></svg>
</div>
<div class="suggestion-content flex-1">
<div class="suggestion-name font-medium mb-0.5">${s.name}</div>
<div class="suggestion-detail text-sm text-text-secondary">${s.detail}</div>
</div>
<svg class="suggestion-arrow w-5 h-5 stroke-text-muted fill-none"><use href="#icon-chevron-right"></use></svg>
</div>
`).join('');
container.querySelectorAll('.suggestion-item').forEach(item => {
item.addEventListener('click', () => {
const idx = parseInt(item.dataset.index);
suggestions[idx].selected = !suggestions[idx].selected;
renderSuggestions();
});
});
}
function renderAchievements() {
const container = document.getElementById('achievements-grid');
container.innerHTML = achievements.map(a => `
<div class="achievement aspect-square bg-surface-secondary rounded-[12px] flex flex-col items-center justify-center gap-1 p-2 transition-all duration-250 cursor-pointer hover:scale-105 ${a.unlocked ? '' : 'opacity-40 grayscale'}">
<div class="achievement-icon w-9 h-9 rounded-[8px] flex items-center justify-center ${a.unlocked ? a.color === 'gold' ? 'bg-gradient-to-br from-amber-400 to-amber-600 text-white' : a.color === 'silver' ? 'bg-gradient-to-br from-gray-300 to-gray-500 text-white' : 'bg-gradient-to-br from-amber-700 to-amber-900 text-white' : 'bg-overlay text-primary'}">
<span class="text-lg">${a.icon}</span>
</div>
<div class="achievement-name text-[10px] text-center text-text-secondary">${a.name}</div>
<div class="badge-tooltip absolute -top-8 left-1/2 -translate-x-1/2 bg-text-primary text-surface-card px-2 py-1 rounded-[8px] text-xs whitespace-nowrap opacity-0 invisible transition-all duration-150">${a.name}</div>
</div>
`).join('');
}
function updateSwipeCard() {
if (state.swipeIndex >= mockFiles.length) return;
const file = mockFiles[state.swipeIndex];
document.getElementById('card-filename').textContent = file.name;
document.getElementById('card-meta').textContent = `${file.date}${file.size}`;
document.getElementById('card-suggestion').innerHTML = `<svg class="w-4 h-4 stroke-primary fill-none"><use href="#icon-folder"></use></svg><span>${file.folder}로 이동</span>`;
const iconEl = document.getElementById('card-icon');
iconEl.className = `card-file-icon ${file.type === 'image' ? 'image' : file.type === 'video' ? 'video' : file.type === 'pdf' ? 'pdf' : 'doc'} w-20 h-20 mx-auto my-6 rounded-[20px] flex items-center justify-center ${file.type === 'image' ? 'bg-pink-500/15 text-secondary' : file.type === 'video' ? 'bg-purple-500/15 text-purple-500' : file.type === 'pdf' ? 'bg-amber-500/15 text-accent-warn' : 'bg-primary/15 text-primary'}`;
document.getElementById('swipe-progress-fill').style.width = `${((state.swipeIndex + 1) / mockFiles.length) * 100}%`;
document.getElementById('swipe-current').textContent = state.swipeIndex + 1;
document.getElementById('swipe-total').textContent = mockFiles.length;
}
document.getElementById('onboarding-next').addEventListener('click', () => {
if (state.onboardingStep < 3) {
updateOnboardingStep(state.onboardingStep + 1);
} else {
showScreen('dashboard');
}
});
document.getElementById('settings-btn').addEventListener('click', () => showScreen('settings'));
document.getElementById('settings-back').addEventListener('click', () => showScreen('dashboard'));
document.getElementById('swipe-back').addEventListener('click', () => showScreen('dashboard'));
document.getElementById('ai-back').addEventListener('click', () => showScreen('dashboard'));
document.getElementById('apply-suggestions').addEventListener('click', () => showScreen('ai-suggestion'));
document.getElementById('ai-cancel').addEventListener('click', () => showScreen('dashboard'));
document.getElementById('ai-apply').addEventListener('click', () => {
showScreen('dashboard');
document.getElementById('success-modal').classList.add('active');
document.querySelector('.modal').style.transform = 'scale(1)';
});
document.getElementById('modal-close').addEventListener('click', () => {
document.getElementById('success-modal').classList.remove('active');
});
document.querySelectorAll('.toggle').forEach(toggle => {
toggle.addEventListener('click', () => {
toggle.classList.toggle('active');
toggle.style.background = toggle.classList.contains('active') ? '#3b82f6' : '';
toggle.querySelector('::after') && (toggle.style.setProperty('--after-transform', toggle.classList.contains('active') ? 'translateX(24px)' : ''));
});
});
document.querySelectorAll('.theme-option').forEach(option => {
option.addEventListener('click', () => {
document.querySelectorAll('.theme-option').forEach(o => o.classList.remove('active'));
option.classList.add('active');
document.body.setAttribute('data-theme', option.dataset.theme);
localStorage.setItem('chackly_theme', option.dataset.theme);
});
});
let touchStartX = 0;
const card = document.getElementById('current-card');
card.addEventListener('touchstart', e => {
touchStartX = e.touches[0].clientX;
card.classList.add('dragging');
});
card.addEventListener('touchmove', e => {
const diff = e.touches[0].clientX - touchStartX;
card.style.transform = `translateX(${diff}px) rotate(${diff * 0.05}deg)`;
card.classList.toggle('swiping-left', diff < -50);
card.classList.toggle('swiping-right', diff > 50);
});
card.addEventListener('touchend', e => {
const diff = e.changedTouches[0].clientX - touchStartX;
card.classList.remove('dragging', 'swiping-left', 'swiping-right');
if (Math.abs(diff) > 100) {
state.swipeHistory.push({ file: mockFiles[state.swipeIndex], action: diff > 0 ? 'keep' : 'delete' });
state.swipeIndex++;
updateSwipeCard();
} else {
card.style.transform = '';
}
});
document.getElementById('swipe-delete').addEventListener('click', () => {
state.swipeHistory.push({ file: mockFiles[state.swipeIndex], action: 'delete' });
state.swipeIndex++;
updateSwipeCard();
});
document.getElementById('swipe-keep').addEventListener('click', () => {
state.swipeHistory.push({ file: mockFiles[state.swipeIndex], action: 'keep' });
state.swipeIndex++;
updateSwipeCard();
});
document.getElementById('swipe-undo').addEventListener('click', () => {
if (state.swipeHistory.length > 0) {
state.swipeIndex--;
state.swipeHistory.pop();
updateSwipeCard();
}
});
renderFileTree();
renderSuggestions();
renderAchievements();
updateSwipeCard();
</script>
</body>
</html>

48
package-lock.json generated
View File

@@ -8,8 +8,13 @@
"name": "chakmate",
"version": "1.0.0",
"dependencies": {
"@fontsource/noto-color-emoji": "^5.2.12",
"@fontsource/noto-sans-kr": "^5.2.9",
"@fontsource/outfit": "^5.2.8"
"@fontsource/outfit": "^5.2.8",
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-store": "^2.4.3"
},
"devDependencies": {
"@tauri-apps/cli": "^2.0.0",
@@ -399,6 +404,14 @@
"node": ">=12"
}
},
"node_modules/@fontsource/noto-color-emoji": {
"version": "5.2.12",
"resolved": "https://registry.npmjs.org/@fontsource/noto-color-emoji/-/noto-color-emoji-5.2.12.tgz",
"integrity": "sha512-UVOh4qxD/y42mcB4XvEdpV1WE01zn7O4+4tAWwzp5QAd4JY5jWPHu3/3T1kQoD04oYbQ5cJ9SI12IfcCKaS8aw==",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"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",
@@ -810,6 +823,15 @@
"win32"
]
},
"node_modules/@tauri-apps/api": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz",
"integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/tauri"
}
},
"node_modules/@tauri-apps/cli": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz",
@@ -1015,6 +1037,30 @@
"node": ">= 10"
}
},
"node_modules/@tauri-apps/plugin-dialog": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.1.tgz",
"integrity": "sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==",
"dependencies": {
"@tauri-apps/api": "^2.11.0"
}
},
"node_modules/@tauri-apps/plugin-fs": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.5.1.tgz",
"integrity": "sha512-9Lz+Jopp6QyeEWhlpkMx4R/+P9HgR+AVAI4vOZhlT8Xaymtz8iVI/Ov984/XTqgJz/5gz5NretqPB/XEMS3NhQ==",
"dependencies": {
"@tauri-apps/api": "^2.11.0"
}
},
"node_modules/@tauri-apps/plugin-store": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-store/-/plugin-store-2.4.3.tgz",
"integrity": "sha512-9LWPj9yMphRi9czEtUv87XHbl1b6xgd9EXpPrUnq6nG7+nbtoF84d4Kwz9xhAv/Hf30sr58pq7EOlyI936y8qw==",
"dependencies": {
"@tauri-apps/api": "^2.11.0"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",

View File

@@ -10,8 +10,13 @@
"tauri": "tauri"
},
"dependencies": {
"@fontsource/noto-color-emoji": "^5.2.12",
"@fontsource/noto-sans-kr": "^5.2.9",
"@fontsource/outfit": "^5.2.8"
"@fontsource/outfit": "^5.2.8",
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-store": "^2.4.3"
},
"devDependencies": {
"@tauri-apps/cli": "^2.0.0",

View File

@@ -1,552 +0,0 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 분류 - Chakmate</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<!-- Heroicons SVG Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" class="hidden">
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14M12 5l7 7-7 7"/>
</symbol>
<symbol id="icon-sparkles" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v18M5.5 9.5L7.5 12M5.5 14.5L7.5 12M16.5 9.5L14.5 12M16.5 14.5L14.5 12M9 3l1.5 4.5L15 3M9 21l1.5-4.5L15 21"/>
<path d="M9 3l1.5 4.5M14.5 7.5L15 3l-4.5 1.5M9 21l1.5-4.5M14.5 16.5L15 21l-4.5-1.5"/>
</symbol>
<symbol id="icon-shield-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="M9 12l2 2 4-4"/>
</symbol>
<symbol id="icon-chart-bar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<circle cx="19" cy="5" r="2"/>
<circle cx="5" cy="19" r="2"/>
<path d="M10.4 10.6l5.2 5.2M13.6 13.6l5.2 5.2"/>
</symbol>
<symbol id="icon-document" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</symbol>
<symbol id="icon-tag" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
<line x1="7" y1="7" x2="7.01" y2="7"/>
</symbol>
<symbol id="icon-clock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</symbol>
<symbol id="icon-photo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<polyline points="21 15 16 10 5 21"/>
</symbol>
<symbol id="icon-arrow-up" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 19V5M5 12l7-7 7 7"/>
</symbol>
<symbol id="icon-magnifying-glass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</symbol>
<symbol id="icon-sparkles-alt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</symbol>
<symbol id="icon-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="8" y="8" width="12" height="12" rx="2"/>
<path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/>
</symbol>
</svg>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: { DEFAULT: '#3b82f6', light: '#60a5fa', dark: '#1d4ed8' },
secondary: { DEFAULT: '#06b6d4', light: '#22d3ee' },
accent: { DEFAULT: '#0ea5e9', warn: '#38bdf8', danger: '#f472b6' },
surface: { primary: '#f8fafc', secondary: '#e2e8f0', card: '#ffffff' },
text: { primary: '#0f172a', secondary: '#475569', muted: '#94a3b8' },
},
fontFamily: {
sans: ['Noto Sans KR', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
display: ['Outfit', 'sans-serif'],
},
boxShadow: {
'sm': '0 2px 8px rgba(14, 165, 233, 0.06)',
'md': '0 4px 20px rgba(14, 165, 233, 0.08)',
'lg': '0 8px 40px rgba(14, 165, 233, 0.12)',
'glow': '0 0 30px rgba(14, 165, 233, 0.25)',
'blue': '0 4px 20px rgba(59, 130, 246, 0.3)',
},
animation: {
'float': 'float 3s ease-in-out infinite',
'fade-slide-up': 'fadeSlideUp 0.5s ease forwards',
'slide-in': 'slideIn 0.5s ease forwards',
'bounce-in': 'bounceIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards',
'pulse-slow': 'pulse 2s ease-in-out infinite',
'pulse-glow': 'pulseGlow 4s ease-in-out infinite',
'spin': 'spin 2s linear infinite',
'node-pulse': 'nodePulse 1.5s ease-in-out infinite',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
fadeSlideUp: {
from: { opacity: '0', transform: 'translateY(20px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
slideIn: {
from: { opacity: '0', transform: 'translateY(30px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
bounceIn: {
'0%': { transform: 'scale(0)' },
'50%': { transform: 'scale(1.1)' },
'100%': { transform: 'scale(1)' },
},
pulse: {
'0%, 100%': { opacity: '0.5', transform: 'scale(1)' },
'50%': { opacity: '1', transform: 'scale(1.1)' },
},
pulseGlow: {
'0%, 100%': { transform: 'scale(1)', opacity: '0.5' },
'50%': { transform: 'scale(1.1)', opacity: '0.8' },
},
nodePulse: {
'0%, 100%': { transform: 'scale(1)', opacity: '0.6' },
'50%': { transform: 'scale(1.5)', opacity: '1' },
},
},
},
},
}
</script>
<style>
[data-theme="dark"] {
--bg-primary: #0a0e1a;
--bg-secondary: #111827;
--bg-card: #1e293b;
--bg-overlay: rgba(14, 165, 233, 0.12);
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.6);
--shadow-glow: 0 0 30px rgba(14, 165, 233, 0.3);
}
[data-theme="dark"] body { background: var(--bg-primary); color: var(--text-primary); }
[data-theme="dark"] .bg-primary { background: var(--bg-primary); }
[data-theme="dark"] .bg-secondary { background: var(--bg-secondary); }
[data-theme="dark"] .bg-card { background: var(--bg-card); }
[data-theme="dark"] .bg-overlay { background: var(--bg-overlay); }
[data-theme="dark"] .text-primary { color: var(--text-primary); }
[data-theme="dark"] .text-secondary { color: var(--text-secondary); }
[data-theme="dark"] .text-muted { color: var(--text-muted); }
[data-theme="dark"] .shadow-sm { box-shadow: var(--shadow-sm); }
[data-theme="dark"] .shadow-md { box-shadow: var(--shadow-md); }
[data-theme="dark"] .shadow-lg { box-shadow: var(--shadow-lg); }
[data-theme="dark"] .shadow-glow { box-shadow: var(--shadow-glow); }
[data-theme="dark"] .border-secondary { border-color: var(--bg-secondary); }
/* Neural network nodes animation delays */
.neural-node:nth-child(1) { animation-delay: 0s; }
.neural-node:nth-child(2) { animation-delay: 0.3s; }
.neural-node:nth-child(3) { animation-delay: 0.6s; }
.neural-node:nth-child(4) { animation-delay: 0.9s; }
.neural-node:nth-child(5) { animation-delay: 1.2s; }
/* Discovery card stagger animation */
.discovery-card:nth-child(1) { transition-delay: 0ms; }
.discovery-card:nth-child(2) { transition-delay: 100ms; }
.discovery-card:nth-child(3) { transition-delay: 200ms; }
.discovery-card:nth-child(4) { transition-delay: 300ms; }
/* Tag pill click feedback */
.tag-pill-feedback { transform: scale(0.95); }
/* Hint chip click feedback */
.hint-chip-feedback { background: var(--primary-dark); }
</style>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<div class="max-w-[1200px] w-full mx-auto p-6">
<!-- Back Header -->
<div class="flex items-center gap-4 mb-8">
<a href="index.html" class="inline-flex items-center gap-2 text-text-secondary font-medium transition-colors duration-150 hover:text-primary hover:bg-accent/10 rounded-xl px-3 py-2">
<svg class="w-5 h-5"><use href="#icon-arrow-left"></use></svg>
<span>대시보드로</span>
</a>
</div>
<!-- Privacy Hero Banner -->
<div class="bg-gradient-to-br from-primary via-secondary to-primary-light rounded-[28px] p-12 mb-8 relative overflow-hidden shadow-lg">
<div class="absolute top-[-50%] right-[-20%] w-[400px] h-[400px] bg-[radial-gradient(circle,rgba(255,255,255,0.15),transparent_70%)] animate-pulse-glow"></div>
<div class="relative z-10 flex items-center gap-6">
<div class="w-20 h-20 bg-white/20 rounded-xl flex items-center justify-center backdrop-blur-sm">
<svg class="w-12 h-12 text-white"><use href="#icon-shield-check"></use></svg>
</div>
</div>
<div class="bg-surface-card rounded-xl p-6 mb-8 shadow-md flex items-center gap-6">
<div class="w-16 h-16 relative">
<div class="w-full h-full rounded-full border-4 border-surface-secondary relative">
<div class="absolute inset-[-4px] rounded-full border-4 border-transparent border-t-primary animate-spin"></div>
</div>
<div class="absolute inset-0 flex items-center justify-center">
<div class="w-3 h-3 bg-primary rounded-full absolute top-[10%] left-[50%] -translate-x-1/2 animate-node-pulse"></div>
<div class="w-3 h-3 bg-primary rounded-full absolute top-[50%] right-[10%] animate-node-pulse"></div>
<div class="w-3 h-3 bg-primary rounded-full absolute bottom-[10%] left-[50%] -translate-x-1/2 animate-node-pulse"></div>
<div class="w-3 h-3 bg-primary rounded-full absolute top-[50%] left-[10%] animate-node-pulse"></div>
<div class="w-3 h-3 bg-primary rounded-full absolute top-[50%] left-[50%] -translate-x-1/2 -translate-y-1/2 animate-node-pulse"></div>
</div>
</div>
<div class="flex-1">
<h3 class="text-[1.1rem] font-display font-semibold mb-1">신경망 분석 활성화</h3>
<p class="text-text-secondary text-[0.9rem]">12개 디렉토리에서 847개 파일 스캔 중</p>
</div>
<div class="relative w-[60px] h-[60px]">
<svg class="w-full h-full -rotate-[90deg]" viewBox="0 0 60 60">
<circle class="fill-none stroke-surface-secondary" cx="30" cy="30" r="25" stroke-width="6"/>
<circle class="progress-ring-fill fill-none stroke-accent rounded-full transition-all duration-[1.5s]" cx="30" cy="30" r="25" stroke-width="6" stroke-linecap="round" stroke-dasharray="157" stroke-dashoffset="157"/>
</svg>
<span class="absolute inset-0 flex items-center justify-center font-display font-bold text-accent text-[0.9rem]">87%</span>
</div>
</div>
<!-- AI Discoveries Section -->
<div class="mb-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<h2 class="text-[1.5rem] font-display font-semibold">AI 발견</h2>
<span class="bg-primary text-white text-xs font-semibold px-3 py-1 rounded-full">4개 신규</span>
</div>
</div>
<div class="grid grid-cols-2 gap-6">
<!-- Smart Clusters Card -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-400">
<div class="flex items-center gap-4 mb-5">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-primary to-primary-dark flex items-center justify-center">
<svg class="w-6 h-6 text-white"><use href="#icon-chart-bar"></use></svg>
</div>
<div>
<h3 class="text-[1.1rem] font-semibold mb-1">스마트 클러스터</h3>
<span class="text-text-muted text-[0.85rem]">3개 그룹 발견</span>
</div>
</div>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl transition-transform duration-150 hover:translate-x-1">
<div class="w-3 h-10 rounded-sm bg-gradient-to-b from-primary to-primary-light"></div>
<div class="flex-1">
<h4 class="text-[0.95rem] font-semibold mb-1">금융 보고서</h4>
<p class="text-text-muted text-[0.8rem]">5개 파일 - 2024년 4분기 분석 문서</p>
</div>
<span class="bg-surface-card text-text-secondary text-xs font-semibold px-3 py-2 rounded-full">5 files</span>
</div>
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl transition-transform duration-150 hover:translate-x-1">
<div class="w-3 h-10 rounded-sm bg-gradient-to-b from-secondary to-secondary-light"></div>
<div class="flex-1">
<h4 class="text-[0.95rem] font-semibold mb-1">휴가 사진</h4>
<p class="text-text-muted text-[0.8rem]">12개 파일 - 2024년 여름 여행</p>
</div>
<span class="bg-surface-card text-text-secondary text-xs font-semibold px-3 py-2 rounded-full">12 files</span>
</div>
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl transition-transform duration-150 hover:translate-x-1">
<div class="w-3 h-10 rounded-sm bg-gradient-to-b from-accent to-[#6ee7b7]"></div>
<div class="flex-1">
<h4 class="text-[0.95rem] font-semibold mb-1">프로젝트 자산</h4>
<p class="text-text-muted text-[0.8rem]">8개 파일 - 디자인 리소스</p>
</div>
<span class="bg-surface-card text-text-secondary text-xs font-semibold px-3 py-2 rounded-full">8 files</span>
</div>
</div>
</div>
<!-- Duplicate Detection Card -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-400">
<div class="flex items-center gap-4 mb-5">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-secondary to-[#ec4899] flex items-center justify-center">
<svg class="w-6 h-6 text-white"><use href="#icon-cpu"></use></svg>
</div>
<div>
<h3 class="text-[1.1rem] font-semibold mb-1">중복 후보</h3>
<span class="text-text-muted text-[0.85rem]">2개 중복 가능성</span>
</div>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl">
<div class="w-10 h-10 rounded-sm bg-gradient-to-br from-secondary-light to-secondary flex items-center justify-center">
<svg class="w-5 h-5 text-white"><use href="#icon-document"></use></svg>
</div>
<div class="flex-1">
<p class="font-medium text-[0.9rem] mb-1">report_final.pdf</p>
<span class="text-text-muted text-[0.8rem]">report_v2.pdf와 일치</span>
</div>
<span class="bg-gradient-to-r from-accent-warn to-[#f59e0b] text-white text-[0.8rem] font-bold px-3 py-2 rounded-full">94%</span>
</div>
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl">
<div class="w-10 h-10 rounded-sm bg-gradient-to-br from-secondary-light to-secondary flex items-center justify-center">
<svg class="w-5 h-5 text-white"><use href="#icon-document"></use></svg>
</div>
<div class="flex-1">
<p class="font-medium text-[0.9rem] mb-1">presentation.pptx</p>
<span class="text-text-muted text-[0.8rem]">presentation_backup.pptx와 일치</span>
</div>
<span class="bg-gradient-to-r from-accent-warn to-[#f59e0b] text-white text-[0.8rem] font-bold px-3 py-2 rounded-full">78%</span>
</div>
</div>
</div>
<!-- Auto Tags Card -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-400">
<div class="flex items-center gap-4 mb-5">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-accent to-[#059669] flex items-center justify-center">
<svg class="w-6 h-6 text-white"><use href="#icon-tag"></use></svg>
</div>
<div>
<h3 class="text-[1.1rem] font-semibold mb-1">자동 태그</h3>
<span class="text-text-muted text-[0.85rem]">15개 태그 생성됨</span>
</div>
</div>
<div class="flex flex-wrap gap-3">
<span class="tag-pill inline-flex items-center gap-2 px-4 py-3 bg-surface-secondary rounded-full text-[0.85rem] font-medium cursor-pointer transition-all duration-150 hover:scale-105 hover:shadow-sm">
<span class="w-2 h-2 rounded-full bg-accent-danger"></span>
높은 우선순위
</span>
<span class="tag-pill inline-flex items-center gap-2 px-4 py-3 bg-surface-secondary rounded-full text-[0.85rem] font-medium cursor-pointer transition-all duration-150 hover:scale-105 hover:shadow-sm">
<span class="w-2 h-2 rounded-full bg-primary"></span>
작업 관련
</span>
<span class="tag-pill inline-flex items-center gap-2 px-4 py-3 bg-surface-secondary rounded-full text-[0.85rem] font-medium cursor-pointer transition-all duration-150 hover:scale-105 hover:shadow-sm">
<span class="w-2 h-2 rounded-full bg-secondary"></span>
개인
</span>
<span class="tag-pill inline-flex items-center gap-2 px-4 py-3 bg-surface-secondary rounded-full text-[0.85rem] font-medium cursor-pointer transition-all duration-150 hover:scale-105 hover:shadow-sm">
<span class="w-2 h-2 rounded-full bg-text-muted"></span>
아카이브 대상
</span>
<span class="tag-pill inline-flex items-center gap-2 px-4 py-3 bg-surface-secondary rounded-full text-[0.85rem] font-medium cursor-pointer transition-all duration-150 hover:scale-105 hover:shadow-sm">
<span class="w-2 h-2 rounded-full bg-accent"></span>
빠른 접근
</span>
</div>
</div>
<!-- Predictive Actions Card -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-400">
<div class="flex items-center gap-4 mb-5">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-accent-warn to-[#f59e0b] flex items-center justify-center">
<svg class="w-6 h-6 text-white"><use href="#icon-clock"></use></svg>
</div>
<div>
<h3 class="text-[1.1rem] font-semibold mb-1">예측 작업</h3>
<span class="text-text-muted text-[0.85rem]">3개 예측</span>
</div>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-primary to-primary-dark flex items-center justify-center">
<svg class="w-[22px] h-[22px] text-white"><use href="#icon-document"></use></svg>
</div>
<div class="flex-1">
<p class="font-medium text-[0.9rem] mb-1">곧 4분기 보고서가 필요할 것입니다</p>
<span class="text-text-muted text-[0.8rem]">월별 접근 패턴 기반</span>
</div>
<div class="flex items-center gap-2 px-3 py-2 bg-surface-card rounded-full text-[0.75rem] font-semibold">
<div class="w-10 h-1.5 bg-surface-secondary rounded-full overflow-hidden">
<div class="confidence-fill h-full rounded-full transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 92%; background: var(--accent);"></div>
</div>
92%
</div>
</div>
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-secondary to-[#ec4899] flex items-center justify-center">
<svg class="w-[22px] h-[22px] text-white"><use href="#icon-photo"></use></svg>
</div>
<div class="flex-1">
<p class="font-medium text-[0.9rem] mb-1">휴가 사진 아카이브</p>
<span class="text-text-muted text-[0.8rem]">28일 동안 접근 없음</span>
</div>
<div class="flex items-center gap-2 px-3 py-2 bg-surface-card rounded-full text-[0.75rem] font-semibold">
<div class="w-10 h-1.5 bg-surface-secondary rounded-full overflow-hidden">
<div class="confidence-fill h-full rounded-full transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 78%; background: var(--accent-warn);"></div>
</div>
78%
</div>
</div>
<div class="flex items-center gap-4 p-4 bg-surface-secondary rounded-xl">
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-accent to-[#059669] flex items-center justify-center">
<svg class="w-[22px] h-[22px] text-white"><use href="#icon-arrow-up"></use></svg>
</div>
<div class="flex-1">
<p class="font-medium text-[0.9rem] mb-1">프로젝트 자산 백업</p>
<span class="text-text-muted text-[0.8rem]">3일 전 수정됨</span>
</div>
<div class="flex items-center gap-2 px-3 py-2 bg-surface-card rounded-full text-[0.75rem] font-semibold">
<div class="w-10 h-1.5 bg-surface-secondary rounded-full overflow-hidden">
<div class="confidence-fill h-full rounded-full transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 85%; background: var(--accent);"></div>
</div>
85%
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Distribution Chart -->
<div class="mb-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<h2 class="text-[1.5rem] font-display font-semibold">파일 분포</h2>
</div>
</div>
<div class="bg-surface-card rounded-xl p-8 shadow-md">
<div class="grid grid-cols-[300px_1fr] gap-8 items-center max-lg:grid-cols-1">
<div class="relative w-[220px] h-[220px] mx-auto">
<svg class="w-full h-full -rotate-[90deg]" viewBox="0 0 100 100">
<circle class="pie-segment fill-none stroke-primary transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 98;"/>
<circle class="pie-segment fill-none stroke-secondary transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 78;"/>
<circle class="pie-segment fill-none stroke-accent transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 47;"/>
<circle class="pie-segment fill-none stroke-accent-warn transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 47;"/>
</svg>
<div class="absolute inset-10 bg-surface-card rounded-full flex flex-col items-center justify-center">
<span class="font-display text-2xl font-bold text-text-primary">847</span>
<small class="text-text-muted text-[0.8rem]">전체 파일</small>
</div>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">문서</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 45%; background: linear-gradient(90deg, var(--primary), var(--primary-light));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">382</span>
</div>
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">이미지</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 35%; background: linear-gradient(90deg, var(--secondary), var(--secondary-light));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">296</span>
</div>
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">동영상</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 12%; background: linear-gradient(90deg, var(--accent), #6ee7b7);"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">102</span>
</div>
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">기타</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 8%; background: linear-gradient(90deg, var(--accent-warn), #fcd34d);"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">67</span>
</div>
</div>
</div>
</div>
</div>
<!-- Semantic Search Hints -->
<div class="bg-surface-card rounded-xl p-6 shadow-md mb-8">
<h3 class="text-base font-display font-semibold mb-4 flex items-center gap-2">
<svg class="w-5 h-5 text-primary"><use href="#icon-magnifying-glass"></use></svg>
관련 검색
</h3>
<div class="flex flex-wrap gap-3">
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">2024년 4분기 금융 보고서</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">프로젝트 타임라인</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">2024년 휴가</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">디자인 자산</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">세금 문서</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">고객 프레젠테이션</span>
</div>
</div>
<!-- Generate Button -->
<div class="flex justify-center gap-4">
<button class="inline-flex items-center justify-center gap-2 px-8 py-4 bg-gradient-to-br from-primary to-primary-dark text-white rounded-full font-display font-semibold text-base shadow-blue hover:shadow-[0_6px_25px_rgba(59,130,246,0.4)] hover:-translate-y-0.5 active:translate-y-0 transition-all duration-250 min-h-[56px] min-w-[160px]">
<svg class="w-5 h-5"><use href="#icon-sparkles-alt"></use></svg>
새 인사이트 생성
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Stagger reveal discovery cards
setTimeout(() => {
document.querySelectorAll('.discovery-card').forEach(card => {
card.classList.remove('opacity-0', 'translate-y-5');
card.classList.add('opacity-100', 'translate-y-0');
});
}, 300);
// Animate progress ring
setTimeout(() => {
document.querySelector('.progress-ring-fill').style.strokeDashoffset = '20';
}, 500);
// Animate confidence bars
setTimeout(() => {
document.querySelectorAll('.confidence-fill').forEach(bar => {
const width = bar.style.width;
bar.style.width = '0';
setTimeout(() => {
bar.style.width = width;
}, 50);
});
}, 800);
// Animate pie segments
setTimeout(() => {
document.querySelectorAll('.pie-segment').forEach(segment => {
const dash = getComputedStyle(segment).getPropertyValue('--segment-dash');
segment.style.strokeDasharray = dash + ' 314';
});
}, 600);
// Animate pattern bars
setTimeout(() => {
document.querySelectorAll('.pattern-bar-fill').forEach(bar => {
const width = bar.style.width;
bar.style.width = '0';
setTimeout(() => {
bar.style.width = width;
}, 50);
});
}, 700);
// Tag pill interaction
document.querySelectorAll('.tag-pill').forEach(tag => {
tag.addEventListener('click', () => {
tag.classList.add('tag-pill-feedback');
setTimeout(() => {
tag.classList.remove('tag-pill-feedback');
}, 150);
});
});
// Hint chip interaction
document.querySelectorAll('.hint-chip').forEach(chip => {
chip.addEventListener('click', () => {
chip.classList.add('hint-chip-feedback');
setTimeout(() => {
chip.classList.remove('hint-chip-feedback');
}, 300);
});
});
});
</script>
</body>
</html>

View File

@@ -1,564 +0,0 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>내 진행 상황 - Chakmate</title>
<!-- Heroicons SVG Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
</svg>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: { DEFAULT: '#3b82f6', light: '#60a5fa', dark: '#1d4ed8' },
secondary: { DEFAULT: '#06b6d4', light: '#22d3ee' },
accent: { DEFAULT: '#0ea5e9', warn: '#38bdf8', danger: '#f472b6' },
surface: { primary: '#f8fafc', secondary: '#e2e8f0', card: '#ffffff' },
text: { primary: '#0f172a', secondary: '#475569', muted: '#94a3b8' },
},
fontFamily: {
sans: ['Noto Sans KR', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
display: ['Outfit', 'sans-serif'],
},
boxShadow: {
'sm': '0 2px 8px rgba(14, 165, 233, 0.06)',
'md': '0 4px 20px rgba(14, 165, 233, 0.08)',
'lg': '0 8px 40px rgba(14, 165, 233, 0.12)',
'glow': '0 0 30px rgba(14, 165, 233, 0.25)',
'blue': '0 4px 20px rgba(59, 130, 246, 0.3)',
},
animation: {
'shimmer': 'shimmer 3s ease-in-out infinite',
'pulse-slow': 'pulse 1.5s ease-in-out infinite',
'unlock-pulse': 'unlockPulse 0.6s ease-out',
'sparkle': 'sparkle 0.6s ease-out forwards',
'confetti-fall': 'confettiFall 2s ease-out forwards',
'fire-flicker': 'fireFlicker 0.3s ease-in-out infinite',
},
keyframes: {
shimmer: {
'0%, 100%': { transform: 'translate(-10%, -10%) rotate(0deg)' },
'50%': { transform: 'translate(10%, 10%) rotate(180deg)' },
},
unlockPulse: {
'0%': { transform: 'scale(1)', boxShadow: '0 0 0 0 rgba(251, 191, 36, 0.4)' },
'50%': { transform: 'scale(1.1)', boxShadow: '0 0 0 15px rgba(251, 191, 36, 0)' },
'100%': { transform: 'scale(1)', boxShadow: '0 0 0 0 rgba(251, 191, 36, 0)' },
},
sparkle: {
'0%, 100%': { opacity: '0', transform: 'scale(0) rotate(0deg)' },
'50%': { opacity: '1', transform: 'scale(1) rotate(180deg)' },
},
confettiFall: {
'0%': { opacity: '1', transform: 'translateY(-100px) rotate(0deg)' },
'100%': { opacity: '0', transform: 'translateY(100vh) rotate(720deg)' },
},
fireFlicker: {
'0%, 100%': { transform: 'scaleY(1) scaleX(1)' },
'25%': { transform: 'scaleY(1.1) scaleX(0.95)' },
'50%': { transform: 'scaleY(0.95) scaleX(1.05)' },
'75%': { transform: 'scaleY(1.05) scaleX(0.98)' },
},
},
},
},
}
</script>
<style>
.confetti {
position: absolute;
width: 10px;
height: 10px;
opacity: 0;
}
.achievement.just-unlocked {
animation: unlockPulse 0.6s ease-out;
}
.sparkle {
position: absolute;
font-size: 1rem;
animation: sparkle 0.6s ease-out forwards;
}
.toast.show {
opacity: 1 !important;
transform: translateX(-50%) translateY(0) !important;
}
.toggle-switch {
position: relative;
width: 56px;
height: 32px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
inset: 0;
background: #e2e8f0;
border-radius: 9999px;
transition: background 250ms ease;
}
.toggle-slider::before {
content: '';
position: absolute;
height: 24px;
width: 24px;
left: 4px;
bottom: 4px;
background: white;
border-radius: 50%;
transition: transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 2px 8px rgba(14, 165, 233, 0.06);
}
.toggle-switch input:checked + .toggle-slider {
background: linear-gradient(90deg, #3b82f6 0%, #0ea5e9 100%);
}
.toggle-switch input:checked + .toggle-slider::before {
transform: translateX(24px);
}
.achievement-tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%) translateY(0);
background: #0f172a;
color: #ffffff;
padding: 8px 12px;
border-radius: 8px;
font-size: 0.7rem;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 150ms ease;
z-index: 10;
}
.achievement-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #0f172a;
}
.achievement.locked:hover .achievement-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(-10px);
}
.achievement.unlocked .achievement-tooltip {
display: none;
}
.week-day-indicator {
width: 36px;
height: 36px;
border-radius: 12px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
background: #e2e8f0;
transition: all 250ms ease;
}
.week-day-indicator.completed {
background: linear-gradient(135deg, #0ea5e9 0%, #10b981 100%);
color: white;
box-shadow: 0 4px 12px rgba(52, 211, 153, 0.3);
}
.week-day-indicator.today {
border: 2px solid #3b82f6;
box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.08);
}
.achievement {
position: relative;
aspect-ratio: 1;
border-radius: 12px;
background: #e2e8f0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
padding: 8px;
transition: transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 250ms ease;
cursor: pointer;
}
.achievement.unlocked {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
box-shadow: 0 4px 20px rgba(251, 191, 36, 0.3);
}
.achievement.unlocked.gold {
background: linear-gradient(135deg, #fef3c7 0%, #fcd34d 100%);
}
.achievement.unlocked.silver {
background: linear-gradient(135deg, #f3f4f6 0%, #d1d5db 100%);
box-shadow: 0 4px 20px rgba(156, 163, 175, 0.3);
}
.achievement.unlocked.bronze {
background: linear-gradient(135deg, #fed7aa 0%, #fdba74 100%);
box-shadow: 0 4px 20px rgba(251, 146, 60, 0.3);
}
.achievement.locked {
opacity: 0.6;
}
.achievement:hover:not(.locked) {
transform: scale(1.05) translateY(-4px);
}
.achievement.locked:hover {
transform: scale(1.02);
}
.streak-icon.animate {
animation: fireFlicker 0.3s ease-in-out infinite;
}
</style>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen overflow-x-hidden antialiased">
<div class="max-w-[480px] mx-auto p-4 pb-[100px]">
<header class="flex items-center justify-between p-4 bg-surface-card shadow-sm sticky top-0 z-[100] mb-6 rounded-xl">
<div class="flex items-center gap-3">
<div class="relative w-10 h-10 bg-gradient-to-br from-primary to-secondary rounded-xl flex items-center justify-center shadow-blue">
<svg viewBox="0 0 24 24" fill="none" class="w-6 h-6 fill-white drop-shadow-md">
<defs>
<linearGradient id="logoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#06b6d4"/>
<stop offset="50%" stop-color="#3b82f6"/>
<stop offset="100%" stop-color="#0ea5e9"/>
</linearGradient>
</defs>
<path d="M6 3a3 3 0 013 3v2a3 3 0 01-3 3H4a2 2 0 00-2 2v6a2 2 0 002 2h2a3 3 0 013 3v2a3 3 0 01-3 3H6a3 3 0 01-3-3v-2a3 3 0 013-3h2a2 2 0 002-2V8a2 2 0 00-2-2H6a3 3 0 01-3-3V6a3 3 0 013-3z" fill="url(#logoGrad)"/>
<circle cx="12" cy="12" r="3" fill="white" opacity="0.3"/>
</svg>
</div>
<h1 class="font-display text-xl font-semibold">내 진행 상황</h1>
</div>
<div class="flex items-center gap-3">
<button class="w-11 h-11 rounded-xl bg-surface-secondary flex items-center justify-center cursor-pointer shadow-sm hover:-translate-y-0.5 hover:shadow-md active:translate-y-0 transition-all duration-150" onclick="window.location.href='index.html'">
<svg class="w-5 h-5 stroke-text-primary"><use href="#icon-arrow-left"></use></svg>
뒤로
</button>
</div>
</header>
<div class="bg-gradient-to-br from-primary to-secondary rounded-[28px] p-8 text-center relative overflow-hidden shadow-glow mb-6">
<div class="streak-icon text-5xl mb-2 animate-pulse-slow" id="streakIcon">🔥</div>
<div class="font-display text-7xl font-extrabold text-white leading-none drop-shadow-lg" id="streakCount">0</div>
<div class="text-xl font-bold text-white/95 uppercase tracking-[2px] mt-1">STREAK!</div>
<div class="text-white/90 text-sm mt-4" id="streakMessage">오늘 시작하세요!</div>
</div>
<div class="bg-surface-card rounded-[20px] p-5 mb-5 shadow-sm hover:-translate-y-0.5 hover:shadow-md transition-all duration-250">
<div class="text-xs font-semibold text-text-secondary uppercase tracking-wider mb-4 flex items-center gap-2">
<span class="w-1 h-4 bg-gradient-to-b from-primary to-secondary rounded-sm"></span>
주간 목표
</div>
<div class="mb-4">
<div class="flex justify-between items-center mb-3">
<span class="text-sm text-text-primary font-medium">이번 주 완료된 일수</span>
<span class="text-sm text-primary font-semibold"><span id="daysCompleted">0</span>/7</span>
</div>
<div class="h-3 bg-surface-secondary rounded-full overflow-hidden relative">
<div class="h-full bg-gradient-to-r from-primary to-accent rounded-full w-0 transition-all duration-1000 relative" id="progressFill"></div>
</div>
</div>
<div class="grid grid-cols-7 gap-2 mt-4" id="weekGrid">
</div>
</div>
<div class="bg-surface-card rounded-[20px] p-5 mb-5 shadow-sm hover:-translate-y-0.5 hover:shadow-md transition-all duration-250">
<div class="text-xs font-semibold text-text-secondary uppercase tracking-wider mb-4 flex items-center gap-2">
<span class="w-1 h-4 bg-gradient-to-b from-primary to-secondary rounded-sm"></span>
업적
</div>
<div class="grid grid-cols-4 gap-3" id="achievementsGrid">
</div>
</div>
<div class="bg-surface-card rounded-[20px] p-5 mb-5 shadow-sm hover:-translate-y-0.5 hover:shadow-md transition-all duration-250">
<div class="text-xs font-semibold text-text-secondary uppercase tracking-wider mb-4 flex items-center gap-2">
<span class="w-1 h-4 bg-gradient-to-b from-primary to-secondary rounded-sm"></span>
오늘의 팁
</div>
<div class="bg-gradient-to-br from-surface-secondary to-surface-card rounded-xl p-4 flex gap-3 items-start">
<span class="text-2xl shrink-0">💡</span>
<div class="flex-1">
<p class="text-sm text-text-primary leading-relaxed" id="tipText">작은 걸음이 큰 변화를 만듭니다! 오늘 5분만 투자하세요.</p>
<p class="text-xs text-text-muted mt-2">— 오늘의 동기</p>
</div>
</div>
</div>
<div class="flex items-center justify-between p-4 bg-surface-card rounded-[20px] shadow-sm">
<div class="flex items-center gap-3">
<span class="text-2xl">🔔</span>
<div>
<div class="text-sm font-medium text-text-primary">습관 알림</div>
<div class="text-xs text-text-muted mt-0.5">매일 알림 받기</div>
</div>
</div>
<label class="toggle-switch">
<input type="checkbox" id="habitToggle">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="fixed bottom-[100px] left-1/2 -translate-x-1/2 translate-y-[100px] bg-text-primary text-surface-card px-6 py-4 rounded-full text-sm font-medium shadow-lg opacity-0 transition-all duration-500 z-[100] flex items-center gap-2" id="toast">
<span id="toastIcon">🎉</span>
<span id="toastText">업적 달성!</span>
</div>
<div class="fixed top-0 left-0 w-full h-full pointer-events-none z-[1000] overflow-hidden" id="confettiContainer"></div>
<script>
// 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));
}
// UI Elements
const streakCount = document.getElementById('streakCount');
const streakIcon = document.getElementById('streakIcon');
const streakMessage = document.getElementById('streakMessage');
const progressFill = document.getElementById('progressFill');
const daysCompleted = document.getElementById('daysCompleted');
const weekGrid = document.getElementById('weekGrid');
const achievementsGrid = document.getElementById('achievementsGrid');
const habitToggle = document.getElementById('habitToggle');
const toast = document.getElementById('toast');
const toastIcon = document.getElementById('toastIcon');
const toastText = document.getElementById('toastText');
const confettiContainer = document.getElementById('confettiContainer');
const motivationalMessages = [
"불을 이어가세요!",
"오늘 불이 나고 있어요!",
"놀라운 일관성!",
"아무도 당신을 막을 수 없어요!",
"챔피언의 행동!"
];
const dailyTips = [
{ text: "작은 걸음이 큰 변화를 만듭니다! 오늘 5분만 투자하세요.", author: "일일 동기" },
{ text: "정돈된 공간은 정돈된 마음을 만듭니다. 작게 시작하세요!", author: "정리의 지혜" },
{ text: "정리된 모든 파일이 진보입니다.庆祝하세요!", author: "업적 달성" },
{ text: "미래의 당신이 오늘 정리한 것에 감사할 것입니다.", author: "타임 트래블러" },
{ text: "일관성이 완벽함을 이깁니다. 매일来吧!", author: "습관 마스터" }
];
// Animate streak count
function animateCount(element, target, duration = 1500) {
const start = 0;
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOut = 1 - Math.pow(1 - progress, 3);
const current = Math.floor(start + (target - start) * easeOut);
element.textContent = current;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
// Render week grid
function renderWeekGrid(progress) {
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
const today = new Date().getDay();
const mondayIndex = today === 0 ? 6 : today - 1;
weekGrid.innerHTML = days.map((day, i) => {
const completed = progress[i];
const isToday = i === mondayIndex;
return `
<div class="week-day">
<div class="week-day-label">${day}</div>
<div class="week-day-indicator ${completed ? 'completed' : ''} ${isToday ? 'today' : ''}">
${completed ? '✅' : (isToday ? '◉' : '')}
</div>
</div>
`;
}).join('');
}
// Render achievements
function renderAchievements(achievements) {
achievementsGrid.innerHTML = achievements.map(ach => `
<div class="achievement ${ach.unlocked ? `unlocked ${ach.tier}` : 'locked'}" data-id="${ach.id}">
<span class="achievement-icon">${ach.icon}</span>
<span class="achievement-name">${ach.name}</span>
<div class="achievement-tooltip">${ach.requirement}</div>
</div>
`).join('');
}
// Show toast notification
function showToast(icon, message) {
toastIcon.textContent = icon;
toastText.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Confetti celebration
function triggerConfetti() {
const colors = ['#6366f1', '#f472b6', '#34d399', '#fbbf24', '#818cf8', '#34d399'];
const confettiCount = 50;
for (let i = 0; i < confettiCount; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + 'vw';
confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
confetti.style.borderRadius = Math.random() > 0.5 ? '50%' : '0';
confetti.style.width = Math.random() * 10 + 5 + 'px';
confetti.style.height = confetti.style.width;
confetti.style.animation = `confettiFall ${Math.random() * 2 + 2}s ease-out forwards`;
confetti.style.animationDelay = Math.random() * 0.5 + 's';
confettiContainer.appendChild(confetti);
setTimeout(() => {
confetti.remove();
}, 4000);
}
}
// Trigger badge unlock animation
function animateBadgeUnlock(achievementId) {
const badge = document.querySelector(`.achievement[data-id="${achievementId}"]`);
if (badge) {
badge.classList.add('just-unlocked');
// Add sparkles
for (let i = 0; i < 6; i++) {
const sparkle = document.createElement('span');
sparkle.className = 'sparkle';
sparkle.textContent = '✨';
sparkle.style.left = Math.random() * 100 + '%';
sparkle.style.top = Math.random() * 100 + '%';
badge.appendChild(sparkle);
}
setTimeout(() => {
badge.classList.remove('just-unlocked');
badge.querySelectorAll('.sparkle').forEach(s => s.remove());
}, 600);
}
}
// Initialize page
function init() {
const data = loadData();
// Animate streak count
animateCount(streakCount, data.streak);
// Streak icon animation
if (data.streak > 0) {
streakIcon.classList.add('animate');
setTimeout(() => streakIcon.classList.remove('animate'), 1000);
}
// Update streak message
if (data.streak > 0) {
streakMessage.textContent = motivationalMessages[Math.floor(Math.random() * motivationalMessages.length)];
}
// Animate progress bar
const completedDays = data.weeklyProgress.filter(Boolean).length;
setTimeout(() => {
progressFill.style.width = (completedDays / 7 * 100) + '%';
}, 300);
daysCompleted.textContent = completedDays;
// Render week grid
renderWeekGrid(data.weeklyProgress);
// Render achievements
renderAchievements(data.achievements);
// Set habit toggle state
habitToggle.checked = data.habitReminderEnabled;
// Random daily tip
const randomTip = dailyTips[Math.floor(Math.random() * dailyTips.length)];
document.getElementById('tipText').textContent = `"${randomTip.text}"`;
document.querySelector('.tip-author').textContent = `${randomTip.author}`;
// Habit toggle handler
habitToggle.addEventListener('change', (e) => {
data.habitReminderEnabled = e.target.checked;
saveData(data);
showToast(e.target.checked ? '🔔' : '🔕', e.target.checked ? 'Reminders enabled!' : 'Reminders disabled');
});
// Achievement click handler (for demo unlock)
achievementsGrid.addEventListener('click', (e) => {
const achievement = e.target.closest('.achievement');
if (achievement && !achievement.classList.contains('unlocked')) {
// Simulate unlock for demo
const achId = achievement.dataset.id;
const achData = data.achievements.find(a => a.id === achId);
if (achData) {
achData.unlocked = true;
saveData(data);
animateBadgeUnlock(achId);
triggerConfetti();
showToast(achData.icon, `${achData.name} unlocked!`);
renderAchievements(data.achievements);
}
}
});
}
// Run on load
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>

View File

@@ -1,414 +0,0 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>스와이프 모드 - Chakmate</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: { DEFAULT: '#3b82f6', light: '#60a5fa', dark: '#1d4ed8' },
secondary: { DEFAULT: '#06b6d4', light: '#22d3ee' },
accent: { DEFAULT: '#0ea5e9', warn: '#38bdf8', danger: '#f472b6' },
surface: { primary: '#f8fafc', secondary: '#e2e8f0', card: '#ffffff' },
text: { primary: '#0f172a', secondary: '#475569', muted: '#94a3b8' },
},
fontFamily: {
sans: ['Noto Sans KR', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
display: ['Outfit', 'sans-serif'],
},
boxShadow: {
'sm': '0 2px 8px rgba(14, 165, 233, 0.06)',
'md': '0 4px 20px rgba(14, 165, 233, 0.08)',
'lg': '0 8px 40px rgba(14, 165, 233, 0.12)',
'glow': '0 0 30px rgba(14, 165, 233, 0.25)',
'blue': '0 4px 20px rgba(59, 130, 246, 0.3)',
},
animation: {
'float': 'float 3s ease-in-out infinite',
'fade-slide-up': 'fadeSlideUp 0.5s ease forwards',
'slide-in': 'slideIn 0.5s ease forwards',
'bounce-in': 'bounceIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards',
'pulse-slow': 'pulse 2s ease-in-out infinite',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
fadeSlideUp: {
from: { opacity: '0', transform: 'translateY(20px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
slideIn: {
from: { opacity: '0', transform: 'translateY(30px)' },
to: { opacity: '1', transform: 'translateY(0)' },
},
bounceIn: {
'0%': { transform: 'scale(0)' },
'50%': { transform: 'scale(1.1)' },
'100%': { transform: 'scale(1)' },
},
pulse: {
'0%, 100%': { opacity: '0.5', transform: 'scale(1)' },
'50%': { opacity: '1', transform: 'scale(1.05)' },
},
shimmer: {
'0%': { transform: 'translateX(-100%) translateY(-100%)' },
'100%': { transform: 'translateX(100%) translateY(100%)' },
},
cardExitLeft: {
to: { transform: 'translateX(-150%) rotate(-15deg)', opacity: '0' },
},
cardExitRight: {
to: { transform: 'translateX(150%) rotate(15deg)', opacity: '0' },
},
cardEnter: {
from: { transform: 'scale(0.9) translateY(30px)', opacity: '0' },
to: { transform: 'scale(1) translateY(0)', opacity: '1' },
},
},
},
},
}
</script>
<style>
@keyframes shimmer {
0% { transform: translateX(-100%) translateY(-100%); }
100% { transform: translateX(100%) translateY(100%); }
}
@keyframes cardExitLeft {
to { transform: translateX(-150%) rotate(-15deg); opacity: 0; }
}
@keyframes cardExitRight {
to { transform: translateX(150%) rotate(15deg); opacity: 0; }
}
@keyframes cardEnter {
from { transform: scale(0.9) translateY(30px); opacity: 0; }
to { transform: scale(1) translateY(0); opacity: 1; }
}
</style>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased">
<!-- Heroicons SVG Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
<symbol id="icon-arrow-uturn-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 7v6h6M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
</symbol>
<symbol id="icon-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</symbol>
<symbol id="icon-calendar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>
</symbol>
</svg>
<header class="flex items-center justify-between px-6 py-4 bg-surface-card shadow-sm sticky top-0 z-50">
<button class="flex items-center gap-2 px-4 py-2 bg-surface-secondary rounded-full text-text-secondary text-sm font-medium transition-colors duration-150 hover:bg-primary-light hover:text-white" onclick="window.location.href='index.html'">
<svg class="w-5 h-5"><use href="#icon-arrow-left"></use></svg>
뒤로
</button>
<h1 class="font-display text-lg font-semibold text-text-primary">스와이프 모드</h1>
<button class="flex items-center gap-2 px-5 py-3 bg-gradient-to-r from-primary to-primary-dark rounded-full text-white text-sm font-semibold shadow-md transition-all duration-250 hover:-translate-y-0.5 hover:shadow-lg disabled:opacity-40 disabled:cursor-not-allowed disabled:transform-none" id="undoBtn" disabled>
<svg class="w-4 h-4"><use href="#icon-arrow-uturn-left"></use></svg>
실행 취소
</button>
</header>
<main class="flex flex-col min-h-[calc(100vh-70px)] px-6">
<div class="text-center mb-8">
<h2 class="text-[28px] font-bold text-text-primary mb-1" id="currentFileName">project_proposal.pdf</h2>
<p class="text-text-muted text-sm">왼쪽으로 스와이프하면 삭제, 오른쪽으로 스와이프하면 보관</p>
</div>
<div class="flex-1 flex flex-col items-center justify-center relative py-8">
<div class="relative w-full max-w-[320px] h-[420px]" id="cardStack"></div>
<div class="hidden flex-col items-center justify-center text-center p-16" id="emptyState">
<div class="w-[120px] h-[120px] bg-gradient-to-br from-primary to-secondary rounded-full flex items-center justify-center mb-6 animate-pulse-slow shadow-glow">
<svg class="w-[60px] h-[60px] text-white"><use href="#icon-check"></use></svg>
</div>
<h2 class="text-[28px] font-bold text-text-primary mb-3">모두 완료!</h2>
<p class="text-text-muted text-base mb-6">모든 파일을 검토했습니다</p>
<button class="flex items-center gap-2 px-6 py-4 bg-gradient-to-r from-primary to-primary-dark rounded-full text-white text-base font-semibold shadow-md transition-all duration-250 hover:-translate-y-0.5 hover:shadow-lg" onclick="resetFiles()">
다시 시작
</button>
</div>
</div>
<div class="flex justify-center gap-6 mt-8" id="actionButtons">
<button class="w-[80px] h-[80px] rounded-full bg-gradient-to-br from-accent-danger to-red-500 text-white flex items-center justify-center shadow-md transition-transform duration-500 active:scale-90" onclick="handleSwipe('delete')">
<svg class="w-9 h-9"><use href="#icon-trash"></use></svg>
</button>
<button class="w-[80px] h-[80px] rounded-full bg-gradient-to-br from-accent to-emerald-500 text-white flex items-center justify-center shadow-md transition-transform duration-500 active:scale-90" onclick="handleSwipe('keep')">
<svg class="w-9 h-9"><use href="#icon-check"></use></svg>
</button>
</div>
</main>
<footer class="p-5 bg-surface-card rounded-t-3xl shadow-[0_-4px_20px_rgba(30,27,46,0.08)]">
<div class="flex items-center gap-4">
<div class="flex-1 h-2 bg-surface-secondary rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-primary to-secondary rounded-full transition-all duration-500" id="progressFill" style="width: 0%"></div>
</div>
<div class="font-display text-sm font-semibold text-primary min-w-[60px] text-right" id="progressText">0%</div>
</div>
</footer>
<div class="fixed top-[90px] left-1/2 -translate-x-1/2 bg-text-primary text-surface-card px-6 py-3 rounded-full text-sm font-medium shadow-lg opacity-0 pointer-events-none transition-all duration-250 z-[200]" id="toast">파일 보관됨</div>
<script>
const mockFiles = [
{ id: 1, name: 'project_proposal.pdf', type: 'pdf', size: '2.4 MB', date: '2024-01-15', icon: '📄' },
{ id: 2, name: 'meeting_notes.docx', type: 'doc', size: '156 KB', date: '2024-01-14', icon: '📝' },
{ id: 3, name: 'vacation_photo.jpg', type: 'image', size: '3.2 MB', date: '2024-01-13', icon: '🖼️' },
{ id: 4, name: 'budget_2024.xlsx', type: 'excel', size: '89 KB', date: '2024-01-12', icon: '📊' },
{ id: 5, name: 'presentation_final.pptx', type: 'ppt', size: '5.2 MB', date: '2024-01-11', icon: '📽️' },
{ id: 6, name: 'backup_2023.zip', type: 'archive', size: '156 MB', date: '2024-01-10', icon: '📦' },
{ id: 7, name: 'contract_signed.pdf', type: 'pdf', size: '1.1 MB', date: '2024-01-09', icon: '📋' },
];
let files = [...mockFiles];
let historyStack = [];
let currentIndex = 0;
const cardStack = document.getElementById('cardStack');
const emptyState = document.getElementById('emptyState');
const actionButtons = document.getElementById('actionButtons');
const undoBtn = document.getElementById('undoBtn');
const toast = document.getElementById('toast');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const currentFileName = document.getElementById('currentFileName');
function getFileTypeInfo(type) {
const types = {
pdf: { label: 'PDF', color: '#ef4444' },
doc: { label: 'Doc', color: '#3b82f6' },
image: { label: 'Image', color: '#8b5cf6' },
excel: { label: 'Excel', color: '#10b981' },
ppt: { label: 'PPT', color: '#f97316' },
archive: { label: 'Archive', color: '#6b7280' },
};
return types[type] || { label: 'File', color: '#6366f1' };
}
function createCard(file) {
const typeInfo = getFileTypeInfo(file.type);
return `
<div class="file-card absolute w-full h-full bg-surface-card rounded-[28px] shadow-lg flex flex-col overflow-hidden touch-pan-y cursor-grab select-none transition-transform duration-100" data-id="${file.id}" style="transform: translateX(0) rotate(0deg);">
<div class="card-overlay absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-150 pointer-events-none rounded-[28px] z-10" id="overlayDelete">
<span class="font-display text-[28px] font-bold text-white uppercase tracking-[3px]">삭제</span>
</div>
<div class="card-overlay absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-150 pointer-events-none rounded-[28px] z-10" id="overlayKeep">
<span class="font-display text-[28px] font-bold text-white uppercase tracking-[3px]">보관</span>
</div>
<div class="card-header px-6 py-5 bg-gradient-to-br from-primary to-primary-dark text-white flex items-center justify-between">
<div class="flex items-center gap-2 bg-white/20 px-3 py-2 rounded-full text-xs font-semibold uppercase">
<span class="text-base">${file.icon}</span>
<span>${typeInfo.label}</span>
</div>
<span class="text-xs opacity-90">${file.size}</span>
</div>
<div class="card-body flex-1 flex flex-col items-center justify-center px-8">
<div class="w-[120px] h-[120px] bg-surface-secondary rounded-3xl flex items-center justify-center mb-6 relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-transparent via-white/30 to-transparent animate-shimmer"></div>
<span class="text-[64px] relative z-10">${file.icon}</span>
</div>
<h3 class="font-display text-xl font-semibold text-text-primary text-center mb-3 leading-tight break-all">${file.name}</h3>
<p class="text-text-muted text-sm flex items-center gap-2">
<svg class="w-4 h-4"><use href="#icon-calendar"></use></svg>
${file.date}
</p>
</div>
<div class="card-footer px-6 py-4 bg-surface-secondary flex justify-center gap-6">
<div class="flex items-center gap-2 text-text-muted text-xs">
<span class="text-lg text-text-secondary">←</span>
<span>삭제</span>
</div>
<div class="flex items-center gap-2 text-text-muted text-xs">
<span>보관</span>
<span class="text-lg text-text-secondary">→</span>
</div>
</div>
</div>
`;
}
function renderCards() {
if (files.length === 0) {
cardStack.innerHTML = '';
emptyState.classList.remove('hidden');
emptyState.classList.add('flex');
actionButtons.style.display = 'none';
currentFileName.textContent = '모든 파일 검토 완료';
return;
}
emptyState.classList.add('hidden');
emptyState.classList.remove('flex');
actionButtons.style.display = 'flex';
const topFile = files[0];
currentFileName.textContent = topFile.name;
cardStack.innerHTML = createCard(topFile);
const card = cardStack.querySelector('.file-card');
initSwipe(card);
}
let startX = 0;
let currentX = 0;
let isDragging = false;
function initSwipe(card) {
if (!card) return;
card.addEventListener('mousedown', (e) => startDrag(e));
card.addEventListener('touchstart', (e) => startDrag(e), { passive: true });
document.addEventListener('mousemove', (e) => moveDrag(e));
document.addEventListener('touchmove', (e) => moveDrag(e), { passive: true });
document.addEventListener('mouseup', () => endDrag(card));
document.addEventListener('touchend', () => endDrag(card));
}
function startDrag(e) {
isDragging = true;
startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
currentX = 0;
}
function moveDrag(e) {
if (!isDragging) return;
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
currentX = clientX - startX;
const card = cardStack.querySelector('.file-card');
if (!card) return;
const rotation = currentX * 0.05;
card.style.transform = `translateX(${currentX}px) rotate(${rotation}deg)`;
const overlayDelete = card.querySelector('#overlayDelete');
const overlayKeep = card.querySelector('#overlayKeep');
if (currentX < -50) {
overlayDelete.style.opacity = Math.min(Math.abs(currentX) / 100, 1);
overlayDelete.classList.add('bg-gradient-to-br', 'from-red-400/95', 'to-red-600/95');
overlayKeep.style.opacity = 0;
} else if (currentX > 50) {
overlayKeep.style.opacity = Math.min(currentX / 100, 1);
overlayKeep.classList.add('bg-gradient-to-br', 'from-emerald-400/95', 'to-emerald-600/95');
overlayDelete.style.opacity = 0;
} else {
overlayDelete.style.opacity = 0;
overlayKeep.style.opacity = 0;
}
}
function endDrag(card) {
if (!isDragging) return;
isDragging = false;
if (currentX < -150) {
animateCard(card, 'left');
} else if (currentX > 150) {
animateCard(card, 'right');
} else {
card.style.transform = 'translateX(0) rotate(0deg)';
const overlayDelete = card.querySelector('#overlayDelete');
const overlayKeep = card.querySelector('#overlayKeep');
overlayDelete.style.opacity = 0;
overlayKeep.style.opacity = 0;
}
currentX = 0;
}
function animateCard(card, direction) {
if (direction === 'left') {
card.classList.add('animate-card-exit-left');
card.style.opacity = '0';
} else {
card.classList.add('animate-card-exit-right');
card.style.opacity = '0';
}
setTimeout(() => {
handleSwipe(direction === 'left' ? 'delete' : 'keep');
}, 300);
}
function handleSwipe(action) {
if (files.length === 0) return;
const deletedFile = files.shift();
historyStack.push(deletedFile);
undoBtn.disabled = false;
updateProgress();
renderCards();
showToast(action === 'delete' ? '파일 삭제됨' : '파일 보관됨');
}
function undo() {
if (historyStack.length === 0) return;
const restoredFile = historyStack.pop();
files.unshift(restoredFile);
undoBtn.disabled = historyStack.length === 0;
updateProgress();
renderCards();
showToast('실행 취소됨');
}
function updateProgress() {
const total = mockFiles.length;
const current = files.length;
const progress = Math.round(((total - current) / total) * 100);
progressFill.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
}
function showToast(message) {
toast.textContent = message;
toast.classList.add('opacity-100', 'translate-y-0');
toast.classList.remove('opacity-0', '-translate-y-5');
setTimeout(() => {
toast.classList.remove('opacity-100', 'translate-y-0');
toast.classList.add('opacity-0', '-translate-y-5');
}, 2000);
}
function resetFiles() {
files = [...mockFiles];
historyStack = [];
undoBtn.disabled = true;
updateProgress();
renderCards();
showToast('초기화됨');
}
undoBtn.addEventListener('click', undo);
renderCards();
updateProgress();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -23,3 +23,6 @@ serde = { version = "1.0", features = ["derive"] }
log = "0.4"
tauri = { version = "2.11.2", features = [] }
tauri-plugin-log = "2"
tauri-plugin-store = "2"
tauri-plugin-dialog = "2"
tauri-plugin-fs = "2"

View File

@@ -6,6 +6,17 @@
"main"
],
"permissions": [
"core:default"
"core:default",
"core:window:default",
"core:window:allow-close",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"core:window:allow-is-maximized",
"core:window:allow-start-dragging",
"store:default",
"dialog:default",
"fs:default",
"fs:allow-read-dir",
"fs:allow-stat"
]
}
}

View File

@@ -1,6 +1,10 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_log::Builder::default().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -20,7 +20,9 @@
"minHeight": 600,
"center": true,
"resizable": true,
"fullscreen": false
"fullscreen": false,
"decorations": false,
"transparent": true
}
],
"security": {
@@ -28,7 +30,7 @@
}
},
"bundle": {
"active": true,
"active": false,
"targets": "all",
"icon": [
"icons/32x32.png",

View File

@@ -0,0 +1,22 @@
<div id="custom-titlebar" class="custom-titlebar" data-tauri-drag-region>
<div class="titlebar-left" data-tauri-drag-region>
<img src="../assets/logo.svg" alt="Chakmate" class="titlebar-logo" data-tauri-drag-region>
<span class="titlebar-title" data-tauri-drag-region>Chakmate</span>
</div>
<div class="titlebar-center" data-tauri-drag-region></div>
<div class="titlebar-controls">
<button id="titlebar-minimize" class="titlebar-btn" aria-label="최소화">
<svg width="12" height="12" viewBox="0 0 12 12"><rect y="5" width="12" height="2" fill="currentColor"/></svg>
</button>
<button id="titlebar-maximize" class="titlebar-btn" aria-label="최대화">
<svg id="maximize-icon" width="12" height="12" viewBox="0 0 12 12"><rect x="1" y="1" width="10" height="10" stroke="currentColor" stroke-width="2" fill="none"/></svg>
<svg id="restore-icon" width="12" height="12" viewBox="0 0 12 12" style="display:none"><path d="M3 1h8v8h-2v2H1V3h2V1zm1 3v5h5V4H4z" fill="currentColor"/></svg>
</button>
<button id="titlebar-close" class="titlebar-btn titlebar-btn-close" aria-label="닫기">
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="2"/></svg>
</button>
</div>
</div>

16
src/index.html Normal file
View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Chakmate</title>
</head>
<body>
<script type="module">
import { AppState } from './scripts/shared/state.js';
(async () => {
const complete = await AppState.isOnboardingComplete();
location.href = complete ? 'pages/scene_dashboard.html' : 'pages/scene_onboarding.html';
})();
</script>
</body>
</html>

View File

@@ -1,457 +0,0 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chakmate - 지능형 디지털 자산 관리</title>
<link rel="stylesheet" href="./styles/main.css">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="icon-layers" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</symbol>
<symbol id="icon-shield-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="M9 12l2 2 4-4"/>
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"/>
<path d="M12 5l7 7-7 7"/>
</symbol>
<symbol id="icon-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</symbol>
<symbol id="icon-fire" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z"/>
<path d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z"/>
</symbol>
<symbol id="icon-cog" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 15a3 3 0 100-6 3 3 0 000 6z"/>
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>
</symbol>
<symbol id="icon-document" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
<path d="M14 2v6h6"/>
<path d="M16 13H8"/>
<path d="M16 17H8"/>
<path d="M10 9H8"/>
</symbol>
<symbol id="icon-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
</symbol>
<symbol id="icon-bolt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</symbol>
<symbol id="icon-light-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
</symbol>
<symbol id="icon-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
</symbol>
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5"/>
<path d="M12 19l-7-7 7-7"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18l6-6-6-6"/>
</symbol>
<symbol id="icon-x-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6L6 18"/>
<path d="M6 6l12 12"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 6L9 17l-5-5"/>
</symbol>
<symbol id="icon-photo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<polyline points="21 15 16 10 5 21"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/>
<path d="M7 10l5 5 5-5"/>
<path d="M12 15V3"/>
</symbol>
<symbol id="icon-undo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 10h10a5 5 0 015 5v0a5 5 0 01-5 5H3"/>
<path d="M3 10l4-4"/>
<path d="M3 10l4 4"/>
</symbol>
</svg>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<!-- Onboarding Screen -->
<div id="onboarding" class="screen active">
<div class="max-w-[420px] w-full text-center flex flex-col items-center justify-center min-h-screen p-4 gap-4 lg:flex-row lg:max-w-full lg:justify-center lg:gap-8">
<div class="flex flex-col items-center justify-center lg:flex-shrink-0">
<div class="logo w-64 h-64 mx-auto mb-6 flex items-center justify-center animate-float lg:w-48 lg:h-48">
<img src="assets/logo.svg" alt="Chakmate Logo" class="w-full h-full object-contain">
</div>
<h1 class="brand-name font-display text-4xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent mb-2 lg:text-3xl">Chakmate</h1>
<p class="tagline text-text-secondary text-base mb-10 lg:mb-0">지능형 디지털 자산 관리 솔루션</p>
</div>
<div class="flex flex-col items-center gap-4 lg:flex-col">
<div class="steps-indicator flex gap-2 justify-center mb-8 lg:mb-0">
<div class="step-dot active w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="1"></div>
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="2"></div>
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="3"></div>
</div>
<!-- Step 1: Welcome -->
<div class="onboarding-step active" data-step="1">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-layers"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">파일 정리, 지능적으로</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">AI가 파일 사용 패턴을 분석하여 당신만의 최적화된 폴더 구조를 제안합니다. 더 이상 수동 정리는 필요 없습니다.</p>
<div class="privacy-badge inline-flex items-center gap-2 bg-accent text-white px-4 py-2 rounded-full text-sm font-medium mb-6">
<svg class="w-4 h-4 stroke-white fill-none"><use href="#icon-shield-check"></use></svg>
<span>기기 내 단독 처리 • 완벽한 개인정보 보호</span>
</div>
</div>
<!-- Step 2: Swipe -->
<div class="onboarding-step hidden" data-step="2">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-bolt"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">스вай프로 빠른 정리</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">좌우 스와이프로 파일을 빠르게 분류하세요. 한번의 제스처로 파일의 귀 fate를 결정합니다.</p>
<div class="swipe-hints flex justify-between w-full max-w-[280px] mx-auto text-text-muted text-sm">
<div class="flex items-center gap-1">
<svg class="w-[16px] h-[16px] stroke-current fill-none"><use href="#icon-arrow-left"></use></svg>
<span>삭제</span>
</div>
<div class="flex items-center gap-1">
<span>보관</span>
<svg class="w-[16px] h-[16px] stroke-accent-warn fill-none stroke-2"><use href="#icon-arrow-right"></use></svg>
</div>
</div>
</div>
<!-- Step 3: AI -->
<div class="onboarding-step hidden" data-step="3">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-light-bulb"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">AI가 제안하는 구조</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">당신의 파일 사용 패턴을 학습하여 최적인 폴더 구조를 제안해 드립니다.</p>
</div>
<button class="btn btn-primary w-full max-w-[280px] mx-auto lg:mx-0" id="start-organizing">
<span>정리 시작하기</span>
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-right"></use></svg>
</button>
</div>
</div>
</div>
<!-- Navigation -->
<nav class="fixed bottom-0 left-0 right-0 bg-surface-card border-t border-surface-secondary z-[100]">
<div class="max-w-[600px] mx-auto flex justify-around py-3">
<button class="nav-btn flex flex-col items-center gap-1 px-6 py-2 rounded-2xl transition-all duration-250 active" id="nav-dashboard">
<svg class="w-6 h-6 stroke-current fill-none"><use href="#icon-layers"></use></svg>
<span class="text-xs font-medium"></span>
</button>
<button class="nav-btn flex flex-col items-center gap-1 px-6 py-2 rounded-2xl transition-all duration-250 text-text-muted" id="nav-swipe">
<svg class="w-6 h-6 stroke-current fill-none"><use href="#icon-bolt"></use></svg>
<span class="text-xs font-medium"><EFBFBD>ipe</span>
</button>
<button class="nav-btn flex flex-col items-center gap-1 px-6 py-2 rounded-2xl transition-all duration-250 text-text-muted" id="nav-ai-suggestion">
<svg class="w-6 h-6 stroke-current fill-none"><use href="#icon-light-bulb"></use></svg>
<span class="text-xs font-medium">AI 제안</span>
</button>
<button class="nav-btn flex flex-col items-center gap-1 px-6 py-2 rounded-2xl transition-all duration-250 text-text-muted" id="nav-settings">
<svg class="w-6 h-6 stroke-current fill-none"><use href="#icon-cog"></use></svg>
<span class="text-xs font-medium">설정</span>
</button>
</div>
</nav>
<!-- Dashboard Screen -->
<div id="dashboard" class="screen">
<header class="p-4 flex items-center justify-between">
<div>
<p class="text-text-muted text-sm">환영합니다</p>
<h1 class="text-xl font-display font-bold">대시보드</h1>
</div>
<div class="flex items-center gap-3">
<div class="streak-badge flex items-center gap-2 bg-surface-card px-4 py-2 rounded-full shadow-sm">
<svg class="w-5 h-5 stroke-accent-warn fill-none text-orange-500"><use href="#icon-fire"></use></svg>
<span class="font-bold text-accent-warn" id="streak-count">7</span>
<span class="text-text-muted text-sm"></span>
</div>
</div>
</header>
<!-- Stats Cards -->
<div class="stats-container p-4 flex flex-col gap-4">
<div class="stats-row flex gap-4">
<div class="stat-card flex-1 bg-surface-card rounded-[20px] p-5 shadow-sm">
<div class="stat-icon w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center mb-3">
<svg class="w-5 h-5 stroke-primary fill-none"><use href="#icon-folder"></use></svg>
</div>
<p class="stat-value text-2xl font-display font-bold text-text-primary" id="files-count">248</p>
<p class="stat-label text-text-muted text-sm">정리된 파일</p>
</div>
<div class="stat-card flex-1 bg-surface-card rounded-[20px] p-5 shadow-sm">
<div class="stat-icon w-10 h-10 rounded-full bg-accent/10 flex items-center justify-center mb-3">
<svg class="w-5 h-5 stroke-accent fill-none"><use href="#icon-document"></use></svg>
</div>
<p class="stat-value text-2xl font-display font-bold text-text-primary" id="folders-count">12</p>
<p class="stat-label text-text-muted text-sm">관리된 폴더</p>
</div>
</div>
<div class="suggestions-card bg-surface-card rounded-[20px] p-5 shadow-md">
<div class="suggestions-header flex items-center justify-between mb-4">
<h2 class="text-lg font-display font-bold">AI 제안</h2>
<button class="text-sm text-primary font-medium hover:underline" id="apply-suggestions">적용하기</button>
</div>
<div class="suggestions-list flex flex-col gap-3" id="suggestions-list"></div>
</div>
</div>
<!-- Achievements -->
<div class="achievements-section p-4">
<div class="section-header flex items-center justify-between mb-4">
<h2 class="text-lg font-display font-bold">업적</h2>
<div class="flex items-center gap-1">
<svg class="w-5 h-5 stroke-amber-500 fill-none text-yellow-400"><use href="#icon-star"></use></svg>
<span class="text-sm font-medium text-text-muted">4/8</span>
</div>
</div>
<div class="achievements-grid grid grid-cols-4 gap-3" id="achievements-grid"></div>
</div>
</div>
<!-- Swipe Screen -->
<div id="swipe" class="screen">
<header class="p-4 flex items-center justify-between">
<div>
<h1 class="text-xl font-display font-bold">스와이프 정리</h1>
<p class="text-text-muted text-sm" id="swipe-progress">1/10 파일</p>
</div>
<button class="btn btn-icon btn-secondary" id="swipe-undo">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-undo"></use></svg>
</button>
</header>
<div class="swipe-container flex flex-col items-center justify-center min-h-[calc(100vh-200px)] p-4">
<div class="card-container w-full max-w-[320px] relative" id="card-container">
<div class="current-card absolute inset-0 bg-surface-card rounded-[24px] shadow-lg p-6 flex flex-col" id="current-card">
<div class="card-header flex items-center justify-between mb-4">
<div class="file-icon w-12 h-12 rounded-xl flex items-center justify-center bg-primary/10">
<svg class="w-6 h-6 stroke-primary fill-none" id="card-type-icon"><use href="#icon-document"></use></svg>
</div>
<div class="file-meta text-right">
<p class="file-name font-medium text-text-primary" id="card-name">IMG_20240115_123456.jpg</p>
<p class="file-size text-text-muted text-sm" id="card-size">3.2 MB</p>
</div>
</div>
<div class="card-body flex-1 flex flex-col items-center justify-center">
<div class="card-preview w-24 h-24 rounded-xl bg-surface-secondary flex items-center justify-center mb-4 overflow-hidden">
<svg class="w-12 h-12 stroke-text-muted fill-none" id="card-preview"><use href="#icon-photo"></use></svg>
</div>
<p class="card-folder text-text-muted text-sm" id="card-folder">📷 사진</p>
<p class="card-date text-text-muted text-xs mt-1" id="card-date">2024년 1월 15일</p>
</div>
<div class="card-hints flex justify-between w-full text-text-muted text-sm mt-4">
<span>삭제</span>
<span>보관</span>
</div>
</div>
</div>
<div class="swipe-actions flex gap-6 mt-8">
<button class="btn btn-icon w-16 h-16 rounded-full bg-surface-card shadow-lg border-2 border-accent-danger/30 hover:border-accent-danger transition-colors" id="swipe-delete">
<svg class="w-8 h-8 stroke-accent-danger fill-none"><use href="#icon-x-mark"></use></svg>
</button>
<button class="btn btn-icon w-16 h-16 rounded-full bg-surface-card shadow-lg border-2 border-accent/30 hover:border-accent transition-colors" id="swipe-keep">
<svg class="w-8 h-8 stroke-accent fill-none"><use href="#icon-check"></use></svg>
</button>
</div>
<div class="swipe-hints flex justify-between w-full max-w-[300px] text-text-muted text-sm">
<div class="swipe-hint flex items-center gap-2">
<svg class="w-[18px] h-[18px] stroke-current fill-none"><use href="#icon-arrow-left"></use></svg>
<span>삭제</span>
</div>
<div class="swipe-hint flex items-center gap-2">
<span>보관</span>
<svg class="w-[18px] h-[18px] stroke-accent-warn fill-none stroke-2"><use href="#icon-arrow-right"></use></svg>
</div>
</div>
</div>
</div>
<!-- AI Suggestion Screen -->
<div id="ai-suggestion" class="screen">
<div class="ai-container max-w-[900px] w-full mx-auto">
<div class="ai-header text-center mb-8">
<h2 class="ai-title text-3xl mb-3 font-display">AI 구조 제안</h2>
<p class="ai-subtitle text-text-secondary text-lg">분석된 파일 패턴을 기반으로 최적화된 폴더 구조를 제안합니다</p>
</div>
<div class="comparison-view grid grid-cols-1 md:grid-cols-[1fr_auto_1fr] gap-6 items-start mb-8">
<div class="comparison-panel bg-surface-card rounded-[28px] shadow-md p-6">
<div class="comparison-header flex items-center gap-3 mb-5 pb-4 border-b border-surface-secondary">
<span class="comparison-badge before px-3 py-1 rounded-full text-xs font-bold uppercase bg-red-500/15 text-accent-danger">현재</span>
<span>현재 폴더 구조</span>
</div>
<div class="comparison-tree flex flex-col gap-2" id="before-tree"></div>
</div>
<div class="comparison-arrow hidden md:flex items-center justify-center pt-16">
<svg class="w-12 h-12 stroke-primary fill-none animate-pulse-slow"><use href="#icon-arrow-right"></use></svg>
</div>
<div class="comparison-panel bg-surface-card rounded-[28px] shadow-md p-6">
<div class="comparison-header flex items-center gap-3 mb-5 pb-4 border-b border-surface-secondary">
<span class="comparison-badge after px-3 py-1 rounded-full text-xs font-bold uppercase bg-emerald-500/15 text-accent">제안</span>
<span>정리된 구조</span>
</div>
<div class="comparison-tree flex flex-col gap-2" id="after-tree"></div>
</div>
</div>
<div class="ai-actions flex justify-center gap-4">
<button class="btn btn-secondary" id="ai-cancel">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-x-mark"></use></svg>
<span>취소</span>
</button>
<button class="btn btn-primary" id="ai-apply">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-check"></use></svg>
<span>구조 적용하기</span>
</button>
</div>
</div>
</div>
<!-- Settings Screen -->
<div id="settings" class="screen">
<div class="settings-container max-w-[600px] w-full mx-auto">
<div class="settings-header mb-8">
<h2 class="settings-title text-3xl mb-2 font-display">설정</h2>
<p class="settings-subtitle text-text-secondary">앱 환경 사용자 지정</p>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">알림</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">정리 알림</div>
<div class="setting-desc text-sm text-text-secondary">매일 일정한 시간에 파일 정리를 안내합니다</div>
</div>
<div class="toggle active w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-notifications"></div>
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">습관 형성 알림</div>
<div class="setting-desc text-sm text-text-secondary">정리 습관 형성을 돕는 동기를 부여합니다</div>
</div>
<div class="toggle w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-habits"></div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">정리 습관</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">자동 정리</div>
<div class="setting-desc text-sm text-text-secondary">자동으로 파일을 제안된 구조로 정리합니다</div>
</div>
<div class="toggle w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-autoOrganize"></div>
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">삭제 확인</div>
<div class="setting-desc text-sm text-text-secondary">파일 삭제 시 확인 메시지를 표시합니다</div>
</div>
<div class="toggle active w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-confirmDelete"></div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">테마</h3>
<div class="theme-options flex gap-3">
<div class="theme-option light w-12 h-12 rounded-[12px] cursor-pointer border-[3px] border-transparent transition-all duration-250 hover:scale-105 flex items-center justify-center bg-gradient-to-br from-[#f8fafc] to-[#e2e8f0]" data-theme="light">
<svg class="w-5 h-5 stroke-text-primary fill-none"><use href="#icon-sun"></use></svg>
</div>
<div class="theme-option dark w-12 h-12 rounded-[12px] cursor-pointer border-[3px] border-transparent transition-all duration-250 hover:scale-105 flex items-center justify-center bg-gradient-to-br from-[#0f0e17] to-[#252336]" data-theme="dark">
<svg class="w-5 h-5 stroke-text-primary fill-none"><use href="#icon-moon"></use></svg>
</div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">데이터</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">데이터 내보내기</div>
<div class="setting-desc text-sm text-text-secondary">정리된 파일 구조를 JSON으로 내보냅니다</div>
</div>
<button class="btn btn-secondary" id="export-data">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-download"></use></svg>
<span>내보내기</span>
</button>
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">데이터 초기화</div>
<div class="setting-desc text-sm text-text-secondary">모든 데이터를 초기 상태로 되돌립니다</div>
</div>
<button class="btn btn-secondary text-accent-danger" id="reset-data">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-trash"></use></svg>
<span>초기화</span>
</button>
</div>
</div>
</div>
</div>
<!-- Success Modal -->
<div class="modal-overlay fixed inset-0 bg-black/50 flex items-center justify-center z-[1000] opacity-0 invisible transition-all duration-250" id="success-modal">
<div class="modal bg-surface-card rounded-[28px] p-8 max-w-[400px] w-[90%] text-center scale-90 transition-transform duration-500" style="transform: scale(0.9)">
<div class="modal-icon w-20 h-20 mx-auto mb-5 bg-gradient-to-br from-accent to-emerald-500 rounded-full flex items-center justify-center">
<svg class="w-10 h-10 stroke-white fill-none"><use href="#icon-check"></use></svg>
</div>
<h3 class="modal-title text-2xl mb-3 font-display">정리 완료!</h3>
<p class="modal-desc text-text-secondary mb-6">파일 정리가 성공적으로 완료되었습니다.</p>
<button class="btn btn-primary" id="modal-close">
<span>계속하기</span>
</button>
</div>
</div>
<!-- Toast -->
<div class="toast fixed bottom-8 left-1/2 -translate-x-1/2 translate-y-[100px] bg-surface-card px-6 py-4 rounded-full shadow-lg flex items-center gap-3 z-[500] transition-transform duration-500" id="toast">
<div class="toast-icon w-8 h-8 rounded-full flex items-center justify-center bg-amber-500/15 text-accent-warn">
<svg class="w-[18px] h-[18px] stroke-current fill-none text-orange-500"><use href="#icon-fire"></use></svg>
</div>
<span class="toast-message font-medium" id="toast-message">7일 스트릭 달성!</span>
</div>
<script type="module" src="./scripts/shared/state.js"></script>
</body>
</html>

View File

@@ -5,129 +5,37 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 분류 - Chakmate</title>
<link rel="stylesheet" href="../styles/main.css">
<!-- Heroicons SVG Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" class="hidden">
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14M12 5l7 7-7 7"/>
</symbol>
<symbol id="icon-sparkles" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3v18M5.5 9.5L7.5 12M5.5 14.5L7.5 12M16.5 9.5L14.5 12M16.5 14.5L14.5 12M9 3l1.5 4.5L15 3M9 21l1.5-4.5L15 21"/>
<path d="M9 3l1.5 4.5M14.5 7.5L15 3l-4.5 1.5M9 21l1.5-4.5M14.5 16.5L15 21l-4.5-1.5"/>
</symbol>
<symbol id="icon-shield-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="M9 12l2 2 4-4"/>
</symbol>
<symbol id="icon-chart-bar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<circle cx="19" cy="5" r="2"/>
<circle cx="5" cy="19" r="2"/>
<path d="M10.4 10.6l5.2 5.2M13.6 13.6l5.2 5.2"/>
</symbol>
<symbol id="icon-document" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</symbol>
<symbol id="icon-tag" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
<line x1="7" y1="7" x2="7.01" y2="7"/>
</symbol>
<symbol id="icon-clock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</symbol>
<symbol id="icon-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</symbol>
<symbol id="icon-arrow-up" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 19V5M5 12l7-7 7 7"/>
</symbol>
<symbol id="icon-magnifying-glass" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</symbol>
<symbol id="icon-sparkles-alt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</symbol>
<symbol id="icon-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="8" y="8" width="12" height="12" rx="2"/>
<path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/>
</symbol>
</svg>
<style>
[data-theme="dark"] {
--bg-primary: #0a0e1a;
--bg-secondary: #111827;
--bg-card: #1e293b;
--bg-overlay: rgba(14, 165, 233, 0.12);
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.6);
--shadow-glow: 0 0 30px rgba(14, 165, 233, 0.3);
}
[data-theme="dark"] body { background: var(--bg-primary); color: var(--text-primary); }
[data-theme="dark"] .bg-primary { background: var(--bg-primary); }
[data-theme="dark"] .bg-secondary { background: var(--bg-secondary); }
[data-theme="dark"] .bg-card { background: var(--bg-card); }
[data-theme="dark"] .bg-overlay { background: var(--bg-overlay); }
[data-theme="dark"] .text-primary { color: var(--text-primary); }
[data-theme="dark"] .text-secondary { color: var(--text-secondary); }
[data-theme="dark"] .text-muted { color: var(--text-muted); }
[data-theme="dark"] .shadow-sm { box-shadow: var(--shadow-sm); }
[data-theme="dark"] .shadow-md { box-shadow: var(--shadow-md); }
[data-theme="dark"] .shadow-lg { box-shadow: var(--shadow-lg); }
[data-theme="dark"] .shadow-glow { box-shadow: var(--shadow-glow); }
[data-theme="dark"] .border-secondary { border-color: var(--bg-secondary); }
/* Neural network nodes animation delays */
.neural-node:nth-child(1) { animation-delay: 0s; }
.neural-node:nth-child(2) { animation-delay: 0.3s; }
.neural-node:nth-child(3) { animation-delay: 0.6s; }
.neural-node:nth-child(4) { animation-delay: 0.9s; }
.neural-node:nth-child(5) { animation-delay: 1.2s; }
/* Discovery card stagger animation */
.discovery-card:nth-child(1) { transition-delay: 0ms; }
.discovery-card:nth-child(2) { transition-delay: 100ms; }
.discovery-card:nth-child(3) { transition-delay: 200ms; }
.discovery-card:nth-child(4) { transition-delay: 300ms; }
/* Tag pill click feedback */
.tag-pill-feedback { transform: scale(0.95); }
/* Hint chip click feedback */
.hint-chip-feedback { background: var(--primary-dark); }
</style>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<div class="max-w-[1200px] w-full mx-auto p-6">
<!-- Back Header -->
<div class="flex items-center gap-4 mb-8">
<a href="scene_dashboard.html" class="inline-flex items-center gap-2 text-text-secondary font-medium transition-colors duration-150 hover:text-primary hover:bg-accent/10 rounded-xl px-3 py-2">
<svg class="w-5 h-5"><use href="#icon-arrow-left"></use></svg>
<span>대시보드로</span>
</a>
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
<header class="page-header">
<div class="header-left flex items-center gap-3">
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
</div>
<!-- Privacy Hero Banner -->
<div class="bg-gradient-to-br from-primary via-secondary to-primary-light rounded-[28px] p-12 mb-8 relative overflow-hidden shadow-lg">
<div class="absolute top-[-50%] right-[-20%] w-[400px] h-[400px] bg-[radial-gradient(circle,rgba(255,255,255,0.15),transparent_70%)] animate-pulse-glow"></div>
<div class="relative z-10 flex items-center gap-6">
<div class="w-20 h-20 bg-white/20 rounded-xl flex items-center justify-center backdrop-blur-sm">
<svg class="w-12 h-12 text-white"><use href="#icon-shield-check"></use></svg>
</div>
</div>
<div class="relative z-10 ml-4">
<h1 class="text-white text-2xl font-display font-bold mb-2">AI 파일 분류</h1>
<p class="text-white/80 text-sm font-light">머신러닝으로 파일을 자동으로 분석하고 분류합니다</p>
<div class="header-right flex items-center gap-3">
<div class="streak-badge flex items-center gap-2 bg-surface-card px-4 py-2 rounded-full shadow-sm">
<svg class="w-5 h-5 stroke-accent-warn fill-none text-orange-500"><use href="../assets/icons/icons.svg#icon-fire"></use></svg>
<span class="font-bold text-accent-warn" id="streak-count">7</span>
<span class="text-text-muted text-sm"></span>
</div>
</div>
</header>
<div class="page-content">
<div class="bg-gradient-to-br from-primary via-secondary to-primary-light rounded-xl p-8 mb-4 relative overflow-hidden shadow-lg">
<div class="absolute top-[-50%] right-[-20%] w-[400px] h-[400px] bg-[radial-gradient(circle,rgba(255,255,255,0.15),transparent_70%)] animate-pulse-glow"></div>
<div class="relative z-10 flex items-center gap-6">
<div class="w-20 h-20 bg-white/20 rounded-xl flex items-center justify-center backdrop-blur-sm">
<svg class="w-12 h-12 text-white"><use href="../assets/icons/icons.svg#icon-shield-check"></use></svg>
</div>
<div>
<h1 class="text-white text-2xl font-display font-bold mb-2">AI 파일 분류</h1>
<p class="text-white/80 text-sm font-light">머신러닝으로 파일을 자동으로 분석하고 분류합니다</p>
</div>
</div>
</div>
<div class="bg-surface-card rounded-xl p-6 mb-8 shadow-md flex items-center gap-6">
<div class="w-16 h-16 relative">
<div class="w-full h-full rounded-full border-4 border-surface-secondary relative">
@@ -141,409 +49,25 @@
<h2 class="text-[1.1rem] font-display font-semibold mb-1">AI 분석 진행 중</h2>
<p class="text-text-muted text-[0.85rem]">파일을 스캔하고 패턴을 인식하는 중...</p>
<div class="mt-2 h-2 bg-surface-secondary rounded-full overflow-hidden w-[200px]">
<div class="h-full bg-gradient-to-r from-primary to-secondary rounded-full transition-all duration-1000 animate-[width_2s_ease-out]" style="width: 67%;"></div>
<div class="h-full bg-gradient-to-r from-primary to-secondary rounded-full transition-all duration-1000" style="width: 67%;"></div>
</div>
</div>
<button class="inline-flex items-center gap-2 px-4 py-2 bg-surface-secondary hover:bg-surface-primary text-text-secondary hover:text-text-primary rounded-xl text-sm font-medium transition-all duration-200">
<svg class="w-4 h-4"><use href="#icon-arrow-up"></use></svg>
분석 결과 내보내기
<button class="inline-flex items-center gap-2 px-4 py-2 bg-surface-secondary hover:bg-surface-primary text-text-secondary hover:text-text-primary rounded-lg transition-colors text-sm" id="cancelBtn">
취소
</button>
</div>
<!-- Neural Network Visualization -->
<div class="bg-surface-card rounded-xl p-8 mb-8 shadow-md relative overflow-hidden">
<div class="absolute inset-0 opacity-5">
<div class="absolute top-[20%] left-[10%] w-32 h-32 border border-primary rounded-full animate-float"></div>
<div class="absolute top-[60%] right-[15%] w-24 h-24 border border-secondary rounded-full animate-float" style="animation-delay: 0.5s;"></div>
<div class="absolute bottom-[20%] left-[30%] w-20 h-20 border border-accent rounded-full animate-float" style="animation-delay: 1s;"></div>
</div>
<div class="relative z-10">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<h2 class="text-[1.5rem] font-display font-semibold">분류 네트워크</h2>
<span class="inline-flex items-center gap-1.5 px-3 py-1 bg-primary/10 text-primary rounded-full text-[0.75rem] font-medium">
<span class="w-1.5 h-1.5 bg-primary rounded-full animate-pulse"></span>
활성
</span>
</div>
<div class="flex items-center gap-2 text-text-muted text-[0.85rem]">
<svg class="w-4 h-4"><use href="#icon-cpu"></use></svg>
<span>GPU 가속</span>
</div>
</div>
<div class="relative h-[200px] flex items-center justify-center">
<!-- Neural Network SVG -->
<svg class="w-full h-full absolute inset-0" style="min-width: 600px;">
<!-- Connection lines -->
<path d="M100 100 Q200 50 300 100" class="stroke-surface-secondary stroke-[1] fill-none opacity-30"/>
<path d="M100 100 Q200 150 300 100" class="stroke-surface-secondary stroke-[1] fill-none opacity-30"/>
<path d="M100 100 Q200 100 300 100" class="stroke-surface-secondary stroke-[1] fill-none opacity-50"/>
<path d="M300 100 Q400 50 500 100" class="stroke-primary stroke-[2] fill-none opacity-60"/>
<path d="M300 100 Q400 150 500 100" class="stroke-secondary stroke-[2] fill-none opacity-60"/>
<path d="M300 100 Q400 100 500 100" class="stroke-accent stroke-[2] fill-none opacity-80"/>
<!-- Animated pulses on connections -->
<circle r="4" fill="var(--primary)" class="animate-node-pulse">
<animateMotion dur="2s" repeatCount="indefinite" path="M100 100 Q200 50 300 100"/>
</circle>
<circle r="4" fill="var(--secondary)" class="animate-node-pulse" style="animation-delay: 0.5s;">
<animateMotion dur="2s" repeatCount="indefinite" path="M300 100 Q400 150 500 100" begin="0.5s"/>
</circle>
</svg>
<!-- Input Layer -->
<div class="flex flex-col gap-4 absolute left-[10%] top-1/2 -translate-y-1/2">
<div class="neural-node w-12 h-12 rounded-full bg-gradient-to-br from-primary to-primary-dark shadow-md flex items-center justify-center animate-node-pulse">
<svg class="w-5 h-5 text-white"><use href="#icon-document"></use></svg>
</div>
<div class="neural-node w-12 h-12 rounded-full bg-gradient-to-br from-primary to-primary-dark shadow-md flex items-center justify-center animate-node-pulse" style="animation-delay: 0.3s;">
<svg class="w-5 h-5 text-white"><use href="#icon-image"></use></svg>
</div>
<div class="neural-node w-12 h-12 rounded-full bg-gradient-to-br from-primary to-primary-dark shadow-md flex items-center justify-center animate-node-pulse" style="animation-delay: 0.6s;">
<svg class="w-5 h-5 text-white"><use href="#icon-folder"></use></svg>
</div>
</div>
<!-- Hidden Layer -->
<div class="flex flex-col gap-4 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
<div class="neural-node w-14 h-14 rounded-full bg-gradient-to-br from-secondary to-secondary-light shadow-lg flex items-center justify-center animate-node-pulse" style="animation-delay: 0.2s;">
<svg class="w-6 h-6 text-white"><use href="#icon-sparkles"></use></svg>
</div>
<div class="neural-node w-14 h-14 rounded-full bg-gradient-to-br from-secondary to-secondary-light shadow-lg flex items-center justify-center animate-node-pulse" style="animation-delay: 0.4s;">
<span class="text-white font-bold text-lg">NN</span>
</div>
<div class="neural-node w-14 h-14 rounded-full bg-gradient-to-br from-secondary to-secondary-light shadow-lg flex items-center justify-center animate-node-pulse" style="animation-delay: 0.6s;">
<svg class="w-6 h-6 text-white"><use href="#icon-chart-bar"></use></svg>
</div>
</div>
<!-- Output Layer -->
<div class="flex flex-col gap-4 absolute right-[10%] top-1/2 -translate-y-1/2">
<div class="neural-node w-12 h-12 rounded-full bg-gradient-to-br from-accent to-accent-warn shadow-md flex items-center justify-center animate-node-pulse" style="animation-delay: 0.3s;">
<svg class="w-5 h-5 text-white"><use href="#icon-tag"></use></svg>
</div>
<div class="neural-node w-12 h-12 rounded-full bg-gradient-to-br from-accent to-accent-warn shadow-md flex items-center justify-center animate-node-pulse" style="animation-delay: 0.5s;">
<svg class="w-5 h-5 text-white"><use href="#icon-clock"></use></svg>
</div>
<div class="neural-node w-12 h-12 rounded-full bg-gradient-to-br from-accent to-accent-warn shadow-md flex items-center justify-center animate-node-pulse" style="animation-delay: 0.7s;">
<svg class="w-5 h-5 text-white"><use href="#icon-folder"></use></svg>
</div>
</div>
<div class="bg-surface-card rounded-xl p-6 shadow-md">
<h2 class="text-lg font-display font-semibold mb-4">분류 결과</h2>
<div class="space-y-3" id="classificationResults">
<div class="flex items-center justify-center py-8 text-text-muted">
<svg class="w-8 h-8 animate-spin mr-3"><use href="../assets/icons/icons.svg#icon-loader"></use></svg>
분석 중...
</div>
</div>
</div>
<!-- Confidence Metrics -->
<div class="grid grid-cols-3 gap-6 mb-8 max-lg:grid-cols-1">
<!-- Overall Confidence -->
<div class="bg-surface-card rounded-xl p-6 shadow-md">
<div class="flex items-center justify-between mb-4">
<h3 class="text-[0.9rem] font-medium text-text-secondary">전체 신뢰도</h3>
<svg class="w-5 h-5 text-primary"><use href="#icon-sparkles"></use></svg>
</div>
<div class="relative w-[120px] h-[120px] mx-auto mb-4">
<svg class="w-full h-full -rotate-[90deg]" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke="var(--surface-secondary)" stroke-width="12"/>
<circle class="progress-ring-fill fill-none stroke-primary transition-all duration-1000" cx="50" cy="50" r="40" stroke-width="12" stroke-dasharray="251" stroke-dashoffset="251" stroke-linecap="round"/>
</svg>
<div class="absolute inset-0 flex items-center justify-center">
<span class="font-display text-2xl font-bold text-text-primary">92<span class="text-[1rem]">%</span></span>
</div>
</div>
<div class="text-center">
<span class="inline-flex items-center gap-1.5 px-3 py-1 bg-primary/10 text-primary rounded-full text-[0.8rem] font-medium">
<svg class="w-3.5 h-3.5"><use href="#icon-shield-check"></use></svg>
매우 높음
</span>
</div>
</div>
<!-- Files Processed -->
<div class="bg-surface-card rounded-xl p-6 shadow-md">
<div class="flex items-center justify-between mb-4">
<h3 class="text-[0.9rem] font-medium text-text-secondary">처리된 파일</h3>
<svg class="w-5 h-5 text-secondary"><use href="#icon-folder"></use></svg>
</div>
<div class="text-center mb-4">
<span class="font-display text-4xl font-bold text-text-primary">847</span>
<span class="text-text-muted text-sm ml-2">파일</span>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between text-[0.85rem]">
<span class="text-text-secondary">오늘</span>
<span class="font-semibold text-text-primary">+127</span>
</div>
<div class="flex items-center justify-between text-[0.85rem]">
<span class="text-text-secondary">이번 주</span>
<span class="font-semibold text-text-primary">+389</span>
</div>
</div>
</div>
<!-- Categories Found -->
<div class="bg-surface-card rounded-xl p-6 shadow-md">
<div class="flex items-center justify-between mb-4">
<h3 class="text-[0.9rem] font-medium text-text-secondary">발견된 카테고리</h3>
<svg class="w-5 h-5 text-accent"><use href="#icon-tag"></use></svg>
</div>
<div class="text-center mb-4">
<span class="font-display text-4xl font-bold text-text-primary">24</span>
<span class="text-text-muted text-sm ml-2"></span>
</div>
<div class="flex flex-wrap justify-center gap-2">
<span class="tag-pill px-3 py-1 bg-primary/10 text-primary rounded-full text-[0.75rem] font-medium cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white">문서</span>
<span class="tag-pill px-3 py-1 bg-secondary/10 text-secondary rounded-full text-[0.75rem] font-medium cursor-pointer transition-all duration-150 hover:bg-secondary hover:text-white">이미지</span>
<span class="tag-pill px-3 py-1 bg-accent/10 text-accent rounded-full text-[0.75rem] font-medium cursor-pointer transition-all duration-150 hover:bg-accent hover:text-white">프로젝트</span>
</div>
</div>
</div>
<!-- AI Discovery Cards -->
<div class="mb-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<h2 class="text-[1.5rem] font-display font-semibold">AI 발견 항목</h2>
<span class="inline-flex items-center gap-1.5 px-3 py-1 bg-accent/10 text-accent rounded-full text-[0.75rem] font-medium">
<span class="w-1.5 h-1.5 bg-accent rounded-full animate-pulse"></span>
실시간
</span>
</div>
<button class="inline-flex items-center gap-2 px-4 py-2 bg-surface-secondary hover:bg-surface-primary text-text-secondary hover:text-text-primary rounded-xl text-sm font-medium transition-all duration-200">
<svg class="w-4 h-4"><use href="#icon-arrow-right"></use></svg>
모두 보기
</button>
</div>
<div class="grid grid-cols-2 gap-6 max-lg:grid-cols-1">
<!-- Discovery Card 1 -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-500">
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-primary to-primary-dark flex items-center justify-center shadow-md">
<svg class="w-6 h-6 text-white"><use href="#icon-document"></use></svg>
</div>
<div class="flex-1">
<h3 class="font-display font-semibold text-[1.05rem] mb-1">금융 문서 자동 분류</h3>
<p class="text-text-muted text-[0.85rem]">보고서, 세금, 계약서를 자동으로 구분합니다</p>
</div>
</div>
<div class="flex flex-wrap gap-2 mb-4">
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">보고서</span>
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">금융</span>
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">자동</span>
</div>
<div class="flex items-center justify-between pt-4 border-t border-surface-secondary">
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-primary rounded-full"></div>
<span class="text-[0.8rem] text-text-muted">신뢰도 94%</span>
</div>
<span class="text-[0.8rem] text-text-muted">방금 전</span>
</div>
</div>
<!-- Discovery Card 2 -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-500">
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-secondary to-secondary-light flex items-center justify-center shadow-md">
<svg class="w-6 h-6 text-white"><use href="#icon-chart-bar"></use></svg>
</div>
<div class="flex-1">
<h3 class="font-display font-semibold text-[1.05rem] mb-1">시간 기반 정리</h3>
<p class="text-text-muted text-[0.85rem]">파일 수정일을 분석하여 자동으로 연도/분기별로 분류</p>
</div>
</div>
<div class="flex flex-wrap gap-2 mb-4">
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">타임라인</span>
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">정리</span>
</div>
<div class="flex items-center justify-between pt-4 border-t border-surface-secondary">
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-secondary rounded-full"></div>
<span class="text-[0.8rem] text-text-muted">신뢰도 89%</span>
</div>
<span class="text-[0.8rem] text-text-muted">5분 전</span>
</div>
</div>
<!-- Discovery Card 3 -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-500">
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-accent to-accent-warn flex items-center justify-center shadow-md">
<svg class="w-6 h-6 text-white"><use href="#icon-tag"></use></svg>
</div>
<div class="flex-1">
<h3 class="font-display font-semibold text-[1.05rem] mb-1">태그 제안</h3>
<p class="text-text-muted text-[0.85rem]">파일 내용을 분석하여 적절한 태그를 자동으로 추천</p>
</div>
</div>
<div class="flex flex-wrap gap-2 mb-4">
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">AI</span>
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">추천</span>
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">태그</span>
</div>
<div class="flex items-center justify-between pt-4 border-t border-surface-secondary">
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-accent rounded-full"></div>
<span class="text-[0.8rem] text-text-muted">신뢰도 91%</span>
</div>
<span class="text-[0.8rem] text-text-muted">12분 전</span>
</div>
</div>
<!-- Discovery Card 4 -->
<div class="discovery-card bg-surface-card rounded-xl p-6 shadow-md opacity-0 translate-y-5 transition-all duration-500">
<div class="flex items-start gap-4 mb-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-primary-dark to-primary flex items-center justify-center shadow-md">
<svg class="w-6 h-6 text-white"><use href="#icon-folder"></use></svg>
</div>
<div class="flex-1">
<h3 class="font-display font-semibold text-[1.05rem] mb-1">프로젝트 폴더 감지</h3>
<p class="text-text-muted text-[0.85rem]">유사한 파일들을 하나의 프로젝트로 그룹화</p>
</div>
</div>
<div class="flex flex-wrap gap-2 mb-4">
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">프로젝트</span>
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">그룹</span>
<span class="px-3 py-1 bg-surface-secondary rounded-full text-[0.75rem] text-text-secondary">자동</span>
</div>
<div class="flex items-center justify-between pt-4 border-t border-surface-secondary">
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-primary-dark rounded-full"></div>
<span class="text-[0.8rem] text-text-muted">신뢰도 87%</span>
</div>
<span class="text-[0.8rem] text-text-muted">1시간 전</span>
</div>
</div>
</div>
</div>
<!-- Confidence Breakdown -->
<div class="bg-surface-card rounded-xl p-8 mb-8 shadow-md">
<div class="flex items-center justify-between mb-6">
<h2 class="text-[1.5rem] font-display font-semibold">카테고리별 신뢰도</h2>
<button class="inline-flex items-center gap-2 text-text-muted hover:text-text-primary text-[0.85rem] transition-colors">
<svg class="w-4 h-4"><use href="#icon-chart-bar"></use></svg>
상세 보기
</button>
</div>
<div class="space-y-5">
<!-- Confidence Bar 1 -->
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">문서</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="confidence-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 94%; background: linear-gradient(90deg, var(--primary), var(--primary-light));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">94%</span>
</div>
<!-- Confidence Bar 2 -->
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">이미지</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="confidence-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 89%; background: linear-gradient(90deg, var(--secondary), var(--secondary-light));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">89%</span>
</div>
<!-- Confidence Bar 3 -->
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">프로젝트</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="confidence-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 91%; background: linear-gradient(90deg, var(--accent), var(--accent-warn));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">91%</span>
</div>
<!-- Confidence Bar 4 -->
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">归档</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="confidence-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 78%; background: linear-gradient(90deg, var(--primary-dark), var(--primary));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">78%</span>
</div>
<!-- Confidence Bar 5 -->
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">기타</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="confidence-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 67%; background: linear-gradient(90deg, var(--accent-warn), #fcd34d);"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">67</span>
</div>
</div>
</div>
<!-- Distribution Chart -->
<div class="mb-8">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-3">
<h2 class="text-[1.5rem] font-display font-semibold">파일 분포</h2>
</div>
</div>
<div class="bg-surface-card rounded-xl p-8 shadow-md">
<div class="grid grid-cols-[300px_1fr] gap-8 items-center max-lg:grid-cols-1">
<div class="relative w-[220px] h-[220px] mx-auto">
<svg class="w-full h-full -rotate-[90deg]" viewBox="0 0 100 100">
<circle class="pie-segment fill-none stroke-primary transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 98;"/>
<circle class="pie-segment fill-none stroke-secondary transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 78;"/>
<circle class="pie-segment fill-none stroke-accent transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 47;"/>
<circle class="pie-segment fill-none stroke-accent-warn transition-all duration-1000" cx="50" cy="50" r="25" stroke-width="40" stroke-dasharray="0 314" style="--segment-dash: 47;"/>
</svg>
<div class="absolute inset-10 bg-surface-card rounded-full flex flex-col items-center justify-center">
<span class="font-display text-2xl font-bold text-text-primary">847</span>
<small class="text-text-muted text-[0.8rem]">전체 파일</small>
</div>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">문서</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 45%; background: linear-gradient(90deg, var(--primary), var(--primary-light));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">382</span>
</div>
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">이미지</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 35%; background: linear-gradient(90deg, var(--secondary), var(--secondary-light));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">296</span>
</div>
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">프로젝트</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 12%; background: linear-gradient(90deg, var(--accent), var(--accent-warn));"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">102</span>
</div>
<div class="flex items-center gap-4">
<span class="w-[100px] text-[0.9rem] font-medium">기타</span>
<div class="flex-1 h-8 bg-surface-secondary rounded-xl overflow-hidden relative">
<div class="pattern-bar-fill h-full rounded-xl transition-all duration-1000 animate-[width_1s_ease-out]" style="width: 8%; background: linear-gradient(90deg, var(--accent-warn), #fcd34d);"></div>
</div>
<span class="w-[50px] text-right font-semibold text-[0.9rem] text-text-secondary">67</span>
</div>
</div>
</div>
</div>
</div>
<!-- Semantic Search Hints -->
<div class="bg-surface-card rounded-xl p-6 shadow-md mb-8">
<h3 class="text-base font-display font-semibold mb-4 flex items-center gap-2">
<svg class="w-5 h-5 text-primary"><use href="#icon-magnifying-glass"></use></svg>
관련 검색
</h3>
<div class="flex flex-wrap gap-3">
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">2024년 4분기 금융 보고서</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">프로젝트 타임라인</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">2024년 휴가</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">디자인 자산</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">세금 문서</span>
<span class="hint-chip px-5 py-3 bg-surface-secondary rounded-full text-[0.85rem] text-text-secondary cursor-pointer transition-all duration-150 hover:bg-primary hover:text-white hover:scale-105">고객 프레젠테이션</span>
</div>
</div>
<!-- Generate Button -->
<div class="flex justify-center gap-4">
<button class="inline-flex items-center justify-center gap-2 px-8 py-4 bg-gradient-to-br from-primary to-primary-dark text-white rounded-full font-display font-semibold text-base shadow-blue hover:shadow-[0_6px_25px_rgba(59,130,246,0.4)] hover:-translate-y-0.5 active:translate-y-0 transition-all duration-250 min-h-[56px] min-w-[160px]">
<svg class="w-5 h-5"><use href="#icon-sparkles-alt"></use></svg>
새 인사이트 생성
</button>
</div>
</div>
<script type="module" src="../scripts/ai-classification.js"></script>
<script type="module" src="../scripts/pages/ai-classification.js"></script>
</body>
</html>

View File

@@ -5,76 +5,43 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>대시보드 - Chakmate</title>
<link rel="stylesheet" href="../styles/main.css">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="icon-layers" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</symbol>
<symbol id="icon-fire" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 a8.21 8.21 0 0 0 3 2.48Z"/>
<path d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z"/>
</symbol>
<symbol id="icon-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</symbol>
<symbol id="icon-document" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</symbol>
<symbol id="icon-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</symbol>
<symbol id="icon-cog" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 15a3 3 0 100-6 3 3 0 000 6z"/>
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>
</symbol>
<symbol id="icon-bolt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"/>
</symbol>
</svg>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden pb-20">
<header class="flex items-center justify-between p-5 bg-surface-card rounded-b-3xl shadow-[0_4px_20px_rgba(30,27,46,0.08)]">
<div class="flex items-center gap-3">
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
<header class="page-header">
<div class="header-left flex items-center gap-3">
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
</div>
<div class="flex items-center gap-3">
<div class="header-right flex items-center gap-3">
<div class="streak-badge flex items-center gap-2 bg-surface-card px-4 py-2 rounded-full shadow-sm">
<svg class="w-5 h-5 stroke-accent-warn fill-none text-orange-500"><use href="#icon-fire"></use></svg>
<svg class="w-5 h-5 stroke-accent-warn fill-none text-orange-500"><use href="../assets/icons/icons.svg#icon-fire"></use></svg>
<span class="font-bold text-accent-warn" id="streak-count">7</span>
<span class="text-text-muted text-sm"></span>
</div>
</div>
</header>
<div id="dashboard">
<div class="stats-container p-4 flex flex-col gap-4">
<div id="dashboard" class="page-content">
<div class="stats-container flex flex-col gap-4">
<div class="stats-row flex gap-4">
<div class="stat-card flex-1 bg-surface-card rounded-[20px] p-5 shadow-sm">
<div class="stat-card flex-1 bg-surface-card rounded-xl p-5 shadow-sm">
<div class="stat-icon w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center mb-3">
<svg class="w-5 h-5 stroke-primary fill-none"><use href="#icon-folder"></use></svg>
<svg class="w-5 h-5 stroke-primary fill-none"><use href="../assets/icons/icons.svg#icon-folder"></use></svg>
</div>
<p class="stat-value text-2xl font-display font-bold text-text-primary" id="files-count">248</p>
<p class="stat-label text-text-muted text-sm">정리된 파일</p>
</div>
<div class="stat-card flex-1 bg-surface-card rounded-[20px] p-5 shadow-sm">
<div class="stat-card flex-1 bg-surface-card rounded-xl p-5 shadow-sm">
<div class="stat-icon w-10 h-10 rounded-full bg-accent/10 flex items-center justify-center mb-3">
<svg class="w-5 h-5 stroke-accent fill-none"><use href="#icon-document"></use></svg>
<svg class="w-5 h-5 stroke-accent fill-none"><use href="../assets/icons/icons.svg#icon-document"></use></svg>
</div>
<p class="stat-value text-2xl font-display font-bold text-text-primary" id="folders-count">12</p>
<p class="stat-label text-text-muted text-sm">관리된 폴더</p>
</div>
</div>
<div class="suggestions-card bg-surface-card rounded-[20px] p-5 shadow-md">
<div class="suggestions-card bg-surface-card rounded-xl p-5 shadow-md">
<div class="suggestions-header flex items-center justify-between mb-4">
<h2 class="text-lg font-display font-bold">AI 제안</h2>
<button class="text-sm text-primary font-medium hover:underline" id="apply-suggestions">적용하기</button>
@@ -87,7 +54,7 @@
<div class="section-header flex items-center justify-between mb-4">
<h2 class="text-lg font-display font-bold">업적</h2>
<div class="flex items-center gap-1">
<svg class="w-5 h-5 stroke-amber-500 fill-none text-yellow-400"><use href="#icon-star"></use></svg>
<svg class="w-5 h-5 stroke-amber-500 fill-none text-yellow-400"><use href="../assets/icons/icons.svg#icon-star"></use></svg>
<span class="text-sm font-medium text-text-muted">4/8</span>
</div>
</div>
@@ -95,24 +62,8 @@
</div>
</div>
<nav class="fixed bottom-0 left-0 right-0 bg-surface-card border-t border-surface-secondary z-[100]">
<div class="max-w-[600px] mx-auto flex justify-around py-3">
<span class="nav-btn flex flex-col items-center gap-1 px-6 py-2 rounded-2xl transition-all duration-250 active bg-primary/10 text-primary">
<svg class="w-6 h-6 stroke-current fill-none"><use href="#icon-layers"></use></svg>
<span class="text-xs font-medium"></span>
</span>
<a href="scene_swipe.html" class="nav-btn flex flex-col items-center gap-1 px-6 py-2 rounded-2xl transition-all duration-250 text-text-muted">
<svg class="w-6 h-6 stroke-current fill-none"><use href="#icon-bolt"></use></svg>
<span class="text-xs font-medium">스와이프</span>
</a>
<a href="scene_settings.html" class="nav-btn flex flex-col items-center gap-1 px-6 py-2 rounded-2xl transition-all duration-250 text-text-muted">
<svg class="w-6 h-6 stroke-current fill-none"><use href="#icon-cog"></use></svg>
<span class="text-xs font-medium">설정</span>
</a>
</div>
</nav>
<script type="module" src="../scripts/shared/state.js"></script>
<script type="module" src="../scripts/dashboard.js"></script>
<script type="module" src="../scripts/shared/nav.js"></script>
<script type="module" src="../scripts/pages/dashboard.js"></script>
</body>
</html>

View File

@@ -4,235 +4,35 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>내 진행 상황 - Chakmate</title>
<!-- Heroicons SVG Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
</svg>
<link rel="stylesheet" href="../styles/main.css">
<style>
.confetti {
position: absolute;
width: 10px;
height: 10px;
opacity: 0;
}
.achievement.just-unlocked {
animation: unlockPulse 0.6s ease-out;
}
.sparkle {
position: absolute;
font-size: 1rem;
animation: sparkle 0.6s ease-out forwards;
}
.toast.show {
opacity: 1 !important;
transform: translateX(-50%) translateY(0) !important;
}
.toggle-switch {
position: relative;
width: 56px;
height: 32px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
inset: 0;
background: #e2e8f0;
border-radius: 9999px;
transition: background 250ms ease;
}
.toggle-slider::before {
content: '';
position: absolute;
height: 24px;
width: 24px;
left: 4px;
bottom: 4px;
background: white;
border-radius: 50%;
transition: transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 2px 8px rgba(14, 165, 233, 0.06);
}
.toggle-switch input:checked + .toggle-slider {
background: linear-gradient(90deg, #3b82f6 0%, #0ea5e9 100%);
}
.toggle-switch input:checked + .toggle-slider::before {
transform: translateX(24px);
}
.achievement-tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%) translateY(0);
background: #0f172a;
color: #ffffff;
padding: 8px 12px;
border-radius: 8px;
font-size: 0.7rem;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 150ms ease;
z-index: 10;
}
.achievement-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #0f172a;
}
.achievement.locked:hover .achievement-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(-10px);
}
.achievement.unlocked .achievement-tooltip {
display: none;
}
.week-day-indicator {
width: 36px;
height: 36px;
border-radius: 12px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 600;
transition: all 200ms ease;
}
.week-day-indicator.completed {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.week-day-indicator.today {
background: linear-gradient(135deg, #3b82f6 0%, #0ea5e9 100%);
color: white;
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
}
.week-day-label {
font-size: 0.65rem;
color: #94a3b8;
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.week-day {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.week-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
padding: 16px;
background: #f8fafc;
border-radius: 16px;
}
.achievement-icon {
font-size: 2rem;
margin-bottom: 4px;
display: block;
}
.achievement-name {
font-size: 0.7rem;
color: #475569;
text-align: center;
}
.achievement {
background: white;
border-radius: 16px;
padding: 16px 12px;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: all 300ms ease;
position: relative;
}
.achievement.locked {
background: #f1f5f9;
}
.achievement.locked .achievement-icon {
filter: grayscale(100%);
opacity: 0.5;
}
.achievement.gold {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border: 1px solid #fbbf24;
}
.achievement.silver {
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
border: 1px solid #cbd5e1;
}
.achievement.bronze {
background: linear-gradient(135deg, #fef3c7 0%, #fcd34d 100%);
border: 1px solid #d97706;
}
.achievement.unlocked {
cursor: default;
}
.streak-fire {
font-size: 2.5rem;
display: inline-block;
}
.streak-fire.animate {
animation: fireFlicker 0.3s ease-in-out infinite;
}
@keyframes fireFlicker {
0%, 100% { transform: scale(1) rotate(-3deg); }
50% { transform: scale(1.1) rotate(3deg); }
}
@keyframes confettiFall {
0% {
transform: translateY(-10vh) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(720deg);
opacity: 0;
}
}
</style>
</head>
<body class="bg-surface-primary min-h-screen">
<!-- Header -->
<header class="bg-surface-card shadow-sm sticky top-0 z-50">
<div class="max-w-lg mx-auto px-4 h-16 flex items-center justify-between">
<a href="/" class="flex items-center gap-2 text-text-primary hover:text-primary transition-colors">
<svg class="w-6 h-6"><use href="#icon-arrow-left"/></svg>
<span class="text-sm font-medium">뒤로</span>
</a>
<h1 class="text-lg font-display font-semibold text-text-primary">내 진행 상황</h1>
<div class="w-16"></div>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
<header class="page-header">
<div class="header-left flex items-center gap-3">
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
</div>
<div class="header-right flex items-center gap-3">
<div class="streak-badge flex items-center gap-2 bg-surface-card px-4 py-2 rounded-full shadow-sm">
<svg class="w-5 h-5 stroke-accent-warn fill-none text-orange-500"><use href="../assets/icons/icons.svg#icon-fire"></use></svg>
<span class="font-bold text-accent-warn" id="streak-count">7</span>
<span class="text-text-muted text-sm"></span>
</div>
</div>
</header>
<main class="max-w-lg mx-auto px-4 py-6 space-y-6 pb-[120px]">
<!-- Streak Section -->
<section class="bg-gradient-to-br from-primary/10 to-accent/10 rounded-[24px] p-6 text-center">
<main class="page-content space-y-6">
<section class="bg-gradient-to-br from-primary/10 to-accent/10 rounded-xl p-6 text-center">
<div class="flex items-center justify-center gap-3 mb-4">
<span class="streak-fire" id="streakIcon">🔥</span>
<svg class="w-12 h-12 stroke-accent-warn fill-none text-orange-500" id="streakIcon"><use href="../assets/icons/icons.svg#icon-fire"></use></svg>
<span class="text-6xl font-display font-bold text-text-primary" id="streakCount">0</span>
</div>
<p class="text-text-secondary text-sm mb-2">일 연속 정리</p>
<p class="text-text-muted text-xs" id="streakMessage">오늘 불을 이어가세요!</p>
</section>
<!-- Weekly Progress -->
<section class="bg-surface-card rounded-[24px] p-6 shadow-sm">
<section class="bg-surface-card rounded-xl p-6 shadow-sm">
<div class="flex items-center justify-between mb-4">
<h2 class="text-base font-semibold text-text-primary">이번 주 진행률</h2>
<span class="text-sm font-medium text-primary"><span id="daysCompleted">0</span>/7일</span>
@@ -243,64 +43,24 @@
<div class="week-grid" id="weekGrid"></div>
</section>
<!-- Daily Tip -->
<section class="bg-surface-card rounded-[24px] p-5 shadow-sm relative overflow-hidden">
<section class="bg-surface-card rounded-xl p-5 shadow-sm relative overflow-hidden">
<div class="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-primary/5 to-transparent rounded-bl-[100px]"></div>
<div class="relative">
<div class="flex items-center gap-2 mb-3">
<span class="text-lg">💡</span>
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5"><use href="../assets/icons/icons.svg#icon-light-bulb"></use></svg>
<span class="text-sm font-medium text-text-primary">오늘의 팁</span>
</div>
<p class="text-text-secondary text-sm leading-relaxed" id="tipText">"정돈된 공간은 정돈된 마음을 만듭니다."</p>
<p class="text-text-muted text-xs mt-2 tip-author">— 정리의 지혜</p>
<p class="text-text-muted text-xs mt-2">— 정리의 지혜</p>
</div>
</section>
<!-- Achievements -->
<section>
<h2 class="text-base font-semibold text-text-primary mb-4">업적</h2>
<div class="grid grid-cols-4 gap-3" id="achievementsGrid"></div>
<h2 class="text-lg font-semibold text-text-primary mb-4">업적</h2>
<div class="grid grid-cols-2 gap-3" id="achievementsGrid"></div>
</section>
</main>
<!-- Settings -->
<section class="bg-surface-card rounded-[24px] p-4 shadow-sm space-y-3">
<h2 class="text-base font-semibold text-text-primary mb-2">설정</h2>
<div class="flex items-center justify-between p-4 bg-surface-primary rounded-[20px]">
<div class="flex items-center gap-3">
<span class="text-2xl">🌙</span>
<div>
<div class="text-sm font-medium text-text-primary">다크 모드</div>
<div class="text-xs text-text-muted mt-0.5">앱 테마 변경</div>
</div>
</div>
<label class="toggle-switch">
<input type="checkbox" id="darkModeToggle">
<span class="toggle-slider"></span>
</label>
</div>
<div class="flex items-center justify-between p-4 bg-surface-card rounded-[20px] shadow-sm">
<div class="flex items-center gap-3">
<span class="text-2xl">🔔</span>
<div>
<div class="text-sm font-medium text-text-primary">습관 알림</div>
<div class="text-xs text-text-muted mt-0.5">매일 알림 받기</div>
</div>
</div>
<label class="toggle-switch">
<input type="checkbox" id="habitToggle">
<span class="toggle-slider"></span>
</label>
</div>
</section>
<div class="fixed bottom-[100px] left-1/2 -translate-x-1/2 translate-y-[100px] bg-text-primary text-surface-card px-6 py-4 rounded-full text-sm font-medium shadow-lg opacity-0 transition-all duration-500 z-[100] flex items-center gap-2" id="toast">
<span id="toastIcon">🎉</span>
<span id="toastText">업적 달성!</span>
</div>
<div class="fixed top-0 left-0 w-full h-full pointer-events-none z-[1000] overflow-hidden" id="confettiContainer"></div>
<script type="module" src="../scripts/shared/state.js"></script>
<script type="module" src="../scripts/gamification.js"></script>
<script type="module" src="../scripts/pages/gamification.js"></script>
</body>
</html>

View File

@@ -5,99 +5,94 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chakmate - 지능형 디지털 자산 관리</title>
<link rel="stylesheet" href="../styles/main.css">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="icon-layers" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</symbol>
<symbol id="icon-shield-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
<path d="M9 12l2 2 4-4"/>
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"/>
<path d="M12 5l7 7-7 7"/>
</symbol>
<symbol id="icon-bolt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</symbol>
<symbol id="icon-light-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18h6M10 22h4M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0018 8 6 6 0 006 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 018.91 14"/>
</symbol>
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
</svg>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<div class="max-w-[420px] w-full text-center flex flex-col items-center justify-center min-h-screen p-4 gap-4 lg:flex-row lg:max-w-full lg:justify-center lg:gap-8 mx-auto">
<div class="flex flex-col items-center justify-center lg:flex-shrink-0">
<div class="logo w-64 h-64 mx-auto mb-6 flex items-center justify-center animate-float lg:w-48 lg:h-48">
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-full h-full object-contain">
</div>
<h1 class="brand-name font-display text-4xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent mb-2 lg:text-3xl">Chakmate</h1>
<p class="tagline text-text-secondary text-base mb-10 lg:mb-0">지능형 디지털 자산 관리 솔루션</p>
</div>
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
<div class="onboarding-content max-w-[420px] w-full text-center flex flex-col items-center justify-center min-h-[calc(100vh-80px)] p-4 gap-4 mx-auto">
<div class="flex flex-col items-center gap-4 lg:flex-col">
<div class="steps-indicator flex gap-2 justify-center mb-8 lg:mb-0">
<div class="step-dot active w-2 h-2 rounded-full bg-primary transition-all duration-250" data-step="1"></div>
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="2"></div>
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="3"></div>
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="4"></div>
</div>
<!-- Step 1: Welcome -->
<div class="onboarding-step active" data-step="1">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-xl flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-layers"></use></svg>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="../assets/icons/icons.svg#icon-layers"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">파일 정리, 지능적으로</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">AI가 파일 사용 패턴을 분석하여 당신만의 최적화된 폴더 구조를 제안합니다. 더 이상 수동 정리는 필요 없습니다.</p>
<div class="privacy-badge inline-flex items-center gap-2 bg-accent text-white px-4 py-2 rounded-full text-sm font-medium mb-6">
<svg class="w-4 h-4 stroke-white fill-none"><use href="#icon-shield-check"></use></svg>
<span>기기 내 단독 처리 완벽한 개인정보 보호</span>
<svg class="w-4 h-4 stroke-white fill-none"><use href="../assets/icons/icons.svg#icon-shield-check"></use></svg>
<span>기기 내 단독 처리 - 완벽한 개인정보 보호</span>
</div>
</div>
<!-- Step 2: Swipe -->
<div class="onboarding-step hidden" data-step="2">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-xl flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-bolt"></use></svg>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="../assets/icons/icons.svg#icon-bolt"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">вай프로 빠른 정리</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">좌우 스와이프로 파일을 빠르게 분류하세요. 한번의 제스처로 파일의 귀 fate를 결정합니다.</p>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">와이프로 빠른 정리</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">좌우 스와이프로 파일을 빠르게 분류하세요. 한번의 제스처로 파일의 운명을 결정합니다.</p>
<div class="swipe-hints flex justify-between w-full max-w-[280px] mx-auto text-text-muted text-sm">
<div class="flex items-center gap-1">
<svg class="w-[16px] h-[16px] stroke-current fill-none"><use href="#icon-arrow-left"></use></svg>
<svg class="w-[16px] h-[16px] stroke-current fill-none"><use href="../assets/icons/icons.svg#icon-arrow-left"></use></svg>
<span>삭제</span>
</div>
<div class="flex items-center gap-1">
<span>보관</span>
<svg class="w-[16px] h-[16px] stroke-accent-warn fill-none stroke-2"><use href="#icon-arrow-right"></use></svg>
<svg class="w-[16px] h-[16px] stroke-accent-warn fill-none stroke-2"><use href="../assets/icons/icons.svg#icon-arrow-right"></use></svg>
</div>
</div>
</div>
<!-- Step 3: AI -->
<div class="onboarding-step hidden" data-step="3">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-[28px] flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-xl flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="#icon-light-bulb"></use></svg>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="../assets/icons/icons.svg#icon-light-bulb"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">AI가 제안하는 구조</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-8">당신의 파일 사용 패턴을 학습하여 최적인 폴더 구조를 제안해 드립니다.</p>
<p class="step-desc text-text-secondary leading-relaxed mb-6">당신의 파일 사용 패턴을 학습하여 최적인 폴더 구조를 제안해 드립니다.</p>
</div>
<button class="btn btn-primary w-full max-w-[280px] mx-auto lg:mx-0" id="start-organizing">
<span>정리 시작하기</span>
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-right"></use></svg>
<div class="onboarding-step hidden" data-step="4">
<div class="step-illustration w-[200px] h-[200px] mx-auto mb-8 bg-surface-card rounded-xl flex items-center justify-center shadow-lg relative overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 to-transparent"></div>
<svg class="w-[100px] h-[100px] stroke-primary fill-none stroke-[1.5] relative z-10"><use href="../assets/icons/icons.svg#icon-folder"></use></svg>
</div>
<h2 class="step-title text-xl mb-3 text-text-primary font-display">탐색할 폴더 선택</h2>
<p class="step-desc text-text-secondary leading-relaxed mb-6">정리할 파일이 있는 폴더를 선택하세요.</p>
<button class="btn btn-secondary w-full max-w-[280px] mx-auto mb-4" id="select-folder">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5"><use href="../assets/icons/icons.svg#icon-folder"></use></svg>
<span>폴더 선택</span>
</button>
<p class="text-text-muted text-sm" id="selected-path">선택된 폴더: 없음</p>
</div>
<button class="btn btn-primary w-full max-w-[280px] mx-auto" id="next-btn" onclick="debugClick()">
<span>다음</span>
<svg class="w-5 h-5 stroke-current fill-none"><use href="../assets/icons/icons.svg#icon-arrow-right"></use></svg>
</button>
<button class="btn btn-primary w-full max-w-[280px] mx-auto" id="go-dashboard" style="display:none" onclick="debugClick()">
<span>대시보드로 이동</span>
<svg class="w-5 h-5 stroke-current fill-none"><use href="../assets/icons/icons.svg#icon-arrow-right"></use></svg>
</button>
</div>
</div>
<script type="module" src="../scripts/onboarding.js"></script>
<script type="module">
import { mountTitlebar, initTitlebar } from '../scripts/shared/titlebar.js';
document.addEventListener('DOMContentLoaded', async () => {
mountTitlebar();
await initTitlebar();
});
</script>
<script type="module" src="../scripts/pages/onboarding.js"></script>
</body>
</html>

View File

@@ -5,47 +5,24 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>설정 - Chakmate</title>
<link rel="stylesheet" href="../styles/main.css">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol id="icon-x-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
</symbol>
<symbol id="icon-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"/>
</symbol>
<symbol id="icon-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
</symbol>
</svg>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<header class="flex items-center justify-between p-5 bg-surface-card rounded-b-3xl shadow-[0_4px_20px_rgba(30,27,46,0.08)]">
<a href="scene_dashboard.html" class="flex items-center gap-2 px-4 py-2 rounded-full text-text-secondary hover:bg-surface-secondary transition-colors">
<svg class="w-5 h-5"><use href="#icon-home"></use></svg>
<span class="text-sm font-medium">홈으로</span>
</a>
<h1 class="font-display text-lg font-semibold text-text-primary">설정</h1>
<div class="w-[80px]"></div>
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
<header class="page-header">
<div class="header-left flex items-center gap-3">
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
</div>
<div class="header-right flex items-center gap-3"></div>
</header>
<div class="settings-container max-w-[600px] w-full mx-auto p-4">
<div class="settings-container page-content">
<div class="settings-header mb-8">
<h2 class="settings-title text-3xl mb-2 font-display">설정</h2>
<p class="settings-subtitle text-text-secondary">앱 환경 사용자 지정</p>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<div class="settings-section bg-surface-card rounded-xl shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">알림</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
@@ -63,7 +40,7 @@
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<div class="settings-section bg-surface-card rounded-xl shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">정리 습관</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
@@ -74,51 +51,14 @@
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">삭제 확인</div>
<div class="setting-desc text-sm text-text-secondary">파일 삭제 시 확인 메시지를 표시합니다</div>
<div class="setting-name font-medium mb-1">스캔 경로</div>
<div class="setting-desc text-sm text-text-secondary" id="scanPathDisplay">Downloads</div>
</div>
<div class="toggle active w-14 h-8 bg-surface-secondary rounded-full cursor-pointer transition-all duration-250 relative" id="toggle-confirmDelete"></div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6 mb-5">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">테마</h3>
<div class="theme-options flex gap-3">
<div class="theme-option light w-12 h-12 rounded-[12px] cursor-pointer border-[3px] border-transparent transition-all duration-250 hover:scale-105 flex items-center justify-center bg-gradient-to-br from-[#f8fafc] to-[#e2e8f0]" data-theme="light">
<svg class="w-5 h-5 stroke-text-primary fill-none"><use href="#icon-sun"></use></svg>
</div>
<div class="theme-option dark w-12 h-12 rounded-[12px] cursor-pointer border-[3px] border-transparent transition-all duration-250 hover:scale-105 flex items-center justify-center bg-gradient-to-br from-[#0f0e17] to-[#252336]" data-theme="dark">
<svg class="w-5 h-5 stroke-text-primary fill-none"><use href="#icon-moon"></use></svg>
</div>
</div>
</div>
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6">
<h3 class="settings-section-title text-lg mb-5 pb-3 border-b border-surface-secondary font-display">데이터</h3>
<div class="setting-row flex items-center justify-between py-4 border-b border-surface-secondary">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">데이터 내보내기</div>
<div class="setting-desc text-sm text-text-secondary">정리된 파일 구조를 JSON으로 내보냅니다</div>
</div>
<button class="btn btn-secondary" id="export-data">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-download"></use></svg>
<span>내보내기</span>
</button>
</div>
<div class="setting-row flex items-center justify-between py-4 border-b-0">
<div class="setting-info flex-1">
<div class="setting-name font-medium mb-1">데이터 초기화</div>
<div class="setting-desc text-sm text-text-secondary">모든 데이터를 초기 상태로 되돌립니다</div>
</div>
<button class="btn btn-secondary text-accent-danger" id="reset-data">
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-trash"></use></svg>
<span>초기화</span>
</button>
<button class="text-primary text-sm font-medium" id="changePathBtn">변경</button>
</div>
</div>
</div>
<script type="module" src="../scripts/shared/state.js"></script>
<script type="module" src="../scripts/settings.js"></script>
<script type="module" src="../scripts/pages/settings.js"></script>
</body>
</html>

View File

@@ -6,77 +6,62 @@
<title>스와이프 모드 - Chakmate</title>
<link rel="stylesheet" href="../styles/main.css">
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased">
<!-- Heroicons SVG Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
</symbol>
<symbol id="icon-arrow-uturn-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 7v6h6M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
</symbol>
<symbol id="icon-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</symbol>
</svg>
<header class="flex items-center justify-between p-5 bg-surface-card rounded-b-3xl shadow-[0_4px_20px_rgba(30,27,46,0.08)]">
<a href="scene_dashboard.html" class="flex items-center gap-2 px-4 py-2 rounded-full text-text-secondary hover:bg-surface-secondary transition-colors">
<svg class="w-5 h-5"><use href="#icon-home"></use></svg>
<span class="text-sm font-medium"></span>
</a>
<h1 class="font-display text-lg font-semibold text-text-primary">스와이프 모드</h1>
<button class="flex items-center gap-2 px-5 py-3 bg-gradient-to-r from-primary to-primary-dark rounded-full text-white text-sm font-semibold shadow-md transition-all duration-250 hover:-translate-y-0.5 hover:shadow-lg disabled:opacity-40 disabled:cursor-not-allowed disabled:transform-none" id="undoBtn" disabled>
<svg class="w-4 h-4"><use href="#icon-arrow-uturn-left"></use></svg>
실행 취소
</button>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
<header class="page-header">
<div class="header-left flex items-center gap-3">
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
</div>
<div class="header-right flex items-center gap-3">
<button class="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-primary to-primary-dark rounded-full text-white text-sm font-semibold shadow-md transition-all duration-250 hover:-translate-y-0.5 hover:shadow-lg disabled:opacity-40 disabled:cursor-not-allowed disabled:transform-none" id="undoBtn" disabled>
<svg class="w-4 h-4"><use href="../assets/icons/icons.svg#icon-arrow-uturn-left"></use></svg>
실행 취소
</button>
</div>
</header>
<main class="flex flex-col min-h-[calc(100vh-70px)] px-6">
<div class="text-center mb-8">
<h2 class="text-[28px] font-bold text-text-primary mb-1" id="currentFileName">project_proposal.pdf</h2>
<div id="swipe" class="page-content">
<div class="text-center mb-4">
<h2 class="text-xl font-bold text-text-primary mb-1" id="currentFileName">파일을 로딩 중...</h2>
<p class="text-text-muted text-sm">왼쪽으로 스와이프하면 삭제, 오른쪽으로 스와이프하면 보관</p>
</div>
<div class="flex-1 flex flex-col items-center justify-center relative py-8">
<div class="relative w-full max-w-[320px] h-[420px]" id="cardStack"></div>
<div class="flex-1 flex flex-col items-center justify-center relative min-h-0">
<div class="relative w-full max-w-[280px] aspect-[3/4] max-h-[380px]" id="cardStack"></div>
<div class="hidden flex-col items-center justify-center text-center p-16" id="emptyState">
<div class="w-[120px] h-[120px] bg-gradient-to-br from-primary to-secondary rounded-full flex items-center justify-center mb-6 animate-pulse-slow shadow-glow">
<svg class="w-[60px] h-[60px] text-white"><use href="#icon-check"></use></svg>
<div class="hidden flex-col items-center justify-center text-center p-8 absolute inset-0" id="emptyState">
<div class="w-[100px] h-[100px] bg-gradient-to-br from-primary to-secondary rounded-full flex items-center justify-center mb-6 animate-pulse-slow shadow-glow">
<svg class="w-[50px] h-[50px] text-white"><use href="../assets/icons/icons.svg#icon-check"></use></svg>
</div>
<h2 class="text-[28px] font-bold text-text-primary mb-3">모두 완료!</h2>
<h2 class="text-2xl font-bold text-text-primary mb-3">모두 완료!</h2>
<p class="text-text-muted text-base mb-6">모든 파일을 검토했습니다</p>
<button class="flex items-center gap-2 px-6 py-4 bg-gradient-to-r from-primary to-primary-dark rounded-full text-white text-base font-semibold shadow-md transition-all duration-250 hover:-translate-y-0.5 hover:shadow-lg" onclick="resetFiles()">
<button class="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-primary to-primary-dark rounded-full text-white text-base font-semibold shadow-md transition-all duration-250 hover:-translate-y-0.5 hover:shadow-lg" onclick="resetFiles()">
다시 시작
</button>
</div>
</div>
<div class="flex justify-center gap-6 mt-8" id="actionButtons">
<button class="w-[80px] h-[80px] rounded-full bg-gradient-to-br from-accent-danger to-red-500 text-white flex items-center justify-center shadow-md transition-transform duration-500 active:scale-90" onclick="handleSwipe('delete')">
<svg class="w-9 h-9"><use href="#icon-trash"></use></svg>
<div class="flex justify-center gap-6 py-4" id="actionButtons">
<button class="w-[64px] h-[64px] rounded-full bg-gradient-to-br from-accent-danger to-red-500 text-white flex items-center justify-center shadow-md transition-transform duration-500 active:scale-90" onclick="handleSwipe('delete')">
<svg class="w-7 h-7"><use href="../assets/icons/icons.svg#icon-trash"></use></svg>
</button>
<button class="w-[80px] h-[80px] rounded-full bg-gradient-to-br from-accent to-emerald-500 text-white flex items-center justify-center shadow-md transition-transform duration-500 active:scale-90" onclick="handleSwipe('keep')">
<svg class="w-9 h-9"><use href="#icon-check"></use></svg>
<button class="w-[64px] h-[64px] rounded-full bg-gradient-to-br from-primary to-primary-dark text-white flex items-center justify-center shadow-md transition-transform duration-500 active:scale-90" onclick="handleSwipe('keep')">
<svg class="w-7 h-7"><use href="../assets/icons/icons.svg#icon-folder"></use></svg>
</button>
</div>
</main>
<footer class="p-5 bg-surface-card rounded-t-3xl shadow-[0_-4px_20px_rgba(30,27,46,0.08)]">
<div class="flex items-center gap-4">
<div class="flex-1 h-2 bg-surface-secondary rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-primary to-secondary rounded-full transition-all duration-500" id="progressFill" style="width: 0%"></div>
<div class="flex justify-between items-center px-4 py-2 bg-surface-card rounded-full max-w-[280px] mx-auto">
<span class="text-text-muted text-xs">삭제</span>
<div class="flex-1 mx-3 h-2 bg-surface-secondary rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-primary to-accent rounded-full transition-all duration-300" id="progressFill" style="width: 0%"></div>
</div>
<div class="font-display text-sm font-semibold text-primary min-w-[60px] text-right" id="progressText">0%</div>
<span class="text-text-muted text-xs" id="progressText">0 / 0</span>
</div>
</footer>
</div>
<div class="fixed top-[90px] left-1/2 -translate-x-1/2 bg-text-primary text-surface-card px-6 py-3 rounded-full text-sm font-medium shadow-lg opacity-0 pointer-events-none transition-all duration-250 z-[200]" id="toast">파일 보관됨</div>
<div id="toast" class="fixed bottom-24 left-1/2 -translate-x-1/2 bg-surface-card text-text-primary px-6 py-3 rounded-full shadow-lg flex items-center gap-2 opacity-0 transition-opacity duration-300 pointer-events-none"></div>
<script type="module" src="../scripts/swipe.js"></script>
<script type="module" src="../scripts/pages/swipe.js"></script>
</body>
</html>

View File

@@ -4,125 +4,55 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>구조 제안 - Chakmate</title>
<!-- Heroicons SVG Sprite -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none; width: 0; height: 0;">
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14M12 5l7 7-7 7"/>
</symbol>
<symbol id="icon-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
</symbol>
<symbol id="icon-folder-solid" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h6l4 4h6a2 2 0 012 2v8a2 2 0 01-2 2z"/>
</symbol>
<symbol id="icon-folder-open" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2v11z"/>
</symbol>
<symbol id="icon-document" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"/>
</symbol>
<symbol id="icon-x-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"/>
</symbol>
<symbol id="icon-chevron-up" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="18 15 12 9 6 15"/>
</symbol>
<symbol id="icon-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"/>
</symbol>
<symbol id="icon-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"/>
<line x1="5" y1="12" x2="19" y2="12"/>
</symbol>
<symbol id="icon-minus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"/>
</symbol>
<symbol id="icon-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</symbol>
<symbol id="icon-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6"/>
<polyline points="8 6 2 12 8 18"/>
</symbol>
</svg>
<link rel="stylesheet" href="../styles/main.css">
<script type="module" src="../scripts/visualization.js"></script>
<div class="app">
<!-- Header -->
<header class="header">
<div class="header-left">
<div class="header-logo">
<svg viewBox="0 0 24 24" fill="none">
<defs>
<linearGradient id="logoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#06b6d4"/>
<stop offset="50%" stop-color="#3b82f6"/>
<stop offset="100%" stop-color="#0ea5e9"/>
</linearGradient>
</defs>
<path d="M6 3a3 3 0 013 3v2a3 3 0 01-3 3H4a2 2 0 00-2 2v6a2 2 0 002 2h2a3 3 0 013 3v2a3 3 0 01-3 3H6a3 3 0 01-3-3v-2a3 3 0 013-3h2a2 2 0 002-2V8a2 2 0 00-2-2H6a3 3 0 01-3-3V6a3 3 0 013-3z" fill="url(#logoGrad)"/>
<circle cx="12" cy="12" r="3" fill="white" opacity="0.3"/>
</svg>
</div>
<h1 class="header-title">구조 제안</h1>
</head>
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
<header class="page-header">
<div class="header-left flex items-center gap-3">
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
</div>
<div class="header-right flex items-center gap-3">
<div class="streak-badge flex items-center gap-2 bg-surface-card px-4 py-2 rounded-full shadow-sm">
<svg class="w-5 h-5 stroke-accent-warn fill-none text-orange-500"><use href="../assets/icons/icons.svg#icon-fire"></use></svg>
<span class="font-bold text-accent-warn" id="streak-count">7</span>
<span class="text-text-muted text-sm"></span>
</div>
<div class="header-right">
<a href="scene_dashboard.html" class="back-btn" aria-label="Go back">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<use href="#icon-arrow-left"></use>
</svg>
</a>
</div>
</header>
</div>
</header>
<div class="page-content">
<div class="comparison-banner">
<div class="comparison-item current">
<div class="comparison-icon">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<use href="#icon-folder"></use>
<use href="../assets/icons/icons.svg#icon-folder"></use>
</svg>
</div>
<span>현재</span>
</div>
<span class="comparison-arrow">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<use href="#icon-arrow-right"></use>
<use href="../assets/icons/icons.svg#icon-arrow-right"></use>
</svg>
</span>
<div class="comparison-item proposed">
<div class="comparison-icon">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<use href="#icon-folder"></use>
<use href="../assets/icons/icons.svg#icon-folder"></use>
</svg>
</div>
<span>AI 제안</span>
</div>
</div>
<!-- Before/After Comparison UI -->
<div class="comparison-container">
<!-- Before Panel - Messy State -->
<div class="comparison-panel">
<div class="comparison-panel-header before">
<div class="comparison-panel-icon before">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<use href="#icon-folder"></use>
<use href="../assets/icons/icons.svg#icon-folder"></use>
</svg>
</div>
<div>
@@ -130,352 +60,35 @@
<div class="comparison-panel-subtitle">지저분한 폴더 구조</div>
</div>
</div>
<div class="before-tree">
<div class="messy-folder">
<div class="messy-folder-name">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
다운로드
</div>
<div class="messy-files">
<span class="messy-file-tag">사진_2024.jpg</span>
<span class="messy-file-tag">invoice.pdf</span>
<span class="messy-file-tag">Screenshot.png</span>
<span class="messy-file-tag">doc.docx</span>
<span class="messy-file-tag">video.mp4</span>
</div>
</div>
<div class="messy-folder">
<div class="messy-folder-name">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
바탕화면
</div>
<div class="messy-files">
<span class="messy-file-tag">report_final.xls</span>
<span class="messy-file-tag">report_v2.xls</span>
<span class="messy-file-tag">report_FINAL.xls</span>
<span class="messy-file-tag">temp.png</span>
</div>
</div>
<div class="messy-folder">
<div class="messy-folder-name">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
문서
</div>
<div class="messy-files">
<span class="messy-file-tag">새 폴더</span>
<span class="messy-file-tag">새 폴더 (2)</span>
<span class="messy-file-tag">archive</span>
<span class="messy-file-tag">backup</span>
</div>
</div>
</div>
<div class="comparison-stats">
<div class="comparison-stat">
<div class="comparison-stat-icon removed">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-x-mark"></use></svg>
</div>
<div>
<div class="comparison-stat-value">87</div>
<div class="comparison-stat-label">분산 파일</div>
</div>
</div>
<div class="comparison-stat">
<div class="comparison-stat-icon removed">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
</div>
<div>
<div class="comparison-stat-value">12</div>
<div class="comparison-stat-label">중복 폴더</div>
</div>
</div>
</div>
<div class="before-tree" id="beforeTree"></div>
</div>
<!-- After Panel - Organized State -->
<div class="comparison-panel">
<div class="comparison-panel-header after">
<div class="comparison-panel-icon after">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use><use href="#icon-check" class="absolute inset-0"></use></svg>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<use href="../assets/icons/icons.svg#icon-check"></use>
</svg>
</div>
<div>
<div class="comparison-panel-title">정리 후</div>
<div class="comparison-panel-subtitle">AI가 제안하는 구조</div>
</div>
</div>
<div class="after-tree">
<div class="clean-folder">
<div class="clean-folder-header">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" stroke-width="2"><use href="#icon-plus"></use></svg>
<span class="clean-folder-name">작업 프로젝트</span>
<span class="clean-folder-count">24개 파일</span>
</div>
<div class="clean-subfolders">
<div class="clean-subfolder">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
2024 보고서
<span class="diff-indicator added">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-plus"></use></svg>
+8
</span>
</div>
<div class="clean-subfolder">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
고객 파일
</div>
</div>
</div>
<div class="clean-folder">
<div class="clean-folder-header">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="clean-folder-name">개인 파일</span>
<span class="clean-folder-count">31개 파일</span>
</div>
<div class="clean-subfolders">
<div class="clean-subfolder">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
사진 아카이브
<span class="diff-indicator added">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-plus"></use></svg>
+15
</span>
</div>
<div class="clean-subfolder">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
문서
</div>
</div>
</div>
<div class="clean-folder">
<div class="clean-folder-header">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="clean-folder-name">유틸리티</span>
<span class="clean-folder-count">18개 파일</span>
</div>
<div class="clean-subfolders">
<div class="clean-subfolder">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
임시 파일
<span class="diff-indicator removed">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-check"></use></svg>
정리됨
</span>
</div>
<div class="clean-subfolder">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
아카이브
</div>
</div>
</div>
</div>
<div class="comparison-stats">
<div class="comparison-stat">
<div class="comparison-stat-icon improved">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-check"></use></svg>
</div>
<div>
<div class="comparison-stat-value">73</div>
<div class="comparison-stat-label">정리된 파일</div>
</div>
</div>
<div class="comparison-stat">
<div class="comparison-stat-icon optimized">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
</div>
<div>
<div class="comparison-stat-value">6</div>
<div class="comparison-stat-label">개선된 구조</div>
</div>
</div>
</div>
<div class="after-tree" id="afterTree"></div>
</div>
</div>
<section class="section">
<div class="section-header">
<h2 class="section-title">AI 추천 구조</h2>
<span class="section-badge" id="selectedCount">7개 중 3개 선택됨</span>
</div>
<div class="tree-container" id="folderTree">
<div class="tree-item expanded" data-category="work" data-folder="work-projects">
<div class="tree-item-header">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="folder" data-folder="work-projects" checked>
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="folder-icon" viewBox="0 0 24 24" fill="currentColor"><use href="#icon-folder-solid"></use></svg>
<span class="folder-name">작업 프로젝트</span>
<div class="folder-meta">
<span class="file-count">12개 파일</span>
<svg class="expand-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-chevron-down"></use></svg>
</div>
</div>
<div class="tree-children">
<div class="tree-children-inner">
<div class="child-item" data-preview="work-projects/2024-reports">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="subfolder" data-folder="work-projects" data-subfolder="2024-reports" checked>
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="child-folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="child-folder-name">2024 보고서</span>
</div>
<div class="child-item" data-preview="work-projects/client-files">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="subfolder" data-folder="work-projects" data-subfolder="client-files" checked>
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="child-folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="child-folder-name">고객 파일</span>
</div>
<div class="child-item" data-preview="work-projects/archive">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="subfolder" data-folder="work-projects" data-subfolder="archive">
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="child-folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="child-folder-name">아카이브</span>
</div>
</div>
</div>
</div>
<div class="tree-item" data-category="personal" data-folder="personal">
<div class="tree-item-header">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="folder" data-folder="personal">
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="folder-icon" viewBox="0 0 24 24" fill="currentColor"><use href="#icon-folder-solid"></use></svg>
<span class="folder-name">개인</span>
<div class="folder-meta">
<span class="file-count">24개 파일</span>
<svg class="expand-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-chevron-down"></use></svg>
</div>
</div>
<div class="tree-children">
<div class="tree-children-inner">
<div class="child-item" data-preview="personal/photos">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="subfolder" data-folder="personal" data-subfolder="photos">
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="child-folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="child-folder-name">사진</span>
</div>
<div class="child-item" data-preview="personal/downloads-archive">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="subfolder" data-folder="personal" data-subfolder="downloads-archive">
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="child-folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="child-folder-name">다운로드 아카이브</span>
</div>
</div>
</div>
</div>
<div class="tree-item" data-category="utility" data-folder="utilities">
<div class="tree-item-header">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="folder" data-folder="utilities">
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="folder-icon" viewBox="0 0 24 24" fill="currentColor"><use href="#icon-folder-solid"></use></svg>
<span class="folder-name">유틸리티</span>
<div class="folder-meta">
<span class="file-count">8개 파일</span>
<svg class="expand-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-chevron-down"></use></svg>
</div>
</div>
<div class="tree-children">
<div class="tree-children-inner">
<div class="child-item" data-preview="utilities/temp-files">
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox-input" data-type="subfolder" data-folder="utilities" data-subfolder="temp-files">
<span class="checkbox-visual">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><use href="#icon-check"></use></svg>
</span>
</label>
<svg class="child-folder-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-folder"></use></svg>
<span class="child-folder-name">임시 파일</span>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="section-header">
<h2 class="section-title">미리보기</h2>
</div>
<div class="preview-container">
<div class="preview-header">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-eye"></use></svg>
<span class="preview-title">폴더 내용</span>
<span class="preview-path" id="previewPath">폴더 선택</span>
</div>
<div class="preview-content" id="previewContent">
<div class="preview-empty">
<svg class="w-12 h-12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><use href="#icon-folder"></use></svg>
<p>폴더를 클릭하여 내용 미리보기</p>
</div>
</div>
</div>
</section>
</div>
<div class="action-bar">
<div class="action-bar-inner">
<div class="progress-bar-container">
<div class="progress-label">
<span id="progressText">7개 폴더 중 0개 적용됨</span>
<span id="progressPercent">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
</div>
</div>
<button class="apply-btn" id="applyBtn" disabled>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><use href="#icon-check"></use></svg>
<span>선택 항목 적용</span>
<span class="count-badge" id="applyCount">0</span>
<div class="flex gap-3 mt-6">
<button class="flex-1 py-3 bg-surface-card rounded-xl text-text-primary font-semibold shadow-sm hover:bg-surface-secondary transition-colors" id="rejectBtn">
거절
</button>
<button class="flex-1 py-3 bg-gradient-to-r from-primary to-primary-dark rounded-xl text-white font-semibold shadow-md hover:-translate-y-0.5 hover:shadow-lg transition-all" id="acceptBtn">
제안 받기
</button>
</div>
</div>
<div class="success-overlay" id="successOverlay">
<div class="success-content">
<div class="success-icon">
<svg class="w-12 h-12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><use href="#icon-check"></use></svg>
</div>
<h2 class="success-title">적용 완료!</h2>
<p class="success-subtitle" id="successSubtitle">3개의 폴더가 정리되었습니다</p>
</div>
</div>
<script type="module" src="../scripts/pages/visualization.js"></script>
</body>
</html>
</html>

View File

@@ -0,0 +1,109 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<!-- Navigation & UI -->
<symbol id="icon-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>
</symbol>
<symbol id="icon-arrow-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14M12 5l7 7-7 7"/>
</symbol>
<symbol id="icon-arrow-uturn-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 7v6h6M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
</symbol>
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"/>
</symbol>
<symbol id="icon-x-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</symbol>
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"/>
</symbol>
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"/>
</symbol>
<symbol id="icon-chevron-up" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="18 15 12 9 6 15"/>
</symbol>
<symbol id="icon-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"/>
</symbol>
<!-- Dashboard & Stats -->
<symbol id="icon-layers" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>
</symbol>
<symbol id="icon-fire" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z"/>
<path d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z"/>
</symbol>
<symbol id="icon-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</symbol>
<symbol id="icon-bolt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</symbol>
<!-- Files & Folders -->
<symbol id="icon-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</symbol>
<symbol id="icon-folder-solid" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h6l4 4h6a2 2 0 012 2v8a2 2 0 01-2 2z"/>
</symbol>
<symbol id="icon-folder-open" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2v11z"/>
</symbol>
<symbol id="icon-document" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>
</symbol>
<symbol id="icon-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</symbol>
<!-- Settings & Tools -->
<symbol id="icon-cog" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 15a3 3 0 100-6 3 3 0 000 6z"/>
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z"/>
</symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
</symbol>
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</symbol>
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</symbol>
<!-- AI & Features -->
<symbol id="icon-shield-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/>
</symbol>
<symbol id="icon-light-bulb" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18h6M10 22h4M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0018 8 6 6 0 006 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 018.91 14"/>
</symbol>
<symbol id="icon-sparkles" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/>
</symbol>
<symbol id="icon-chart-bar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18M9 21V9"/>
</symbol>
<symbol id="icon-tag" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>
</symbol>
<symbol id="icon-clock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
</symbol>
<symbol id="icon-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>
</symbol>
<symbol id="icon-code" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
</symbol>
<symbol id="icon-minus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -1,210 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
let gamificationData = AppState.getGamificationData();
// UI Elements
const streakCount = document.getElementById('streakCount');
const streakIcon = document.getElementById('streakIcon');
const streakMessage = document.getElementById('streakMessage');
const progressFill = document.getElementById('progressFill');
const daysCompleted = document.getElementById('daysCompleted');
const weekGrid = document.getElementById('weekGrid');
const achievementsGrid = document.getElementById('achievementsGrid');
const habitToggle = document.getElementById('habitToggle');
const toast = document.getElementById('toast');
const toastIcon = document.getElementById('toastIcon');
const toastText = document.getElementById('toastText');
const confettiContainer = document.getElementById('confettiContainer');
const motivationalMessages = [
"불을 이어가세요!",
"오늘 불이 나고 있어요!",
"놀라운 일관성!",
"아무도 당신을 막을 수 없어요!",
"챔피언의 행동!"
];
const dailyTips = [
{ text: "작은 걸음이 큰 변화를 만듭니다! 오늘 5분만 투자하세요.", author: "일일 동기" },
{ text: "정돈된 공간은 정돈된 마음을 만듭니다. 작게 시작하세요!", author: "정리의 지혜" },
{ text: "정리된 모든 파일이 진보입니다.庆祝하세요!", author: "업적 달성" },
{ text: "미래의 당신이 오늘 정리한 것에 감사할 것입니다.", author: "타임 트래블러" },
{ text: "일관성이 완벽함을 이깁니다. 매일来吧!", author: "습관 마스터" }
];
// Animate streak count
function animateCount(element, target, duration = 1500) {
const start = 0;
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOut = 1 - Math.pow(1 - progress, 3);
const current = Math.floor(start + (target - start) * easeOut);
element.textContent = current;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
// Render week grid
function renderWeekGrid(progress) {
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
const today = new Date().getDay();
const mondayIndex = today === 0 ? 6 : today - 1;
weekGrid.innerHTML = days.map((day, i) => {
const completed = progress[i];
const isToday = i === mondayIndex;
return `
<div class="week-day">
<div class="week-day-label">${day}</div>
<div class="week-day-indicator ${completed ? 'completed' : ''} ${isToday ? 'today' : ''}">
${completed ? '✅' : (isToday ? '◉' : '')}
</div>
</div>
`;
}).join('');
}
// Render achievements
function renderAchievements(achievements) {
achievementsGrid.innerHTML = achievements.map(ach => `
<div class="achievement ${ach.unlocked ? `unlocked ${ach.tier}` : 'locked'}" data-id="${ach.id}">
<span class="achievement-icon">${ach.icon}</span>
<span class="achievement-name">${ach.name}</span>
<div class="achievement-tooltip">${ach.requirement}</div>
</div>
`).join('');
}
// Show toast notification
function showToast(icon, message) {
toastIcon.textContent = icon;
toastText.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Confetti celebration
function triggerConfetti() {
const colors = ['#6366f1', '#f472b6', '#34d399', '#fbbf24', '#818cf8', '#34d399'];
const confettiCount = 50;
for (let i = 0; i < confettiCount; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + 'vw';
confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
confetti.style.borderRadius = Math.random() > 0.5 ? '50%' : '0';
confetti.style.width = Math.random() * 10 + 5 + 'px';
confetti.style.height = confetti.style.width;
confetti.style.animation = `confettiFall ${Math.random() * 2 + 2}s ease-out forwards`;
confetti.style.animationDelay = Math.random() * 0.5 + 's';
confettiContainer.appendChild(confetti);
setTimeout(() => {
confetti.remove();
}, 4000);
}
}
// Trigger badge unlock animation
function animateBadgeUnlock(achievementId) {
const badge = document.querySelector(`.achievement[data-id="${achievementId}"]`);
if (badge) {
badge.classList.add('just-unlocked');
// Add sparkles
for (let i = 0; i < 6; i++) {
const sparkle = document.createElement('span');
sparkle.className = 'sparkle';
sparkle.textContent = '✨';
sparkle.style.left = Math.random() * 100 + '%';
sparkle.style.top = Math.random() * 100 + '%';
badge.appendChild(sparkle);
}
setTimeout(() => {
badge.classList.remove('just-unlocked');
badge.querySelectorAll('.sparkle').forEach(s => s.remove());
}, 600);
}
}
// Initialize page
function init() {
gamificationData = AppState.getGamificationData();
// Animate streak count
animateCount(streakCount, gamificationData.streak);
// Streak icon animation
if (gamificationData.streak > 0) {
streakIcon.classList.add('animate');
setTimeout(() => streakIcon.classList.remove('animate'), 1000);
}
// Update streak message
if (gamificationData.streak > 0) {
streakMessage.textContent = motivationalMessages[Math.floor(Math.random() * motivationalMessages.length)];
}
// Animate progress bar
const completedDays = gamificationData.weeklyProgress.filter(Boolean).length;
setTimeout(() => {
progressFill.style.width = (completedDays / 7 * 100) + '%';
}, 300);
daysCompleted.textContent = completedDays;
// Render week grid
renderWeekGrid(gamificationData.weeklyProgress);
// Render achievements
renderAchievements(gamificationData.achievements);
// Set habit toggle state
habitToggle.checked = gamificationData.habitReminderEnabled;
// Random daily tip
const randomTip = dailyTips[Math.floor(Math.random() * dailyTips.length)];
document.getElementById('tipText').textContent = `"${randomTip.text}"`;
document.querySelector('.tip-author').textContent = `${randomTip.author}`;
// Habit toggle handler
habitToggle.addEventListener('change', (e) => {
gamificationData.habitReminderEnabled = e.target.checked;
AppState.setGamificationData(gamificationData);
showToast(e.target.checked ? '🔔' : '🔕', e.target.checked ? 'Reminders enabled!' : 'Reminders disabled');
});
// Achievement click handler (for demo unlock)
achievementsGrid.addEventListener('click', (e) => {
const achievement = e.target.closest('.achievement');
if (achievement && !achievement.classList.contains('unlocked')) {
// Simulate unlock for demo
const achId = achievement.dataset.id;
const achData = gamificationData.achievements.find(a => a.id === achId);
if (achData) {
achData.unlocked = true;
AppState.setGamificationData(gamificationData);
animateBadgeUnlock(achId);
triggerConfetti();
showToast(achData.icon, `${achData.name} unlocked!`);
renderAchievements(gamificationData.achievements);
}
}
});
}
// Run on load
init();
});

View File

@@ -1,273 +1,58 @@
document.addEventListener('DOMContentLoaded', () => {
const state = {
currentScreen: 'onboarding',
onboardingStep: 1,
streak: parseInt(localStorage.getItem('chackly_streak') || '7'),
filesOrganized: parseInt(localStorage.getItem('chackly_files_organized') || '248'),
foldersManaged: parseInt(localStorage.getItem('chackly_folders') || '12'),
swipeIndex: 0,
swipeHistory: [],
theme: localStorage.getItem('chackly_theme') || 'light',
settings: JSON.parse(localStorage.getItem('chackly_settings') || '{"notifications":true,"habits":false,"autoOrganize":false,"confirmDelete":true}')
import { AppState } from './shared/state.js';
export const FILE_TYPES = {
pdf: { label: 'PDF', color: '#ef4444', icon: 'document' },
doc: { label: 'Doc', color: '#3b82f6', icon: 'document' },
image: { label: 'Image', color: '#8b5cf6', icon: 'image' },
video: { label: 'Video', color: '#ec4899', icon: 'image' },
excel: { label: 'Excel', color: '#10b981', icon: 'chart-bar' },
ppt: { label: 'PPT', color: '#f97316', icon: 'document' },
archive: { label: 'Archive', color: '#6b7280', icon: 'folder' },
};
export function getFileTypeInfo(type) {
return FILE_TYPES[type] || { label: 'File', color: '#6366f1', icon: 'document' };
}
export const SUGGESTIONS = [
{ name: 'Photos to 2024', detail: '2 files moved', selected: false },
{ name: 'Docs to quarterly', detail: '3 files moved', selected: true },
{ name: 'Archives to storage', detail: '1 files moved', selected: false }
];
export const ACHIEVEMENTS = [
{ id: 'first_sort', name: 'First Sort', icon: 'trophy', color: 'gold', unlocked: true, tier: 'gold', requirement: 'Complete first sort session' },
{ id: 'week_warrior', name: 'Week Warrior', icon: 'star', color: 'gold', unlocked: true, tier: 'gold', requirement: '7 days consecutive' },
{ id: 'streak_7', name: '7-Day Streak', icon: 'fire', color: 'silver', unlocked: true, tier: 'silver', requirement: 'Maintain 7-day streak' },
{ id: 'diamond', name: 'Diamond', icon: 'star', color: 'gold', unlocked: true, tier: 'gold', requirement: 'Maintain 30-day streak' },
{ id: 'organizer', name: 'Organizer Pro', icon: 'folder', color: 'silver', unlocked: false, tier: 'silver', requirement: 'Organize 100 files' },
{ id: 'minimalist', name: 'Minimalist', icon: 'sparkles', color: 'bronze', unlocked: false, tier: 'bronze', requirement: 'Delete 50 files' },
{ id: 'speed_demon', name: 'Speed Demon', icon: 'bolt', color: 'gold', unlocked: false, tier: 'gold', requirement: 'Organize 50 files in one day' },
{ id: 'collector', name: 'Collector', icon: 'folder', color: 'silver', unlocked: false, tier: 'silver', requirement: 'Create 5 custom folders' }
];
export const MOTIVATIONAL_MESSAGES = [
"Keep the fire burning!",
"It's on fire today!",
"Amazing consistency!",
"Nobody can stop you!",
"Champion's actions!"
];
export const DAILY_TIPS = [
{ text: "Small steps lead to big changes! Invest just 5 minutes today.", author: "Daily Motivation" },
{ text: "A tidy space creates a tidy mind. Start small!", author: "Organizing Wisdom" },
{ text: "Every organized file is progress. Celebrate it!", author: "Achievement" },
{ text: "Future you will thank you for organizing today.", author: "Time Traveler" },
{ text: "Consistency achieves perfection. Keep going!", author: "Habit Master" }
];
export async function getStats() {
return {
streak: await AppState.getStreak(),
filesOrganized: await AppState.getFilesOrganized(),
foldersManaged: await AppState.getFoldersManaged()
};
}
const mockFiles = [
{ name: 'IMG_20240115_123456.jpg', type: 'image', size: '3.2 MB', date: '2024년 1월 15일', folder: '📷 사진' },
{ name: 'report_Q4_2023.pdf', type: 'pdf', size: '1.8 MB', date: '2024년 1월 10일', folder: '📄 문서' },
{ name: 'vacation_video.mp4', type: 'video', size: '156 MB', date: '2024년 1월 8일', folder: '🎬 영상' },
{ name: 'presentation.pptx', type: 'doc', size: '5.4 MB', date: '2024년 1월 5일', folder: '📄 문서' },
{ name: 'screenshot_2024.png', type: 'image', size: '890 KB', date: '2024년 1월 3일', folder: '📷 사진' },
{ name: 'meeting_notes.docx', type: 'doc', size: '245 KB', date: '2023년 12월 28일', folder: '📄 문서' },
{ name: 'family_photo.jpg', type: 'image', size: '4.1 MB', date: '2023년 12월 25일', folder: '📷 사진' },
{ name: 'project_files.zip', type: 'archive', size: '45 MB', date: '2023년 12월 20일', folder: '📦 압축' },
{ name: 'music_playlist.m3u', type: 'doc', size: '12 KB', date: '2023년 12월 15일', folder: '🎵 음악' },
{ name: 'budget_2024.xlsx', type: 'doc', size: '567 KB', date: '2023년 12월 10일', folder: '📊 스프레드시트' }
];
const fileTree = [
{ name: '📁 문서', type: 'folder', children: [
{ name: '📄 report_Q4_2023.pdf', type: 'pdf', meta: '1.8 MB' },
{ name: '📄 presentation.pptx', type: 'ppt', meta: '5.4 MB' },
{ name: '📄 meeting_notes.docx', type: 'doc', meta: '245 KB' }
]},
{ name: '📁 사진', type: 'folder', children: [
{ name: '🖼️ IMG_20240115.jpg', type: 'image', meta: '3.2 MB' },
{ name: '🖼️ screenshot_2024.png', type: 'image', meta: '890 KB' },
{ name: '🖼️ family_photo.jpg', type: 'image', meta: '4.1 MB' }
]},
{ name: '📁 영상', type: 'folder', children: [
{ name: '🎬 vacation_video.mp4', type: 'video', meta: '156 MB' }
]},
{ name: '📁 압축', type: 'folder', children: [
{ name: '📦 project_files.zip', type: 'archive', meta: '45 MB' }
]}
];
const suggestions = [
{ name: '📷 사진 → 2024', detail: '2개 파일 이동', selected: false },
{ name: '📄 문서 → quarterly', detail: '3개 파일 이동', selected: true },
{ name: '📦 압축 → 보관', detail: '1개 파일 이동', selected: false }
];
const achievements = [
{ name: '첫 정리', icon: '🏆', color: 'gold', unlocked: true },
{ name: '7일 스트릭', icon: '🔥', color: 'gold', unlocked: true },
{ name: '파일 마스터', icon: '📁', color: 'silver', unlocked: true },
{ name: '정리의 달인', icon: '⭐', color: 'silver', unlocked: true },
{ name: '30일 스트릭', icon: '💎', color: 'gold', unlocked: false },
{ name: 'AI 활용자', icon: '🤖', color: 'bronze', unlocked: false },
{ name: '조직력왕', icon: '👑', color: 'silver', unlocked: false },
{ name: '창의命名', icon: '💡', color: 'bronze', unlocked: false }
];
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screenId).classList.add('active');
state.currentScreen = screenId;
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.classList.remove('active');
btn.classList.add('text-text-muted');
});
const navBtn = document.getElementById('nav-' + screenId);
if (navBtn) {
navBtn.classList.add('active');
navBtn.classList.remove('text-text-muted');
}
}
function updateOnboardingStep(step) {
document.querySelectorAll('.step-dot').forEach((dot, i) => {
const isActive = i + 1 === step;
dot.classList.toggle('active', isActive);
dot.classList.toggle('bg-primary', isActive);
dot.classList.toggle('bg-text-muted', !isActive);
dot.style.width = isActive ? '32px' : '8px';
});
document.querySelectorAll('.onboarding-step').forEach(s => {
const isActive = parseInt(s.dataset.step) === step;
s.classList.toggle('active', isActive);
s.classList.toggle('hidden', !isActive);
});
state.onboardingStep = step;
}
function renderFileTree() {
const container = document.getElementById('file-tree');
container.innerHTML = fileTree.map(item => `
<div class="tree-item folder flex items-center gap-3 p-3 bg-accent/10 rounded-[12px] cursor-pointer hover:bg-overlay hover:translate-x-1 transition-all duration-150">
<div class="tree-icon folder-icon w-8 h-8 rounded-[8px] flex items-center justify-center flex-shrink-0 bg-primary/15 text-primary">
<svg class="w-[18px] h-[18px] stroke-current fill-none stroke-2"><use href="#icon-folder"></use></svg>
</div>
<div class="tree-item-info flex-1 min-w-0">
<div class="tree-item-name font-medium truncate">${item.name}</div>
<div class="tree-item-meta text-xs text-text-muted">${item.children.length}개 항목</div>
</div>
</div>
<div class="tree-children ml-8 flex flex-col gap-2">
${item.children.map(child => `
<div class="tree-item flex items-center gap-3 p-3 bg-surface-secondary rounded-[12px] cursor-pointer hover:bg-overlay hover:translate-x-1 transition-all duration-150">
<div class="tree-icon ${child.type === 'image' ? 'image-icon' : child.type === 'video' ? 'image-icon' : 'doc-icon'} w-8 h-8 rounded-[8px] flex items-center justify-center flex-shrink-0 ${child.type === 'image' ? 'bg-pink-500/15 text-secondary' : child.type === 'video' ? 'bg-purple-500/15 text-purple-500' : 'bg-amber-500/15 text-accent-warn'}">
<svg class="w-[18px] h-[18px] stroke-current fill-none stroke-2"><use href="#icon-document"></use></svg>
</div>
<div class="tree-item-info flex-1 min-w-0">
<div class="tree-item-name font-medium truncate">${child.name}</div>
<div class="tree-item-meta text-xs text-text-muted">${child.meta}</div>
</div>
</div>
`).join('')}
</div>
`).join('');
}
function renderSuggestions() {
const container = document.getElementById('suggestion-list');
container.innerHTML = suggestions.map((s, i) => `
<div class="suggestion-item flex items-center gap-3 p-3 bg-surface-secondary rounded-[12px] cursor-pointer border-2 border-transparent transition-all duration-150 ${s.selected ? 'bg-primary/10 border-primary' : 'hover:bg-overlay hover:border-primary-light'}" data-index="${i}">
<div class="suggestion-checkbox w-6 h-6 border-2 ${s.selected ? 'bg-primary border-primary' : 'border-text-muted'} rounded-[8px] flex items-center justify-center flex-shrink-0 transition-all duration-150">
<svg class="w-3.5 h-3.5 stroke-white fill-none ${s.selected ? 'opacity-100' : 'opacity-0'} transition-opacity duration-150"><use href="#icon-check"></use></svg>
</div>
<div class="suggestion-content flex-1">
<div class="suggestion-name font-medium mb-0.5">${s.name}</div>
<div class="suggestion-detail text-sm text-text-secondary">${s.detail}</div>
</div>
<svg class="suggestion-arrow w-5 h-5 stroke-text-muted fill-none"><use href="#icon-chevron-right"></use></svg>
</div>
`).join('');
container.querySelectorAll('.suggestion-item').forEach(item => {
item.addEventListener('click', () => {
const idx = parseInt(item.dataset.index);
suggestions[idx].selected = !suggestions[idx].selected;
renderSuggestions();
});
});
}
function renderAchievements() {
const container = document.getElementById('achievements-grid');
container.innerHTML = achievements.map(a => `
<div class="achievement aspect-square bg-surface-secondary rounded-[12px] flex flex-col items-center justify-center gap-1 p-2 transition-all duration-250 cursor-pointer hover:scale-105 ${a.unlocked ? '' : 'opacity-40 grayscale'}">
<div class="achievement-icon w-9 h-9 rounded-[8px] flex items-center justify-center ${a.unlocked ? a.color === 'gold' ? 'bg-gradient-to-br from-amber-400 to-amber-600 text-white' : a.color === 'silver' ? 'bg-gradient-to-br from-gray-300 to-gray-500 text-white' : 'bg-gradient-to-br from-amber-700 to-amber-900 text-white' : 'bg-overlay text-primary'}">
<span class="text-lg">${a.icon}</span>
</div>
<div class="achievement-name text-[10px] text-center text-text-secondary">${a.name}</div>
<div class="badge-tooltip absolute -top-8 left-1/2 -translate-x-1/2 bg-text-primary text-surface-card px-2 py-1 rounded-[8px] text-xs whitespace-nowrap opacity-0 invisible transition-all duration-150">${a.name}</div>
</div>
`).join('');
}
function updateSwipeCard() {
if (state.swipeIndex >= mockFiles.length) return;
const file = mockFiles[state.swipeIndex];
const card = document.getElementById('current-card');
card.style.opacity = '0';
card.style.transform = 'scale(0.9) translateY(30px)';
card.classList.add('animate-card-enter');
requestAnimationFrame(() => {
card.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
card.style.opacity = '1';
card.style.transform = 'scale(1) translateY(0)';
});
document.getElementById('card-name').textContent = file.name;
document.getElementById('card-size').textContent = file.size;
document.getElementById('card-folder').textContent = file.folder;
document.getElementById('card-date').textContent = file.date;
const cardPreview = document.getElementById('card-preview');
cardPreview.innerHTML = `<use href="#icon-${file.type === 'image' ? 'photo' : file.type === 'video' ? 'photo' : file.type === 'pdf' ? 'document' : 'document'}"></use>`;
document.getElementById('swipe-progress').textContent = `${state.swipeIndex + 1}/${mockFiles.length} 파일`;
}
document.getElementById('start-organizing').addEventListener('click', () => {
showScreen('dashboard');
});
document.getElementById('nav-dashboard').addEventListener('click', () => showScreen('dashboard'));
document.getElementById('nav-swipe').addEventListener('click', () => showScreen('swipe'));
document.getElementById('nav-ai-suggestion').addEventListener('click', () => showScreen('ai-suggestion'));
document.getElementById('nav-settings').addEventListener('click', () => showScreen('settings'));
document.getElementById('apply-suggestions').addEventListener('click', () => showScreen('ai-suggestion'));
document.getElementById('ai-cancel').addEventListener('click', () => showScreen('dashboard'));
document.getElementById('ai-apply').addEventListener('click', () => {
showScreen('dashboard');
document.getElementById('success-modal').classList.add('active');
document.querySelector('.modal').style.transform = 'scale(1)';
});
document.getElementById('modal-close').addEventListener('click', () => {
document.getElementById('success-modal').classList.remove('active');
});
document.querySelectorAll('.toggle').forEach(toggle => {
toggle.addEventListener('click', () => {
toggle.classList.toggle('active');
toggle.style.background = toggle.classList.contains('active') ? '#3b82f6' : '';
toggle.querySelector('::after') && (toggle.style.setProperty('--after-transform', toggle.classList.contains('active') ? 'translateX(24px)' : ''));
});
});
document.querySelectorAll('.theme-option').forEach(option => {
option.addEventListener('click', () => {
document.querySelectorAll('.theme-option').forEach(o => o.classList.remove('active'));
option.classList.add('active');
document.body.setAttribute('data-theme', option.dataset.theme);
localStorage.setItem('chackly_theme', option.dataset.theme);
});
});
let touchStartX = 0;
const card = document.getElementById('current-card');
card.addEventListener('touchstart', e => {
touchStartX = e.touches[0].clientX;
card.classList.add('dragging');
});
card.addEventListener('touchmove', e => {
const diff = e.touches[0].clientX - touchStartX;
card.style.transform = `translateX(${diff}px) rotate(${diff * 0.05}deg)`;
card.classList.toggle('swiping-left', diff < -50);
card.classList.toggle('swiping-right', diff > 50);
});
card.addEventListener('touchend', e => {
const diff = e.changedTouches[0].clientX - touchStartX;
card.classList.remove('dragging', 'swiping-left', 'swiping-right');
if (Math.abs(diff) > 100) {
state.swipeHistory.push({ file: mockFiles[state.swipeIndex], action: diff > 0 ? 'keep' : 'delete' });
state.swipeIndex++;
updateSwipeCard();
} else {
card.style.transform = '';
}
});
document.getElementById('swipe-delete').addEventListener('click', () => {
state.swipeHistory.push({ file: mockFiles[state.swipeIndex], action: 'delete' });
state.swipeIndex++;
updateSwipeCard();
});
document.getElementById('swipe-keep').addEventListener('click', () => {
state.swipeHistory.push({ file: mockFiles[state.swipeIndex], action: 'keep' });
state.swipeIndex++;
updateSwipeCard();
});
document.getElementById('swipe-undo').addEventListener('click', () => {
if (state.swipeHistory.length > 0) {
state.swipeIndex--;
state.swipeHistory.pop();
updateSwipeCard();
}
});
renderFileTree();
renderSuggestions();
renderAchievements();
updateSwipeCard();
});
export { AppState };

View File

@@ -1,37 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const state = {
onboardingStep: 1
};
function updateOnboardingStep(step) {
document.querySelectorAll('.step-dot').forEach((dot, i) => {
const isActive = i + 1 === step;
dot.classList.toggle('active', isActive);
dot.classList.toggle('bg-primary', isActive);
dot.classList.toggle('bg-text-muted', !isActive);
dot.style.width = isActive ? '32px' : '8px';
});
document.querySelectorAll('.onboarding-step').forEach(s => {
const isActive = parseInt(s.dataset.step) === step;
s.classList.toggle('active', isActive);
s.classList.toggle('hidden', !isActive);
});
state.onboardingStep = step;
}
document.querySelectorAll('.step-dot').forEach(dot => {
dot.addEventListener('click', () => {
const step = parseInt(dot.dataset.step);
updateOnboardingStep(step);
});
});
const startBtn = document.getElementById('start-organizing');
if (startBtn) {
startBtn.addEventListener('click', () => {
window.location.href = 'scene_dashboard.html';
});
}
updateOnboardingStep(1);
});

View File

@@ -1,25 +1,27 @@
document.addEventListener('DOMContentLoaded', () => {
import { SUGGESTIONS, ACHIEVEMENTS, AppState, getStats } from '../main.js';
document.addEventListener('DOMContentLoaded', async () => {
const state = {
streak: AppState.getStreak(),
filesOrganized: AppState.getFilesOrganized(),
foldersManaged: AppState.getFoldersManaged()
streak: await AppState.getStreak(),
filesOrganized: await AppState.getFilesOrganized(),
foldersManaged: await AppState.getFoldersManaged()
};
const suggestions = [
{ name: '📷 사진 → 2024', detail: '2개 파일 이동', selected: false },
{ name: '📄 문서 → quarterly', detail: '3개 파일 이동', selected: true },
{ name: '📦 압축 → 보관', detail: '1개 파일 이동', selected: false }
{ name: 'Photos to 2024', detail: '2 files moved', selected: false },
{ name: 'Docs to quarterly', detail: '3 files moved', selected: true },
{ name: 'Archives to storage', detail: '1 files moved', selected: false }
];
const achievements = [
{ name: '첫 정리', icon: '🏆', color: 'gold', unlocked: true },
{ name: '7일 스트릭', icon: '🔥', color: 'gold', unlocked: true },
{ name: '파일 마스터', icon: '📁', color: 'silver', unlocked: true },
{ name: '정리의 달인', icon: '', color: 'silver', unlocked: true },
{ name: '30일 스트릭', icon: '💎', color: 'gold', unlocked: false },
{ name: 'AI 활용자', icon: '🤖', color: 'bronze', unlocked: false },
{ name: '조직력왕', icon: '👑', color: 'silver', unlocked: false },
{ name: '창의命名', icon: '💡', color: 'bronze', unlocked: false }
{ name: 'First Sort', icon: 'trophy', color: 'gold', unlocked: true },
{ name: '7-Day Streak', icon: 'fire', color: 'gold', unlocked: true },
{ name: 'File Master', icon: 'folder', color: 'silver', unlocked: true },
{ name: 'Organizer Pro', icon: 'star', color: 'silver', unlocked: true },
{ name: '30-Day Streak', icon: 'star', color: 'gold', unlocked: false },
{ name: 'AI User', icon: 'sparkles', color: 'bronze', unlocked: false },
{ name: 'Organized King', icon: 'folder', color: 'silver', unlocked: false },
{ name: 'Creative', icon: 'light-bulb', color: 'bronze', unlocked: false }
];
function renderSuggestions() {
@@ -29,13 +31,13 @@ document.addEventListener('DOMContentLoaded', () => {
container.innerHTML = suggestions.map((s, i) => `
<div class="suggestion-item flex items-center gap-3 p-3 bg-surface-secondary rounded-[12px] cursor-pointer border-2 border-transparent transition-all duration-150 ${s.selected ? 'bg-primary/10 border-primary' : 'hover:bg-overlay hover:border-primary-light'}" data-index="${i}">
<div class="suggestion-checkbox w-6 h-6 border-2 ${s.selected ? 'bg-primary border-primary' : 'border-text-muted'} rounded-[8px] flex items-center justify-center flex-shrink-0 transition-all duration-150">
<svg class="w-3.5 h-3.5 stroke-white fill-none ${s.selected ? 'opacity-100' : 'opacity-0'} transition-opacity duration-150"><use href="#icon-check"></use></svg>
<svg class="w-3.5 h-3.5 stroke-white fill-none ${s.selected ? 'opacity-100' : 'opacity-0'} transition-opacity duration-150"><use href="../assets/icons/icons.svg#icon-check"></use></svg>
</div>
<div class="suggestion-content flex-1">
<div class="suggestion-name font-medium mb-0.5">${s.name}</div>
<div class="suggestion-detail text-sm text-text-secondary">${s.detail}</div>
</div>
<svg class="suggestion-arrow w-5 h-5 stroke-text-muted fill-none"><use href="#icon-chevron-right"></use></svg>
<svg class="suggestion-arrow w-5 h-5 stroke-text-muted fill-none"><use href="../assets/icons/icons.svg#icon-chevron-right"></use></svg>
</div>
`).join('');
@@ -55,7 +57,7 @@ document.addEventListener('DOMContentLoaded', () => {
container.innerHTML = achievements.map(a => `
<div class="achievement aspect-square bg-surface-secondary rounded-[12px] flex flex-col items-center justify-center gap-1 p-2 transition-all duration-250 cursor-pointer hover:scale-105 ${a.unlocked ? '' : 'opacity-40 grayscale'}">
<div class="achievement-icon w-9 h-9 rounded-[8px] flex items-center justify-center ${a.unlocked ? a.color === 'gold' ? 'bg-gradient-to-br from-amber-400 to-amber-600 text-white' : a.color === 'silver' ? 'bg-gradient-to-br from-gray-300 to-gray-500 text-white' : 'bg-gradient-to-br from-amber-700 to-amber-900 text-white' : 'bg-overlay text-primary'}">
<span class="text-lg">${a.icon}</span>
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5"><use href="../assets/icons/icons.svg#icon-${a.icon}"></use></svg>
</div>
<div class="achievement-name text-[10px] text-center text-text-secondary">${a.name}</div>
</div>

View File

@@ -0,0 +1,140 @@
import { MOTIVATIONAL_MESSAGES, DAILY_TIPS, AppState } from '../main.js';
document.addEventListener('DOMContentLoaded', async () => {
let gamificationData = await AppState.getGamificationData();
const achievements = gamificationData.achievements;
const streakCount = document.getElementById('streakCount');
const streakIcon = document.getElementById('streakIcon');
const streakMessage = document.getElementById('streakMessage');
const progressFill = document.getElementById('progressFill');
const daysCompleted = document.getElementById('daysCompleted');
const weekGrid = document.getElementById('weekGrid');
const achievementsGrid = document.getElementById('achievementsGrid');
const habitToggle = document.getElementById('habitToggle');
const toast = document.getElementById('toast');
const toastText = document.getElementById('toastText');
const confettiContainer = document.getElementById('confettiContainer');
function animateCount(element, target, duration = 1500) {
const start = 0;
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOut = 1 - Math.pow(1 - progress, 3);
const current = Math.floor(start + (target - start) * easeOut);
element.textContent = current;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
function renderWeekGrid(progress) {
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
const today = new Date().getDay();
const mondayIndex = today === 0 ? 6 : today - 1;
weekGrid.innerHTML = days.map((day, i) => {
const completed = progress[i];
const isToday = i === mondayIndex;
return `
<div class="week-day">
<div class="week-day-label">${day}</div>
<div class="week-day-indicator ${completed ? 'completed' : ''} ${isToday ? 'today' : ''}">
${completed ? '<svg class="w-4 h-4 text-green-500"><use href="../assets/icons/icons.svg#icon-check"></use></svg>' : (isToday ? '<svg class="w-4 h-4 text-primary"><use href="../assets/icons/icons.svg#icon-clock"></use></svg>' : '')}
</div>
</div>
`;
}).join('');
}
function renderAchievements() {
achievementsGrid.innerHTML = achievements.map(a => `
<div class="achievement ${a.unlocked ? 'unlocked' : 'locked'}" data-id="${a.id}">
<div class="achievement-icon">
<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="1.5"><use href="../assets/icons/icons.svg#icon-${a.icon}"></use></svg>
</div>
<div class="achievement-name">${a.name}</div>
<div class="achievement-tier tier-${a.tier}">${a.tier}</div>
</div>
`).join('');
}
function showToast(message) {
toastText.textContent = message;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3000);
}
function triggerConfetti() {
for (let i = 0; i < 50; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + 'vw';
confetti.style.backgroundColor = `hsl(${Math.random() * 360}, 100%, 50%)`;
confettiContainer.appendChild(confetti);
}
setTimeout(() => confettiContainer.innerHTML = '', 3000);
}
function animateBadgeUnlock(achId) {
const badge = achievementsGrid.querySelector(`[data-id="${achId}"]`);
if (badge) {
badge.classList.add('unlocked');
badge.classList.add('animate');
setTimeout(() => badge.classList.remove('animate'), 600);
}
}
function init() {
if (!gamificationData) return;
animateCount(streakCount, gamificationData.streak);
streakIcon.innerHTML = `<svg class="w-8 h-8" fill="none" stroke="currentColor" stroke-width="1.5"><use href="../assets/icons/icons.svg#icon-${gamificationData.streak > 7 ? 'fire' : 'star'}"></use></svg>`;
streakMessage.textContent = MOTIVATIONAL_MESSAGES[Math.floor(Math.random() * MOTIVATIONAL_MESSAGES.length)];
const completedDays = gamificationData.weeklyProgress.filter(d => d).length;
const progressPercent = (completedDays / 7) * 100;
progressFill.style.width = progressPercent + '%';
daysCompleted.textContent = `${completedDays}/7`;
renderWeekGrid(gamificationData.weeklyProgress);
renderAchievements(gamificationData.achievements);
habitToggle.checked = gamificationData.habitReminderEnabled;
const randomTip = DAILY_TIPS[Math.floor(Math.random() * DAILY_TIPS.length)];
document.querySelector('.tip-text').textContent = randomTip.text;
document.querySelector('.tip-author').textContent = `${randomTip.author}`;
habitToggle.addEventListener('change', async (e) => {
gamificationData.habitReminderEnabled = e.target.checked;
await AppState.setGamificationData(gamificationData);
showToast(e.target.checked ? 'Reminders enabled!' : 'Reminders disabled');
});
achievementsGrid.addEventListener('click', async (e) => {
const achievement = e.target.closest('.achievement');
if (achievement && !achievement.classList.contains('unlocked')) {
const achId = achievement.dataset.id;
const achData = gamificationData.achievements.find(a => a.id === achId);
if (achData) {
achData.unlocked = true;
await AppState.setGamificationData(gamificationData);
animateBadgeUnlock(achId);
triggerConfetti();
showToast(`${achData.name} unlocked!`);
renderAchievements(gamificationData.achievements);
}
}
});
}
init();
});

View File

@@ -0,0 +1,80 @@
import { AppState } from '../main.js';
import { open } from '@tauri-apps/plugin-dialog';
import { downloadDir } from '@tauri-apps/api/path';
let selectedPath = null;
let currentStep = 1;
const totalSteps = 4;
document.addEventListener('DOMContentLoaded', async () => {
alert('1: start');
const defaultPath = 'C:\\Users\\Public\\Downloads';
alert('2: defaultPath = ' + defaultPath);
selectedPath = defaultPath;
const nextBtn = document.getElementById('next-btn');
alert('3: nextBtn = ' + (nextBtn ? 'found' : 'null'));
const selectFolderBtn = document.getElementById('select-folder');
const selectedPathEl = document.getElementById('selected-path');
if (selectedPathEl) {
selectedPathEl.textContent = `기본 폴더: ${defaultPath}`;
}
function updateStep(s) {
currentStep = s;
document.querySelectorAll('.step-dot').forEach((dot, i) => {
const isActive = i + 1 === s;
dot.classList.toggle('active', isActive);
dot.classList.toggle('bg-primary', isActive);
dot.classList.toggle('bg-text-muted', !isActive);
dot.style.width = isActive ? '32px' : '8px';
});
document.querySelectorAll('.onboarding-step').forEach(el => {
const isActive = parseInt(el.dataset.step) === s;
el.classList.toggle('active', isActive);
el.classList.toggle('hidden', !isActive);
});
nextBtn.classList.toggle('hidden', s === totalSteps);
goDashboardBtn.classList.toggle('hidden', s !== totalSteps);
}
document.querySelectorAll('.step-dot').forEach(dot => {
dot.addEventListener('click', () => updateStep(parseInt(dot.dataset.step)));
});
nextBtn.addEventListener('click', () => {
alert('onclick works!');
if (currentStep < totalSteps) {
updateStep(currentStep + 1);
}
});
selectFolderBtn.addEventListener('click', async () => {
try {
const selected = await open({
directory: true,
multiple: false,
title: '정리할 폴더 선택'
});
if (selected) {
selectedPath = selected;
if (selectedPathEl) {
selectedPathEl.textContent = `선택된 폴더: ${selected}`;
}
selectFolderBtn.querySelector('span').textContent = '폴더 변경';
}
} catch (e) {
console.error('Folder selection cancelled or failed:', e);
}
});
goDashboardBtn?.addEventListener('click', async () => {
await AppState.setScanPath(selectedPath);
await AppState.setOnboardingComplete(true);
window.location.href = 'scene_dashboard.html';
});
updateStep(1);
});

View File

@@ -1,11 +1,19 @@
document.addEventListener('DOMContentLoaded', () => {
import { AppState } from '../main.js';
document.addEventListener('DOMContentLoaded', async () => {
const state = {
theme: AppState.getTheme(),
settings: AppState.getSettings()
theme: await AppState.getTheme(),
settings: await AppState.getSettings()
};
function initToggles() {
document.querySelectorAll('.toggle').forEach(toggle => {
const key = toggle.id.replace('toggle-', '');
if (state.settings[key]) {
toggle.classList.add('active');
toggle.style.background = 'linear-gradient(135deg, #6366f1, #8b5cf6)';
}
toggle.addEventListener('click', () => {
toggle.classList.toggle('active');
const isActive = toggle.classList.contains('active');
@@ -17,14 +25,14 @@ document.addEventListener('DOMContentLoaded', () => {
function initThemeOptions() {
document.querySelectorAll('.theme-option').forEach(option => {
option.addEventListener('click', () => {
option.addEventListener('click', async () => {
document.querySelectorAll('.theme-option').forEach(o => {
o.classList.remove('border-primary');
o.classList.add('border-transparent');
});
option.classList.remove('border-transparent');
option.classList.add('border-primary');
AppState.setTheme(option.dataset.theme);
await AppState.setTheme(option.dataset.theme);
state.theme = option.dataset.theme;
});
});
@@ -36,14 +44,14 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function saveSettings() {
async function saveSettings() {
const settings = {
notifications: document.getElementById('toggle-notifications')?.classList.contains('active'),
habits: document.getElementById('toggle-habits')?.classList.contains('active'),
autoOrganize: document.getElementById('toggle-autoOrganize')?.classList.contains('active'),
confirmDelete: document.getElementById('toggle-confirmDelete')?.classList.contains('active')
};
AppState.setSettings(settings);
await AppState.setSettings(settings);
}
document.getElementById('export-data')?.addEventListener('click', () => {
@@ -61,8 +69,8 @@ document.addEventListener('DOMContentLoaded', () => {
URL.revokeObjectURL(url);
});
document.getElementById('reset-data')?.addEventListener('click', () => {
if (confirm('모든 데이터를 초기화하시겠습니까?')) {
document.getElementById('reset-data')?.addEventListener('click', async () => {
if (confirm('Reset all data?')) {
localStorage.clear();
location.reload();
}

179
src/scripts/pages/swipe.js Normal file
View File

@@ -0,0 +1,179 @@
import { getFileTypeInfo } from '../main.js';
import { scanDirectory } from '../shared/fileScanner.js';
import { AppState } from '../shared/state.js';
import { downloadDir } from '@tauri-apps/api/path';
const ICONS = {
delete: 'trash',
keep: 'folder',
calendar: 'clock'
};
document.addEventListener('DOMContentLoaded', async () => {
let files = [];
let historyStack = [];
let currentIndex = 0;
const scanPath = await AppState.getScanPath() || await downloadDir();
files = await scanDirectory(scanPath);
const cardStack = document.getElementById('cardStack');
const emptyState = document.getElementById('emptyState');
const actionButtons = document.getElementById('actionButtons');
const undoBtn = document.getElementById('undoBtn');
const toast = document.getElementById('toast');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const currentFileName = document.getElementById('currentFileName');
function createCard(file) {
const typeInfo = getFileTypeInfo(file.type);
return `
<div class="file-card absolute w-full h-full bg-surface-card rounded-xl shadow-lg flex flex-col overflow-hidden touch-pan-y cursor-grab select-none" data-id="${file.id}">
<div class="card-overlay absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-200 pointer-events-none rounded-xl z-10" id="overlayDelete">
<span class="font-display text-2xl font-bold text-white uppercase tracking-wider">Delete</span>
</div>
<div class="card-overlay absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-200 pointer-events-none rounded-xl z-10" id="overlayKeep">
<span class="font-display text-2xl font-bold text-white uppercase tracking-wider">Keep</span>
</div>
<div class="flex-1 flex flex-col items-center justify-center p-6">
<div class="w-28 h-28 bg-surface-secondary rounded-2xl flex items-center justify-center mb-6">
<svg class="w-12 h-12" fill="none" stroke="currentColor" stroke-width="1.5" style="color: ${typeInfo.color}">
<use href="../assets/icons/icons.svg#icon-${typeInfo.icon}"></use>
</svg>
</div>
<h3 class="font-display text-lg font-semibold text-text-primary text-center mb-2 leading-tight break-all">${file.name}</h3>
<p class="text-text-muted text-sm flex items-center gap-2">
<svg class="w-4 h-4"><use href="../assets/icons/icons.svg#${ICONS.calendar}"></use></svg>
${file.date}
</p>
</div>
<div class="card-footer px-6 py-4 bg-surface-secondary flex justify-center gap-8">
<div class="flex items-center gap-2 text-text-muted text-xs">
<svg class="w-4 h-4"><use href="../assets/icons/icons.svg#${ICONS.delete}"></use></svg>
<span>${file.size}</span>
</div>
<div class="flex items-center gap-2 text-text-muted text-xs">
<span>${typeInfo.label}</span>
</div>
</div>
</div>
`;
}
function renderStack() {
if (!cardStack) return;
cardStack.innerHTML = '';
const visibleFiles = files.slice(currentIndex, currentIndex + 3);
visibleFiles.reverse().forEach((file, i) => {
const reversedIndex = visibleFiles.length - 1 - i;
const card = document.createElement('div');
card.innerHTML = createCard(file);
const cardEl = card.firstElementChild;
cardEl.style.zIndex = 10 + i;
cardEl.style.transform = `scale(${1 - reversedIndex * 0.05}) translateY(${reversedIndex * 8}px)`;
cardEl.style.opacity = reversedIndex === 0 ? 1 : 0.7;
cardStack.appendChild(cardEl);
});
updateProgress();
updateEmptyState();
}
function updateProgress() {
if (!progressFill || !progressText) return;
const total = files.length;
const done = currentIndex;
const percent = total > 0 ? (done / total) * 100 : 0;
progressFill.style.width = `${percent}%`;
progressText.textContent = `${done} / ${total}`;
}
function updateEmptyState() {
if (!emptyState || !actionButtons) return;
const isEmpty = currentIndex >= files.length;
emptyState.classList.toggle('hidden', !isEmpty);
actionButtons.classList.toggle('hidden', !isEmpty);
}
function showToast(message, icon) {
if (!toast) return;
toast.innerHTML = `<svg class="w-5 h-5"><use href="../assets/icons/icons.svg#${icon}"></use></svg><span>${message}</span>`;
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 2500);
}
function swipe(direction) {
if (currentIndex >= files.length) return;
const card = cardStack.lastElementChild;
if (!card) return;
const file = files[currentIndex];
historyStack.push({ ...file, action: direction });
currentIndex++;
card.style.transition = 'transform 0.3s ease-out, opacity 0.3s ease-out';
card.style.transform = direction === 'left'
? 'translateX(-150%) rotate(-30deg)'
: 'translateX(150%) rotate(30deg)';
card.style.opacity = '0';
setTimeout(() => {
renderStack();
showToast(direction === 'left' ? 'Deleted' : 'Kept', direction === 'left' ? 'trash' : 'folder');
}, 300);
}
function undo() {
if (historyStack.length === 0) return;
const last = historyStack.pop();
currentIndex--;
files.splice(currentIndex, 0, last);
renderStack();
showToast('Undo', 'arrow-left');
}
if (undoBtn) undoBtn.addEventListener('click', undo);
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') swipe('left');
if (e.key === 'ArrowRight') swipe('right');
if (e.key === 'z' && (e.ctrlKey || e.metaKey)) undo();
});
let startX = 0;
let currentX = 0;
cardStack?.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
});
cardStack?.addEventListener('touchmove', (e) => {
currentX = e.touches[0].clientX;
const diff = currentX - startX;
const card = cardStack.lastElementChild;
if (card) {
card.style.transform = `translateX(${diff}px) rotate(${diff * 0.1}deg)`;
const overlay = diff < 0 ? '#overlayDelete' : '#overlayKeep';
const otherOverlay = diff < 0 ? '#overlayKeep' : '#overlayDelete';
card.querySelector(overlay).style.opacity = Math.min(Math.abs(diff) / 100, 1);
card.querySelector(otherOverlay).style.opacity = 0;
}
});
cardStack?.addEventListener('touchend', () => {
const diff = currentX - startX;
if (Math.abs(diff) > 100) {
swipe(diff < 0 ? 'left' : 'right');
} else {
renderStack();
}
startX = 0;
currentX = 0;
});
renderStack();
updateEmptyState();
});

View File

@@ -0,0 +1,92 @@
import { readDir, stat } from '@tauri-apps/plugin-fs';
import { FILE_TYPES } from '../main.js';
export async function getFileTypeFromName(name) {
const ext = name.split('.').pop()?.toLowerCase();
const typeMap = {
pdf: 'pdf', doc: 'doc', docx: 'doc', txt: 'doc',
jpg: 'image', jpeg: 'image', png: 'image', gif: 'image', webp: 'image', bmp: 'image',
mp4: 'video', mov: 'video', avi: 'video', mkv: 'video',
xlsx: 'excel', xls: 'excel', csv: 'excel',
pptx: 'ppt', ppt: 'ppt',
zip: 'archive', rar: 'archive', '7z': 'archive', tar: 'archive', gz: 'archive',
};
return typeMap[ext] || 'doc';
}
export async function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
}
export async function scanDirectory(path) {
try {
const entries = await readDir(path);
const files = [];
let id = 1;
for (const entry of entries) {
if (entry.isFile) {
const fullPath = `${path}/${entry.name}`;
try {
const fileStat = await stat(fullPath);
const type = await getFileTypeFromName(entry.name);
files.push({
id: id++,
name: entry.name,
path: fullPath,
type,
size: await formatFileSize(fileStat.size),
sizeBytes: fileStat.size,
date: new Date(fileStat.mtime * 1000).toISOString().split('T')[0],
});
} catch (e) {
files.push({
id: id++,
name: entry.name,
path: fullPath,
type: await getFileTypeFromName(entry.name),
size: 'Unknown',
sizeBytes: 0,
date: 'Unknown',
});
}
}
}
return files;
} catch (e) {
console.error('Failed to scan directory:', e);
return [];
}
}
export async function buildFileTree(path) {
try {
const entries = await readDir(path);
const folders = [];
const files = [];
for (const entry of entries) {
if (entry.isDirectory) {
folders.push({ name: entry.name, type: 'folder', path: `${path}/${entry.name}` });
} else {
const fullPath = `${path}/${entry.name}`;
const fileStat = await stat(fullPath);
const type = await getFileTypeFromName(entry.name);
files.push({ name: entry.name, type, meta: await formatFileSize(fileStat.size), path: fullPath });
}
}
return { folders, files };
} catch (e) {
console.error('Failed to build file tree:', e);
return { folders: [], files: [] };
}
}
export function getFileTypeInfo(type) {
return FILE_TYPES[type] || { label: 'File', color: '#6366f1', icon: 'document' };
}

54
src/scripts/shared/nav.js Normal file
View File

@@ -0,0 +1,54 @@
import { mountTitlebar, initTitlebar } from './titlebar.js';
document.addEventListener('DOMContentLoaded', async () => {
mountTitlebar();
await initTitlebar();
const currentPage = location.pathname.split('/').pop() || 'scene_dashboard.html';
const navItems = [
{ href: 'scene_dashboard.html', label: '홈', svg: '<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>' },
{ href: 'scene_swipe.html', label: '정리', svg: '<path d="M21 12H3M3 12L9 6M3 12L9 18M21 12L15 6M21 12L15 18"/>' },
{ href: 'scene_gamification.html', label: '업적', svg: '<path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22M18 2H6v7a6 6 0 0 0 12 0V2Z"/>' },
{ href: 'scene_ai_classification.html', label: 'AI', svg: '<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/>' },
{ href: 'scene_visualization.html', label: '시각화', svg: '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18M9 21V9"/>' },
{ href: 'scene_settings.html', label: '설정', svg: '<path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"/><path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/>' },
];
const navHTML = `
<nav class="hidden md:flex fixed left-0 top-10 bottom-0 w-16 bg-surface-card border-r border-divider z-50 flex-col items-center py-4 gap-2">
${navItems.map(item => `
<a href="${item.href}"
class="flex flex-col items-center justify-center gap-1 w-12 h-12 rounded-xl transition-all duration-200
${currentPage === item.href
? 'text-primary bg-primary/10'
: 'text-text-muted hover:text-text-primary hover:bg-surface-secondary'}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
${item.svg}
</svg>
<span class="text-[10px] font-medium">${item.label}</span>
</a>
`).join('')}
</nav>
<nav class="md:hidden fixed bottom-0 left-0 right-0 bg-surface-card border-t border-divider z-50">
<div class="flex justify-around items-center h-16 max-w-lg mx-auto px-2">
${navItems.map(item => `
<a href="${item.href}"
class="flex flex-col items-center justify-center gap-0.5 px-3 py-2 rounded-xl transition-all duration-200
${currentPage === item.href
? 'text-primary bg-primary/10'
: 'text-text-muted hover:text-text-primary'}">
<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
${item.svg}
</svg>
<span class="text-[10px] font-medium">${item.label}</span>
</a>
`).join('')}
</div>
</nav>
`;
document.body.insertAdjacentHTML('beforeend', navHTML);
document.body.classList.add('md:ml-16');
});

View File

@@ -1,40 +1,50 @@
/**
* AppState - Shared state management module
* Handles all localStorage interactions for the Chakmate app
*/
import { Store } from '@tauri-apps/plugin-store';
let store = null;
async function getStore() {
if (!store) {
store = await Store.load('chakmate-data.json');
}
return store;
}
const AppState = {
// ============ Theme ============
getTheme() {
return localStorage.getItem('chackly_theme') || 'light';
async getTheme() {
const s = await getStore();
return await s.get('theme') || 'light';
},
setTheme(theme) {
localStorage.setItem('chackly_theme', theme);
async setTheme(theme) {
const s = await getStore();
await s.set('theme', theme);
await s.save();
document.documentElement.setAttribute('data-theme', theme);
},
// ============ Settings ============
getSettings() {
async getSettings() {
const s = await getStore();
const defaults = {
notifications: true,
habits: false,
autoOrganize: false,
confirmDelete: true
};
try {
return { ...defaults, ...JSON.parse(localStorage.getItem('chackly_settings') || '{}') };
} catch {
return defaults;
const stored = await s.get('settings');
if (stored) {
return { ...defaults, ...stored };
}
return defaults;
},
setSettings(settings) {
localStorage.setItem('chackly_settings', JSON.stringify(settings));
async setSettings(settings) {
const s = await getStore();
await s.set('settings', settings);
await s.save();
},
// ============ Gamification ============
getGamificationData() {
async getGamificationData() {
const s = await getStore();
const defaultData = {
streak: 12,
weeklyProgress: [true, true, true, true, false, false, false],
@@ -51,56 +61,84 @@ const AppState = {
habitReminderEnabled: true,
lastUpdated: new Date().toISOString()
};
try {
const stored = localStorage.getItem('chackly_gamification');
if (stored) {
return { ...defaultData, ...JSON.parse(stored) };
}
} catch {
// ignore
const stored = await s.get('gamification');
if (stored) {
return { ...defaultData, ...stored };
}
return defaultData;
},
setGamificationData(data) {
async setGamificationData(data) {
const s = await getStore();
data.lastUpdated = new Date().toISOString();
localStorage.setItem('chackly_gamification', JSON.stringify(data));
await s.set('gamification', data);
await s.save();
},
// ============ Dashboard Stats ============
getStreak() {
return parseInt(localStorage.getItem('chackly_streak') || '7');
async getStreak() {
const s = await getStore();
return (await s.get('streak')) || 7;
},
setStreak(count) {
localStorage.setItem('chackly_streak', count.toString());
async setStreak(count) {
const s = await getStore();
await s.set('streak', count);
await s.save();
},
getFilesOrganized() {
return parseInt(localStorage.getItem('chackly_files_organized') || '248');
async getFilesOrganized() {
const s = await getStore();
return (await s.get('filesOrganized')) || 248;
},
setFilesOrganized(count) {
localStorage.setItem('chackly_files_organized', count.toString());
async setFilesOrganized(count) {
const s = await getStore();
await s.set('filesOrganized', count);
await s.save();
},
getFoldersManaged() {
return parseInt(localStorage.getItem('chackly_folders') || '12');
async getFoldersManaged() {
const s = await getStore();
return (await s.get('foldersManaged')) || 12;
},
setFoldersManaged(count) {
localStorage.setItem('chackly_folders', count.toString());
async setFoldersManaged(count) {
const s = await getStore();
await s.set('foldersManaged', count);
await s.save();
},
// ============ Initialization ============
init() {
// Apply saved theme on page load
const theme = this.getTheme();
async getScanPath() {
const s = await getStore();
return await s.get('scanPath') || null;
},
async setScanPath(path) {
const s = await getStore();
await s.set('scanPath', path);
await s.save();
},
async isOnboardingComplete() {
const s = await getStore();
return (await s.get('onboardingComplete')) || false;
},
async setOnboardingComplete(complete) {
const s = await getStore();
await s.set('onboardingComplete', complete);
await s.save();
},
async init() {
const theme = await this.getTheme();
document.documentElement.setAttribute('data-theme', theme);
}
};
// Auto-initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
AppState.init();
});
});
window.AppState = AppState;
export { AppState };

View File

@@ -0,0 +1,60 @@
export async function initTitlebar() {
const { getCurrentWindow } = await import('@tauri-apps/api/window');
const appWindow = getCurrentWindow();
const titlebar = document.getElementById('custom-titlebar');
const minimizeBtn = document.getElementById('titlebar-minimize');
const maximizeBtn = document.getElementById('titlebar-maximize');
const closeBtn = document.getElementById('titlebar-close');
const maximizeIcon = document.getElementById('maximize-icon');
const restoreIcon = document.getElementById('restore-icon');
async function updateIcon() {
const isMaximized = await appWindow.isMaximized();
maximizeIcon.style.display = isMaximized ? 'none' : 'block';
restoreIcon.style.display = isMaximized ? 'block' : 'none';
}
minimizeBtn.addEventListener('click', () => appWindow.minimize());
maximizeBtn.addEventListener('click', async () => {
await appWindow.toggleMaximize();
updateIcon();
});
closeBtn.addEventListener('click', () => appWindow.close());
titlebar.addEventListener('dblclick', async (e) => {
if (e.target.closest('.titlebar-controls')) return;
await appWindow.toggleMaximize();
updateIcon();
});
updateIcon();
appWindow.onResized(() => updateIcon());
}
export function mountTitlebar() {
const titlebarHTML = `
<div id="custom-titlebar" class="custom-titlebar" data-tauri-drag-region>
<div class="titlebar-left" data-tauri-drag-region>
<img src="../assets/logo.svg" alt="Chakmate" class="titlebar-logo" data-tauri-drag-region>
<span class="titlebar-title" data-tauri-drag-region>Chakmate</span>
</div>
<div class="titlebar-center" data-tauri-drag-region></div>
<div class="titlebar-controls">
<button id="titlebar-minimize" class="titlebar-btn" aria-label="최소화">
<svg width="12" height="12" viewBox="0 0 12 12"><rect y="5" width="12" height="2" fill="currentColor"/></svg>
</button>
<button id="titlebar-maximize" class="titlebar-btn" aria-label="최대화">
<svg id="maximize-icon" width="12" height="12" viewBox="0 0 12 12"><rect x="1" y="1" width="10" height="10" stroke="currentColor" stroke-width="2" fill="none"/></svg>
<svg id="restore-icon" width="12" height="12" viewBox="0 0 12 12" style="display:none"><path d="M3 1h8v8h-2v2H1V3h2V1zm1 3v5h5V4H4z" fill="currentColor"/></svg>
</button>
<button id="titlebar-close" class="titlebar-btn titlebar-btn-close" aria-label="닫기">
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="2"/></svg>
</button>
</div>
</div>
`;
document.body.insertAdjacentHTML('afterbegin', titlebarHTML);
}

229
src/scripts/shared/util.js Normal file
View File

@@ -0,0 +1,229 @@
export const FILE_CATEGORIES = {
IMAGE: 'image',
DOCUMENT: 'document',
VIDEO: 'video',
AUDIO: 'audio',
ARCHIVE: 'archive',
SPREADSHEET: 'spreadsheet',
PRESENTATION: 'presentation',
CODE: 'code',
OTHER: 'other'
};
export const CATEGORY_EXTENSIONS = {
[FILE_CATEGORIES.IMAGE]: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico', 'tiff', 'raw', 'cr2', 'nef', 'arw'],
[FILE_CATEGORIES.DOCUMENT]: ['pdf', 'doc', 'docx', 'txt', 'rtf', 'odt', 'pages'],
[FILE_CATEGORIES.VIDEO]: ['mp4', 'avi', 'mov', 'wmv', 'mkv', 'flv', 'webm', 'm4v', 'mpeg'],
[FILE_CATEGORIES.AUDIO]: ['mp3', 'wav', 'flac', 'aac', 'ogg', 'wma', 'm4a', 'aiff'],
[FILE_CATEGORIES.ARCHIVE]: ['zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso'],
[FILE_CATEGORIES.SPREADSHEET]: ['xls', 'xlsx', 'csv', 'ods', 'numbers'],
[FILE_CATEGORIES.PRESENTATION]: ['ppt', 'pptx', 'odp', 'key'],
[FILE_CATEGORIES.CODE]: ['js', 'ts', 'py', 'java', 'c', 'cpp', 'h', 'css', 'html', 'json', 'xml', 'yaml', 'yml', 'md']
};
export const NAME_PATTERNS = {
SCREENSHOT: /^screenshot/i,
IMG: /^img[_-]?/i,
PHOTO: /^photo/i,
DSC: /^dsc/i,
PIC: /^pic/i,
CAMERA: /^cam[_-]?\d+/i,
WA: /^wa\d+/i,
RECEIVED: /^received/i,
VID_: /^vid_/i,
VIDEO: /^video/i,
AUDIO: /^audio/i,
MUSIC: /^music/i,
RECORDING: /^recording/i,
DOCUMENT: /^doc/i,
NOTE: /^note/i,
BACKUP: /^backup/i,
ARCHIVE: /^archive/i,
COPY: /^copy/i,
OLD: /^old/i,
TEMP: /^temp/i,
DRAFT: /^draft/i,
FINAL: /^final/i,
V[0-9]+: /^v\d+/i,
DRAFT: /^draft/i
};
export function getExtension(filename) {
const parts = filename.split('.');
return parts.length > 1 ? parts.pop().toLowerCase() : '';
}
export function getCategoryByExtension(ext) {
for (const [category, extensions] of Object.entries(CATEGORY_EXTENSIONS)) {
if (extensions.includes(ext)) {
return category;
}
}
return FILE_CATEGORIES.OTHER;
}
export function analyzeFileName(filename) {
const result = {
filename,
category: null,
pattern: null,
year: null,
month: null,
suggestedFolder: null
};
const ext = getExtension(filename);
result.category = getCategoryByExtension(ext);
for (const [patternName, regex] of Object.entries(NAME_PATTERNS)) {
if (regex.test(filename)) {
result.pattern = patternName;
break;
}
}
const dateMatch = filename.match(/(20\d{2})[_-]?(\d{2})?[_-]?(\d{2})?|(\d{4})(\d{2})(\d{2})/);
if (dateMatch) {
if (dateMatch[1]) {
result.year = parseInt(dateMatch[1]);
if (dateMatch[2]) result.month = parseInt(dateMatch[2]);
} else if (dateMatch[4]) {
result.year = parseInt(dateMatch[4]);
if (dateMatch[5]) result.month = parseInt(dateMatch[5]);
}
}
if (result.pattern && result.year) {
result.suggestedFolder = suggestFolderByPattern(result.pattern, result.category, result.year, result.month);
} else if (result.year) {
result.suggestedFolder = result.year.toString();
}
return result;
}
export function suggestFolderByPattern(pattern, category, year, month) {
if (year && month) {
return `${year}/${String(month).padStart(2, '0')}`;
}
if (year) {
return year.toString();
}
const patternFolders = {
SCREENSHOT: 'Screenshots',
IMG: 'Photos',
PHOTO: 'Photos',
DSC: 'Photos',
PIC: 'Photos',
CAMERA: 'Photos',
WA: 'WhatsApp',
RECEIVED: 'Downloads',
VID_: 'Videos',
VIDEO: 'Videos',
AUDIO: 'Audio',
MUSIC: 'Music',
RECORDING: 'Recordings',
DOCUMENT: 'Documents',
NOTE: 'Notes',
BACKUP: 'Backups',
ARCHIVE: 'Archives',
COPY: 'Copies',
OLD: 'Old',
TEMP: 'Temporary',
DRAFT: 'Drafts',
FINAL: 'Final'
};
return patternFolders[pattern] || getCategoryDefaultFolder(category);
}
export function getCategoryDefaultFolder(category) {
const folders = {
[FILE_CATEGORIES.IMAGE]: 'Images',
[FILE_CATEGORIES.DOCUMENT]: 'Documents',
[FILE_CATEGORIES.VIDEO]: 'Videos',
[FILE_CATEGORIES.AUDIO]: 'Audio',
[FILE_CATEGORIES.ARCHIVE]: 'Archives',
[FILE_CATEGORIES.SPREADSHEET]: 'Spreadsheets',
[FILE_CATEGORIES.PRESENTATION]: 'Presentations',
[FILE_CATEGORIES.CODE]: 'Code',
[FILE_CATEGORIES.OTHER]: 'Other'
};
return folders[category] || 'Other';
}
export function classifyFiles(files) {
return files.map(file => {
if (typeof file === 'string') {
return analyzeFileName(file);
}
return {
...file,
...analyzeFileName(file.name || file.path || '')
};
});
}
export function groupFilesBySuggestedFolder(classifiedFiles) {
const groups = {};
for (const file of classifiedFiles) {
const folder = file.suggestedFolder || getCategoryDefaultFolder(file.category);
if (!groups[folder]) {
groups[folder] = [];
}
groups[folder].push(file);
}
return groups;
}
export function suggestFolderStructure(files) {
const classified = classifyFiles(files);
const grouped = groupFilesBySuggestedFolder(classified);
const structure = [];
for (const [folderName, folderFiles] of Object.entries(grouped)) {
structure.push({
name: folderName,
type: 'folder',
children: folderFiles.map(f => ({
name: f.filename || f.name,
type: f.category,
meta: f.size || null
}))
});
}
return structure;
}
export function calculateSimilarity(file1, file2) {
let score = 0;
if (file1.category === file2.category) score += 3;
if (file1.pattern === file2.pattern) score += 2;
if (file1.year === file2.year) score += 2;
if (file1.month === file2.month) score += 1;
if (file1.suggestedFolder === file2.suggestedFolder) score += 2;
const ext1 = getExtension(file1.filename || file1.name || '');
const ext2 = getExtension(file2.filename || file2.name || '');
if (ext1 === ext2) score += 1;
return score;
}
export function findRelatedFiles(targetFile, allFiles, threshold = 3) {
const classified = classifyFiles([targetFile])[0];
return allFiles
.filter(f => f !== targetFile)
.map(f => ({
file: f,
similarity: calculateSimilarity(classified, analyzeFileName(f.name || f.path || f.filename || ''))
}))
.filter(r => r.similarity >= threshold)
.sort((a, b) => b.similarity - a.similarity);
}

View File

@@ -1,232 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const mockFiles = [
{ id: 1, name: 'project_proposal.pdf', type: 'pdf', size: '2.4 MB', date: '2024-01-15', icon: '📄' },
{ id: 2, name: 'meeting_notes.docx', type: 'doc', size: '156 KB', date: '2024-01-14', icon: '📝' },
{ id: 3, name: 'vacation_photo.jpg', type: 'image', size: '3.2 MB', date: '2024-01-13', icon: '🖼️' },
{ id: 4, name: 'budget_2024.xlsx', type: 'excel', size: '89 KB', date: '2024-01-12', icon: '📊' },
{ id: 5, name: 'presentation_final.pptx', type: 'ppt', size: '5.2 MB', date: '2024-01-11', icon: '📽️' },
{ id: 6, name: 'backup_2023.zip', type: 'archive', size: '156 MB', date: '2024-01-10', icon: '📦' },
{ id: 7, name: 'contract_signed.pdf', type: 'pdf', size: '1.1 MB', date: '2024-01-09', icon: '📋' },
];
let files = [...mockFiles];
let historyStack = [];
let currentIndex = 0;
const cardStack = document.getElementById('cardStack');
const emptyState = document.getElementById('emptyState');
const actionButtons = document.getElementById('actionButtons');
const undoBtn = document.getElementById('undoBtn');
const toast = document.getElementById('toast');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const currentFileName = document.getElementById('currentFileName');
function getFileTypeInfo(type) {
const types = {
pdf: { label: 'PDF', color: '#ef4444' },
doc: { label: 'Doc', color: '#3b82f6' },
image: { label: 'Image', color: '#8b5cf6' },
excel: { label: 'Excel', color: '#10b981' },
ppt: { label: 'PPT', color: '#f97316' },
archive: { label: 'Archive', color: '#6b7280' },
};
return types[type] || { label: 'File', color: '#6366f1' };
}
function createCard(file) {
const typeInfo = getFileTypeInfo(file.type);
return `
<div class="file-card absolute inset-0 bg-surface-card rounded-3xl shadow-lg p-6 flex flex-col cursor-grab active:cursor-grabbing select-none" style="animation: cardEnter 0.4s ease forwards;">
<div class="flex items-start justify-between mb-6">
<div class="w-16 h-16 bg-gradient-to-br from-primary/10 to-secondary/10 rounded-2xl flex items-center justify-center text-3xl">
${file.icon}
</div>
<span class="px-3 py-1 rounded-full text-xs font-semibold" style="background-color: ${typeInfo.color}20; color: ${typeInfo.color}">${typeInfo.label}</span>
</div>
<div class="flex-1 flex flex-col justify-center">
<h3 class="font-display text-xl font-semibold text-text-primary mb-2 text-center">${file.name}</h3>
<div class="flex items-center justify-center gap-4 text-text-muted text-sm">
<span>${file.size}</span>
<span class="w-1 h-1 bg-text-muted rounded-full"></span>
<span>${file.date}</span>
</div>
</div>
<div id="overlayDelete" class="absolute inset-0 rounded-3xl bg-gradient-to-br from-red-400/0 to-red-600/0 flex items-center justify-center opacity-0 transition-opacity duration-200 pointer-events-none">
<div class="bg-white/90 backdrop-blur-sm rounded-2xl p-4">
<svg class="w-16 h-16 text-red-500"><use href="#icon-trash"></use></svg>
</div>
</div>
<div id="overlayKeep" class="absolute inset-0 rounded-3xl bg-gradient-to-br from-emerald-400/0 to-emerald-600/0 flex items-center justify-center opacity-0 transition-opacity duration-200 pointer-events-none">
<div class="bg-white/90 backdrop-blur-sm rounded-2xl p-4">
<svg class="w-16 h-16 text-emerald-500"><use href="#icon-check"></use></svg>
</div>
</div>
</div>
`;
}
function renderCards() {
if (files.length === 0) {
cardStack.innerHTML = '';
emptyState.classList.remove('hidden');
emptyState.classList.add('flex');
actionButtons.style.display = 'none';
currentFileName.textContent = '모든 파일을 검토했습니다';
return;
}
emptyState.classList.add('hidden');
emptyState.classList.remove('flex');
actionButtons.style.display = 'flex';
const topFile = files[0];
currentFileName.textContent = topFile.name;
cardStack.innerHTML = createCard(topFile);
const card = cardStack.querySelector('.file-card');
initSwipe(card);
}
let startX = 0;
let currentX = 0;
let isDragging = false;
function initSwipe(card) {
if (!card) return;
card.addEventListener('mousedown', (e) => startDrag(e));
card.addEventListener('touchstart', (e) => startDrag(e), { passive: true });
document.addEventListener('mousemove', (e) => moveDrag(e));
document.addEventListener('touchmove', (e) => moveDrag(e), { passive: true });
document.addEventListener('mouseup', () => endDrag(card));
document.addEventListener('touchend', () => endDrag(card));
}
function startDrag(e) {
isDragging = true;
startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
currentX = 0;
}
function moveDrag(e) {
if (!isDragging) return;
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
currentX = clientX - startX;
const card = cardStack.querySelector('.file-card');
if (!card) return;
const rotation = currentX * 0.05;
card.style.transform = `translateX(${currentX}px) rotate(${rotation}deg)`;
const overlayDelete = card.querySelector('#overlayDelete');
const overlayKeep = card.querySelector('#overlayKeep');
if (currentX < -50) {
overlayDelete.style.opacity = Math.min(Math.abs(currentX) / 100, 1);
overlayDelete.classList.add('bg-gradient-to-br', 'from-red-400/95', 'to-red-600/95');
overlayKeep.style.opacity = 0;
} else if (currentX > 50) {
overlayKeep.style.opacity = Math.min(currentX / 100, 1);
overlayKeep.classList.add('bg-gradient-to-br', 'from-emerald-400/95', 'to-emerald-600/95');
overlayDelete.style.opacity = 0;
} else {
overlayDelete.style.opacity = 0;
overlayKeep.style.opacity = 0;
}
}
function endDrag(card) {
if (!isDragging) return;
isDragging = false;
if (currentX < -150) {
animateCard(card, 'left');
} else if (currentX > 150) {
animateCard(card, 'right');
} else {
card.style.transform = 'translateX(0) rotate(0deg)';
const overlayDelete = card.querySelector('#overlayDelete');
const overlayKeep = card.querySelector('#overlayKeep');
overlayDelete.style.opacity = 0;
overlayKeep.style.opacity = 0;
}
currentX = 0;
}
function animateCard(card, direction) {
if (direction === 'left') {
card.classList.add('animate-card-exit-left');
card.style.opacity = '0';
} else {
card.classList.add('animate-card-exit-right');
card.style.opacity = '0';
}
setTimeout(() => {
handleSwipe(direction === 'left' ? 'delete' : 'keep');
}, 300);
}
function handleSwipe(action) {
if (files.length === 0) return;
const deletedFile = files.shift();
historyStack.push(deletedFile);
undoBtn.disabled = false;
updateProgress();
renderCards();
showToast(action === 'delete' ? '파일 삭제됨' : '파일 보관됨');
}
function undo() {
if (historyStack.length === 0) return;
const restoredFile = historyStack.pop();
files.unshift(restoredFile);
undoBtn.disabled = historyStack.length === 0;
updateProgress();
renderCards();
showToast('실행 취소됨');
}
function updateProgress() {
const total = mockFiles.length;
const current = files.length;
const progress = Math.round(((total - current) / total) * 100);
progressFill.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
}
function showToast(message) {
toast.textContent = message;
toast.classList.add('opacity-100', 'translate-y-0');
toast.classList.remove('opacity-0', '-translate-y-5');
setTimeout(() => {
toast.classList.remove('opacity-100', 'translate-y-0');
toast.classList.add('opacity-0', '-translate-y-5');
}, 2000);
}
function resetFiles() {
files = [...mockFiles];
historyStack = [];
undoBtn.disabled = true;
updateProgress();
renderCards();
showToast('초기화됨');
}
undoBtn.addEventListener('click', undo);
renderCards();
updateProgress();
});

View File

@@ -1,3 +1,4 @@
@import '@fontsource/noto-color-emoji/400.css';
@import '@fontsource/noto-sans-kr/300.css';
@import '@fontsource/noto-sans-kr/400.css';
@import '@fontsource/noto-sans-kr/500.css';
@@ -11,7 +12,13 @@
@tailwind components;
@tailwind utilities;
/* Dark mode CSS variables (extracted from index.html) */
/* Force emoji font for all emoji characters */
[class*="text-"] {
font-variant-east-asian: emoji;
}
.emoji, [class*="emoji"], .file-card, .suggestion-item, .achievement-item, .week-day, .tag-pill, .hint-chip {
font-family: 'Noto Color Emoji', 'Noto Sans KR', system-ui, sans-serif !important;
}
[data-theme="dark"] {
--bg-primary: #0a0e1a;
--bg-secondary: #111827;
@@ -54,44 +61,111 @@
display: block;
}
/* Screen visibility */
.screen {
display: none;
}
.screen.active {
display: block;
}
/* Utility classes */
.hidden { display: none !important; }
/* Button styles */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1rem 2rem;
border-radius: 9999px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
border: none;
transition: all 0.25s ease;
}
.btn-primary {
background: linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%);
color: white;
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(59, 130, 246, 0.4);
}
.btn-secondary {
background: #e2e8f0;
color: #0f172a;
}
.btn-secondary:hover {
background: #cbd5e1;
}
/* Page Header - Unified pattern for all pages */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-5);
background: var(--bg-card);
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
box-shadow: var(--shadow-sm);
}
.page-header .header-left,
.page-header .header-right {
min-width: 80px;
}
.page-header .header-title {
font-family: 'Outfit', sans-serif;
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
}
/* Card - Unified */
.card {
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: var(--space-5);
box-shadow: var(--shadow-sm);
}
.card-lg {
background: var(--bg-card);
border-radius: var(--radius-xl);
padding: var(--space-6);
box-shadow: var(--shadow-md);
}
/* Button - Unified */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-6);
border-radius: var(--radius-full);
font-size: 1rem;
font-weight: 600;
cursor: pointer;
border: none;
transition: all var(--transition-base);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: white;
box-shadow: var(--shadow-blue);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(59, 130, 246, 0.4);
}
.btn-secondary {
background: var(--bg-secondary);
color: var(--text-primary);
border-radius: var(--radius-md);
}
.btn-secondary:hover {
background: #cbd5e1;
}
/* Section Container */
.section {
padding: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
/* Stats Card */
.stat-card {
flex: 1;
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: var(--space-5);
box-shadow: var(--shadow-sm);
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-3);
}
.stat-value {
font-family: 'Outfit', sans-serif;
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
}
.stat-label {
color: var(--text-muted);
font-size: 0.875rem;
}
/* Swipe card animations (from scene_swipe.html) */
@keyframes shimmer {
@@ -266,8 +340,80 @@
box-sizing: border-box;
}
.custom-titlebar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--bg-card);
border-bottom: 1px solid var(--bg-secondary);
z-index: 9999;
-webkit-app-region: drag;
user-select: none;
}
.titlebar-left {
display: flex;
align-items: center;
gap: 10px;
padding-left: 12px;
}
.titlebar-logo {
width: 20px;
height: 20px;
object-fit: contain;
}
.titlebar-title {
font-family: 'Outfit', sans-serif;
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.titlebar-center {
flex: 1;
}
.titlebar-controls {
display: flex;
height: 100%;
-webkit-app-region: no-drag;
}
.titlebar-btn {
width: 46px;
height: 100%;
border: none;
background: transparent;
color: var(--text-secondary);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background var(--transition-fast);
}
.titlebar-btn:hover {
background: var(--bg-secondary);
}
.titlebar-btn-close:hover {
background: #e81123;
color: white;
}
body {
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
padding-top: 40px;
}
body {
font-family: 'Noto Sans KR', 'Noto Color Emoji', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
@@ -1634,3 +1780,421 @@
.checkbox-input:checked + .checkbox-visual {
animation: checkBounce 0.3s ease;
}
/* ============================================
SWIPE PAGE SPECIFIC STYLES
============================================ */
@keyframes shimmer {
0% { transform: translateX(-100%) translateY(-100%); }
100% { transform: translateX(100%) translateY(100%); }
}
@keyframes cardExitLeft {
to { transform: translateX(-150%) rotate(-15deg); opacity: 0; }
}
@keyframes cardExitRight {
to { transform: translateX(150%) rotate(15deg); opacity: 0; }
}
@keyframes cardEnter {
from { transform: scale(0.9) translateY(30px); opacity: 0; }
to { transform: scale(1) translateY(0); opacity: 1; }
}
.animate-card-enter {
animation: cardEnter 0.3s ease forwards;
}
.animate-card-exit-left {
animation: cardExitLeft 0.4s ease forwards;
}
.animate-card-exit-right {
animation: cardExitRight 0.4s ease forwards;
}
.card-container {
overflow: visible;
}
.file-card {
touch-action: pan-y;
transition: transform 100ms ease;
}
.file-card.dragging {
cursor: grabbing;
transition: none;
}
#overlayDelete {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.9), rgba(220, 38, 38, 0.95));
}
#overlayKeep {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.9), rgba(5, 150, 105, 0.95));
}
/* ============================================
GAMIFICATION PAGE SPECIFIC STYLES
============================================ */
@keyframes unlockPulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.4); }
50% { transform: scale(1.1); box-shadow: 0 0 0 15px rgba(251, 191, 36, 0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(251, 191, 36, 0); }
}
@keyframes sparkle {
0%, 100% { opacity: 0; transform: scale(0) rotate(0deg); }
50% { opacity: 1; transform: scale(1) rotate(180deg); }
}
@keyframes confettiFall {
0% { transform: translateY(-100%) rotate(0deg); opacity: 1; }
100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
}
@keyframes fireFlicker {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.confetti {
position: absolute;
width: 10px;
height: 10px;
opacity: 0;
}
.achievement.just-unlocked {
animation: unlockPulse 0.6s ease-out;
}
.sparkle {
position: absolute;
font-size: 1rem;
animation: sparkle 0.6s ease-out forwards;
}
.toast.show {
opacity: 1 !important;
transform: translateX(-50%) translateY(0) !important;
}
.toggle-switch {
position: relative;
width: 56px;
height: 32px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
inset: 0;
background: #e2e8f0;
border-radius: 9999px;
transition: background 250ms ease;
}
.toggle-slider::before {
content: '';
position: absolute;
height: 24px;
width: 24px;
left: 4px;
bottom: 4px;
background: white;
border-radius: 50%;
transition: transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 2px 8px rgba(14, 165, 233, 0.06);
}
.toggle-switch input:checked + .toggle-slider {
background: linear-gradient(90deg, #3b82f6 0%, #0ea5e9 100%);
}
.toggle-switch input:checked + .toggle-slider::before {
transform: translateX(24px);
}
.achievement-tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%) translateY(0);
background: #0f172a;
color: #ffffff;
padding: 8px 12px;
border-radius: 8px;
font-size: 0.7rem;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 150ms ease;
z-index: 10;
}
.achievement-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #0f172a;
}
.achievement.locked:hover .achievement-tooltip {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(-10px);
}
.achievement.unlocked .achievement-tooltip {
display: none;
}
.week-day-indicator {
width: 36px;
height: 36px;
border-radius: 12px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 600;
transition: all 200ms ease;
}
.week-day-indicator.completed {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
}
.week-day-indicator.today {
background: linear-gradient(135deg, #3b82f6 0%, #0ea5e9 100%);
color: white;
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
}
.week-day-label {
font-size: 0.65rem;
color: #94a3b8;
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.week-day {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.week-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 8px;
padding: 16px;
background: #f8fafc;
border-radius: 16px;
}
.achievement-icon {
font-size: 2rem;
margin-bottom: 4px;
display: block;
}
.achievement-name {
font-size: 0.7rem;
color: #475569;
text-align: center;
}
.achievement {
background: white;
border-radius: 16px;
padding: 16px 12px;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: all 300ms ease;
position: relative;
}
.achievement.locked {
background: #f1f5f9;
}
.achievement.locked .achievement-icon {
filter: grayscale(100%);
opacity: 0.5;
}
.achievement.gold {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border: 1px solid #fbbf24;
}
.achievement.silver {
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
border: 1px solid #cbd5e1;
}
.achievement.bronze {
background: linear-gradient(135deg, #fef3c7 0%, #fcd34d 100%);
border: 1px solid #d97706;
}
.achievement.unlocked {
cursor: default;
}
.streak-fire {
font-size: 2.5rem;
display: inline-block;
}
.streak-fire.animate {
animation: fireFlicker 0.3s ease-in-out infinite;
}
/* ============================================
AI CLASSIFICATION PAGE SPECIFIC STYLES
============================================ */
.neural-node:nth-child(1) { animation-delay: 0s; }
.neural-node:nth-child(2) { animation-delay: 0.3s; }
.neural-node:nth-child(3) { animation-delay: 0.6s; }
.neural-node:nth-child(4) { animation-delay: 0.9s; }
.neural-node:nth-child(5) { animation-delay: 1.2s; }
.discovery-card:nth-child(1) { transition-delay: 0ms; }
.discovery-card:nth-child(2) { transition-delay: 100ms; }
.discovery-card:nth-child(3) { transition-delay: 200ms; }
.discovery-card:nth-child(4) { transition-delay: 300ms; }
.tag-pill-feedback { transform: scale(0.95); }
.hint-chip-feedback { background: var(--primary-dark); }
/* ============================================
UNIFIED RESPONSIVE PAGE LAYOUT
============================================ */
/* Page Container - Centers content with max-width */
.page-container {
width: 100%;
max-width: 600px;
margin: 0 auto;
padding-left: var(--space-4);
padding-right: var(--space-4);
}
/* Tablet (md:) - max-w-2xl */
@media (min-width: 768px) {
.page-container {
max-width: 720px;
}
}
/* Desktop (lg:) - max-w-3xl with side nav offset */
@media (min-width: 1024px) {
.page-container {
max-width: 900px;
padding-left: 80px;
}
}
/* Page Header - Unified pattern */
.page-header {
max-width: 600px;
margin: 0 auto;
padding: var(--space-4);
background: var(--bg-card);
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
box-shadow: var(--shadow-sm);
}
@media (min-width: 768px) {
.page-header {
max-width: 720px;
border-radius: var(--radius-xl);
margin-top: var(--space-4);
}
}
@media (min-width: 1024px) {
.page-header {
max-width: 820px;
padding: var(--space-5) var(--space-6);
}
}
/* Page Content */
.page-content {
max-width: 600px;
margin: 0 auto;
padding: var(--space-4);
padding-bottom: 100px;
}
@media (min-width: 768px) {
.page-content {
max-width: 720px;
padding: var(--space-6);
padding-bottom: 120px;
}
}
@media (min-width: 1024px) {
.page-content {
max-width: 900px;
padding-left: 80px;
padding-bottom: 40px;
}
}
/* Cards - Unified rounded-xl */
.card {
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: var(--space-5);
box-shadow: var(--shadow-md);
}
@media (min-width: 768px) {
.card {
border-radius: var(--radius-xl);
padding: var(--space-6);
}
}
/* Buttons - Unified rounded-xl */
.btn {
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-6);
font-weight: 500;
}
@media (min-width: 768px) {
.btn {
border-radius: var(--radius-xl);
padding: var(--space-4) var(--space-8);
}
}
/* Inputs - Unified */
.input {
background: var(--bg-secondary);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
border: none;
outline: none;
color: var(--text-primary);
transition: all var(--transition-fast);
}
.input:focus {
box-shadow: 0 0 0 2px var(--primary);
}
/* Section spacing */
.section {
padding: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
@media (min-width: 768px) {
.section {
padding: var(--space-6);
gap: var(--space-6);
}
}
/* Side Nav - Desktop only */
.side-nav {
display: none;
}
@media (min-width: 1024px) {
.side-nav {
display: flex;
}
}
/* Mobile bottom nav spacing */
@media (max-width: 767px) {
body {
padding-bottom: 80px;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -8,18 +8,18 @@ export default defineConfig({
emptyOutDir: true,
rollupOptions: {
input: {
onboarding: resolve(__dirname, 'src/pages/scene_onboarding.html'),
dashboard: resolve(__dirname, 'src/pages/scene_dashboard.html'),
swipe: resolve(__dirname, 'src/pages/scene_swipe.html'),
settings: resolve(__dirname, 'src/pages/scene_settings.html'),
index: 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'),
gamification: resolve(__dirname, 'src/pages/scene_gamification.html'),
'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'),
visualization: resolve(__dirname, 'src/pages/scene_visualization.html'),
settings: resolve(__dirname, 'src/pages/scene_settings.html'),
},
output: {
assetFileNames: (assetInfo) => {
if (assetInfo.name === 'logo.svg') return 'assets/[name][extname]';
return 'assets/[name]-[hash][extname]';
return assetInfo.name === 'logo.svg' ? 'assets/[name][extname]' : 'assets/[name]-[hash][extname]'
}
}
}