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:
1
.antigravitycli/3d5a0d25-0ec6-40c0-8030-0ea115f57ff0.json
Symbolic link
1
.antigravitycli/3d5a0d25-0ec6-40c0-8030-0ea115f57ff0.json
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/home/yenru0/.gemini/config/projects/3d5a0d25-0ec6-40c0-8030-0ea115f57ff0.json
|
||||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -4,11 +4,6 @@ node_modules/
|
|||||||
# Build output
|
# Build output
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
# Tauri
|
|
||||||
src-tauri/target/
|
|
||||||
src-tauri/gen/schemas
|
|
||||||
src-tauri/Cargo.lock
|
|
||||||
|
|
||||||
# Environment files
|
# Environment files
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
@@ -34,3 +29,20 @@ Thumbs.db
|
|||||||
|
|
||||||
# Sisyphus session files
|
# Sisyphus session files
|
||||||
.sisyphus/
|
.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
65
AGENTS.md
Normal 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.
|
||||||
71
REVIEW.md
71
REVIEW.md
@@ -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.
|
|
||||||
933
index.html
933
index.html
@@ -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
48
package-lock.json
generated
@@ -8,8 +8,13 @@
|
|||||||
"name": "chakmate",
|
"name": "chakmate",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/noto-color-emoji": "^5.2.12",
|
||||||
"@fontsource/noto-sans-kr": "^5.2.9",
|
"@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": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.0.0",
|
"@tauri-apps/cli": "^2.0.0",
|
||||||
@@ -399,6 +404,14 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/@fontsource/noto-sans-kr": {
|
||||||
"version": "5.2.9",
|
"version": "5.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-5.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-5.2.9.tgz",
|
||||||
@@ -810,6 +823,15 @@
|
|||||||
"win32"
|
"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": {
|
"node_modules/@tauri-apps/cli": {
|
||||||
"version": "2.11.2",
|
"version": "2.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz",
|
||||||
@@ -1015,6 +1037,30 @@
|
|||||||
"node": ">= 10"
|
"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": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
|
|||||||
@@ -10,8 +10,13 @@
|
|||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/noto-color-emoji": "^5.2.12",
|
||||||
"@fontsource/noto-sans-kr": "^5.2.9",
|
"@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": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.0.0",
|
"@tauri-apps/cli": "^2.0.0",
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
414
scene_swipe.html
414
scene_swipe.html
@@ -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
@@ -23,3 +23,6 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
log = "0.4"
|
log = "0.4"
|
||||||
tauri = { version = "2.11.2", features = [] }
|
tauri = { version = "2.11.2", features = [] }
|
||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
|
tauri-plugin-store = "2"
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
|
tauri-plugin-fs = "2"
|
||||||
|
|||||||
@@ -6,6 +6,17 @@
|
|||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
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!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
"minHeight": 600,
|
"minHeight": 600,
|
||||||
"center": true,
|
"center": true,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"fullscreen": false
|
"fullscreen": false,
|
||||||
|
"decorations": false,
|
||||||
|
"transparent": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": false,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
|
|||||||
22
src/components/titlebar.html
Normal file
22
src/components/titlebar.html
Normal 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
16
src/index.html
Normal 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>
|
||||||
@@ -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>
|
|
||||||
@@ -5,129 +5,37 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>AI 분류 - Chakmate</title>
|
<title>AI 분류 - Chakmate</title>
|
||||||
<link rel="stylesheet" href="../styles/main.css">
|
<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>
|
</head>
|
||||||
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
|
<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">
|
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
|
||||||
<!-- Back Header -->
|
<header class="page-header">
|
||||||
<div class="flex items-center gap-4 mb-8">
|
<div class="header-left flex items-center gap-3">
|
||||||
<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">
|
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
|
||||||
<svg class="w-5 h-5"><use href="#icon-arrow-left"></use></svg>
|
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
|
||||||
<span>대시보드로</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<!-- Privacy Hero Banner -->
|
<div class="page-content">
|
||||||
<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="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="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="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">
|
<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>
|
<svg class="w-12 h-12 text-white"><use href="../assets/icons/icons.svg#icon-shield-check"></use></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div class="relative z-10 ml-4">
|
|
||||||
<h1 class="text-white text-2xl font-display font-bold mb-2">AI 파일 분류</h1>
|
<h1 class="text-white text-2xl font-display font-bold mb-2">AI 파일 분류</h1>
|
||||||
<p class="text-white/80 text-sm font-light">머신러닝으로 파일을 자동으로 분석하고 분류합니다</p>
|
<p class="text-white/80 text-sm font-light">머신러닝으로 파일을 자동으로 분석하고 분류합니다</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bg-surface-card rounded-xl p-6 mb-8 shadow-md flex items-center gap-6">
|
<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-16 h-16 relative">
|
||||||
<div class="w-full h-full rounded-full border-4 border-surface-secondary 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>
|
<h2 class="text-[1.1rem] font-display font-semibold mb-1">AI 분석 진행 중</h2>
|
||||||
<p class="text-text-muted text-[0.85rem]">파일을 스캔하고 패턴을 인식하는 중...</p>
|
<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="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>
|
||||||
</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">
|
<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">
|
||||||
<svg class="w-4 h-4"><use href="#icon-arrow-up"></use></svg>
|
취소
|
||||||
분석 결과 내보내기
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
|
||||||
</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="bg-surface-card rounded-xl p-6 shadow-md">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<h2 class="text-lg font-display font-semibold mb-4">분류 결과</h2>
|
||||||
<h3 class="text-[0.9rem] font-medium text-text-secondary">전체 신뢰도</h3>
|
<div class="space-y-3" id="classificationResults">
|
||||||
<svg class="w-5 h-5 text-primary"><use href="#icon-sparkles"></use></svg>
|
<div class="flex items-center justify-center py-8 text-text-muted">
|
||||||
</div>
|
<svg class="w-8 h-8 animate-spin mr-3"><use href="../assets/icons/icons.svg#icon-loader"></use></svg>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Confidence Breakdown -->
|
<script type="module" src="../scripts/pages/ai-classification.js"></script>
|
||||||
<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>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -5,76 +5,43 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>대시보드 - Chakmate</title>
|
<title>대시보드 - Chakmate</title>
|
||||||
<link rel="stylesheet" href="../styles/main.css">
|
<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>
|
</head>
|
||||||
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden pb-20">
|
<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)]">
|
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
|
||||||
<div class="flex items-center gap-3">
|
<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">
|
<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>
|
<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>
|
||||||
<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">
|
<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="font-bold text-accent-warn" id="streak-count">7</span>
|
||||||
<span class="text-text-muted text-sm">일</span>
|
<span class="text-text-muted text-sm">일</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="dashboard">
|
<div id="dashboard" class="page-content">
|
||||||
<div class="stats-container p-4 flex flex-col gap-4">
|
<div class="stats-container flex flex-col gap-4">
|
||||||
<div class="stats-row flex 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">
|
<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>
|
</div>
|
||||||
<p class="stat-value text-2xl font-display font-bold text-text-primary" id="files-count">248</p>
|
<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>
|
<p class="stat-label text-text-muted text-sm">정리된 파일</p>
|
||||||
</div>
|
</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">
|
<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>
|
</div>
|
||||||
<p class="stat-value text-2xl font-display font-bold text-text-primary" id="folders-count">12</p>
|
<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>
|
<p class="stat-label text-text-muted text-sm">관리된 폴더</p>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="suggestions-header flex items-center justify-between mb-4">
|
||||||
<h2 class="text-lg font-display font-bold">AI 제안</h2>
|
<h2 class="text-lg font-display font-bold">AI 제안</h2>
|
||||||
<button class="text-sm text-primary font-medium hover:underline" id="apply-suggestions">적용하기</button>
|
<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">
|
<div class="section-header flex items-center justify-between mb-4">
|
||||||
<h2 class="text-lg font-display font-bold">업적</h2>
|
<h2 class="text-lg font-display font-bold">업적</h2>
|
||||||
<div class="flex items-center gap-1">
|
<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>
|
<span class="text-sm font-medium text-text-muted">4/8</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,24 +62,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</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/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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -4,235 +4,35 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>내 진행 상황 - Chakmate</title>
|
<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">
|
<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>
|
</head>
|
||||||
<body class="bg-surface-primary min-h-screen">
|
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
|
||||||
<!-- Header -->
|
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
|
||||||
<header class="bg-surface-card shadow-sm sticky top-0 z-50">
|
<header class="page-header">
|
||||||
<div class="max-w-lg mx-auto px-4 h-16 flex items-center justify-between">
|
<div class="header-left flex items-center gap-3">
|
||||||
<a href="/" class="flex items-center gap-2 text-text-primary hover:text-primary transition-colors">
|
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
|
||||||
<svg class="w-6 h-6"><use href="#icon-arrow-left"/></svg>
|
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
|
||||||
<span class="text-sm font-medium">뒤로</span>
|
</div>
|
||||||
</a>
|
<div class="header-right flex items-center gap-3">
|
||||||
<h1 class="text-lg font-display font-semibold text-text-primary">내 진행 상황</h1>
|
<div class="streak-badge flex items-center gap-2 bg-surface-card px-4 py-2 rounded-full shadow-sm">
|
||||||
<div class="w-16"></div>
|
<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>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="max-w-lg mx-auto px-4 py-6 space-y-6 pb-[120px]">
|
<main class="page-content space-y-6">
|
||||||
<!-- Streak Section -->
|
<section class="bg-gradient-to-br from-primary/10 to-accent/10 rounded-xl p-6 text-center">
|
||||||
<section class="bg-gradient-to-br from-primary/10 to-accent/10 rounded-[24px] p-6 text-center">
|
|
||||||
<div class="flex items-center justify-center gap-3 mb-4">
|
<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>
|
<span class="text-6xl font-display font-bold text-text-primary" id="streakCount">0</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-text-secondary text-sm mb-2">일 연속 정리</p>
|
<p class="text-text-secondary text-sm mb-2">일 연속 정리</p>
|
||||||
<p class="text-text-muted text-xs" id="streakMessage">오늘 불을 이어가세요!</p>
|
<p class="text-text-muted text-xs" id="streakMessage">오늘 불을 이어가세요!</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Weekly Progress -->
|
<section class="bg-surface-card rounded-xl p-6 shadow-sm">
|
||||||
<section class="bg-surface-card rounded-[24px] p-6 shadow-sm">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-base font-semibold text-text-primary">이번 주 진행률</h2>
|
<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>
|
<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>
|
<div class="week-grid" id="weekGrid"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Daily Tip -->
|
<section class="bg-surface-card rounded-xl p-5 shadow-sm relative overflow-hidden">
|
||||||
<section class="bg-surface-card rounded-[24px] 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="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="relative">
|
||||||
<div class="flex items-center gap-2 mb-3">
|
<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>
|
<span class="text-sm font-medium text-text-primary">오늘의 팁</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-text-secondary text-sm leading-relaxed" id="tipText">"정돈된 공간은 정돈된 마음을 만듭니다."</p>
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Achievements -->
|
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-base font-semibold text-text-primary mb-4">업적</h2>
|
<h2 class="text-lg font-semibold text-text-primary mb-4">업적</h2>
|
||||||
<div class="grid grid-cols-4 gap-3" id="achievementsGrid"></div>
|
<div class="grid grid-cols-2 gap-3" id="achievementsGrid"></div>
|
||||||
</section>
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
<!-- Settings -->
|
<script type="module" src="../scripts/pages/gamification.js"></script>
|
||||||
<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>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -5,99 +5,94 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Chakmate - 지능형 디지털 자산 관리</title>
|
<title>Chakmate - 지능형 디지털 자산 관리</title>
|
||||||
<link rel="stylesheet" href="../styles/main.css">
|
<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>
|
</head>
|
||||||
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
|
<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">
|
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
|
||||||
<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">
|
<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">
|
||||||
<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="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="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 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="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="3"></div>
|
||||||
|
<div class="step-dot w-2 h-2 rounded-full bg-text-muted transition-all duration-250" data-step="4"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 1: Welcome -->
|
|
||||||
<div class="onboarding-step active" data-step="1">
|
<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>
|
<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>
|
</div>
|
||||||
<h2 class="step-title text-xl mb-3 text-text-primary font-display">파일 정리, 지능적으로</h2>
|
<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>
|
<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">
|
<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>
|
<svg class="w-4 h-4 stroke-white fill-none"><use href="../assets/icons/icons.svg#icon-shield-check"></use></svg>
|
||||||
<span>기기 내 단독 처리 • 완벽한 개인정보 보호</span>
|
<span>기기 내 단독 처리 - 완벽한 개인정보 보호</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 2: Swipe -->
|
|
||||||
<div class="onboarding-step hidden" data-step="2">
|
<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>
|
<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>
|
</div>
|
||||||
<h2 class="step-title text-xl mb-3 text-text-primary font-display">스вай프로 빠른 정리</h2>
|
<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>
|
<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="swipe-hints flex justify-between w-full max-w-[280px] mx-auto text-text-muted text-sm">
|
||||||
<div class="flex items-center gap-1">
|
<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>
|
<span>삭제</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<span>보관</span>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 3: AI -->
|
|
||||||
<div class="onboarding-step hidden" data-step="3">
|
<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>
|
<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>
|
</div>
|
||||||
<h2 class="step-title text-xl mb-3 text-text-primary font-display">AI가 제안하는 구조</h2>
|
<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>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-primary w-full max-w-[280px] mx-auto lg:mx-0" id="start-organizing">
|
<div class="onboarding-step hidden" data-step="4">
|
||||||
<span>정리 시작하기</span>
|
<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">
|
||||||
<svg class="w-5 h-5 stroke-current fill-none"><use href="#icon-arrow-right"></use></svg>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -5,47 +5,24 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>설정 - Chakmate</title>
|
<title>설정 - Chakmate</title>
|
||||||
<link rel="stylesheet" href="../styles/main.css">
|
<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>
|
</head>
|
||||||
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
|
<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)]">
|
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
|
||||||
<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">
|
<header class="page-header">
|
||||||
<svg class="w-5 h-5"><use href="#icon-home"></use></svg>
|
<div class="header-left flex items-center gap-3">
|
||||||
<span class="text-sm font-medium">홈으로</span>
|
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
|
||||||
</a>
|
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
|
||||||
<h1 class="font-display text-lg font-semibold text-text-primary">설정</h1>
|
</div>
|
||||||
<div class="w-[80px]"></div>
|
<div class="header-right flex items-center gap-3"></div>
|
||||||
</header>
|
</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">
|
<div class="settings-header mb-8">
|
||||||
<h2 class="settings-title text-3xl mb-2 font-display">설정</h2>
|
<h2 class="settings-title text-3xl mb-2 font-display">설정</h2>
|
||||||
<p class="settings-subtitle text-text-secondary">앱 환경 사용자 지정</p>
|
<p class="settings-subtitle text-text-secondary">앱 환경 사용자 지정</p>
|
||||||
</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>
|
<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-row flex items-center justify-between py-4 border-b border-surface-secondary">
|
||||||
<div class="setting-info flex-1">
|
<div class="setting-info flex-1">
|
||||||
@@ -63,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<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-row flex items-center justify-between py-4 border-b border-surface-secondary">
|
||||||
<div class="setting-info flex-1">
|
<div class="setting-info flex-1">
|
||||||
@@ -74,51 +51,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="setting-row flex items-center justify-between py-4 border-b-0">
|
<div class="setting-row flex items-center justify-between py-4 border-b-0">
|
||||||
<div class="setting-info flex-1">
|
<div class="setting-info flex-1">
|
||||||
<div class="setting-name font-medium mb-1">삭제 확인</div>
|
<div class="setting-name font-medium mb-1">스캔 경로</div>
|
||||||
<div class="setting-desc text-sm text-text-secondary">파일 삭제 시 확인 메시지를 표시합니다</div>
|
<div class="setting-desc text-sm text-text-secondary" id="scanPathDisplay">Downloads</div>
|
||||||
</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>
|
<button class="text-primary text-sm font-medium" id="changePathBtn">변경</button>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-section bg-surface-card rounded-[28px] shadow-md p-6">
|
<script type="module" src="../scripts/pages/settings.js"></script>
|
||||||
<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>
|
|
||||||
|
|
||||||
<script type="module" src="../scripts/shared/state.js"></script>
|
|
||||||
<script type="module" src="../scripts/settings.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -6,77 +6,62 @@
|
|||||||
<title>스와이프 모드 - Chakmate</title>
|
<title>스와이프 모드 - Chakmate</title>
|
||||||
<link rel="stylesheet" href="../styles/main.css">
|
<link rel="stylesheet" href="../styles/main.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased">
|
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
|
||||||
<!-- Heroicons SVG Sprite -->
|
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
|
<header class="page-header">
|
||||||
<symbol id="icon-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<div class="header-left flex items-center gap-3">
|
||||||
<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"/>
|
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
|
||||||
</symbol>
|
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
|
||||||
<symbol id="icon-arrow-uturn-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
</div>
|
||||||
<path d="M3 7v6h6M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
|
<div class="header-right flex items-center gap-3">
|
||||||
</symbol>
|
<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>
|
||||||
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg class="w-4 h-4"><use href="../assets/icons/icons.svg#icon-arrow-uturn-left"></use></svg>
|
||||||
<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>
|
</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="flex flex-col min-h-[calc(100vh-70px)] px-6">
|
<div id="swipe" class="page-content">
|
||||||
<div class="text-center mb-8">
|
<div class="text-center mb-4">
|
||||||
<h2 class="text-[28px] font-bold text-text-primary mb-1" id="currentFileName">project_proposal.pdf</h2>
|
<h2 class="text-xl font-bold text-text-primary mb-1" id="currentFileName">파일을 로딩 중...</h2>
|
||||||
<p class="text-text-muted text-sm">왼쪽으로 스와이프하면 삭제, 오른쪽으로 스와이프하면 보관</p>
|
<p class="text-text-muted text-sm">왼쪽으로 스와이프하면 삭제, 오른쪽으로 스와이프하면 보관</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 flex flex-col items-center justify-center relative py-8">
|
<div class="flex-1 flex flex-col items-center justify-center relative min-h-0">
|
||||||
<div class="relative w-full max-w-[320px] h-[420px]" id="cardStack"></div>
|
<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="hidden flex-col items-center justify-center text-center p-8 absolute inset-0" 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">
|
<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-[60px] h-[60px] text-white"><use href="#icon-check"></use></svg>
|
<svg class="w-[50px] h-[50px] text-white"><use href="../assets/icons/icons.svg#icon-check"></use></svg>
|
||||||
</div>
|
</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>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center gap-6 mt-8" id="actionButtons">
|
<div class="flex justify-center gap-6 py-4" 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')">
|
<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-9 h-9"><use href="#icon-trash"></use></svg>
|
<svg class="w-7 h-7"><use href="../assets/icons/icons.svg#icon-trash"></use></svg>
|
||||||
</button>
|
</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')">
|
<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-9 h-9"><use href="#icon-check"></use></svg>
|
<svg class="w-7 h-7"><use href="../assets/icons/icons.svg#icon-folder"></use></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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 justify-between items-center px-4 py-2 bg-surface-card rounded-full max-w-[280px] mx-auto">
|
||||||
<div class="flex items-center gap-4">
|
<span class="text-text-muted text-xs">삭제</span>
|
||||||
<div class="flex-1 h-2 bg-surface-secondary rounded-full overflow-hidden">
|
<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-secondary rounded-full transition-all duration-500" id="progressFill" style="width: 0%"></div>
|
<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>
|
||||||
|
<span class="text-text-muted text-xs" id="progressText">0 / 0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-display text-sm font-semibold text-primary min-w-[60px] text-right" id="progressText">0%</div>
|
|
||||||
</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>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -4,125 +4,55 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>구조 제안 - Chakmate</title>
|
<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">
|
<link rel="stylesheet" href="../styles/main.css">
|
||||||
<script type="module" src="../scripts/visualization.js"></script>
|
</head>
|
||||||
<div class="app">
|
<body class="font-sans bg-surface-primary text-text-primary min-h-screen antialiased overflow-x-hidden">
|
||||||
<!-- Header -->
|
<object type="image/svg+xml" data="../assets/icons/icons.svg" style="display:none"></object>
|
||||||
<header class="header">
|
<header class="page-header">
|
||||||
<div class="header-left">
|
<div class="header-left flex items-center gap-3">
|
||||||
<div class="header-logo">
|
<img src="../assets/logo.svg" alt="Chakmate Logo" class="w-10 h-10 object-contain">
|
||||||
<svg viewBox="0 0 24 24" fill="none">
|
<span class="font-display text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Chakmate</span>
|
||||||
<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>
|
</div>
|
||||||
<h1 class="header-title">구조 제안</h1>
|
<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>
|
||||||
<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>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div class="page-content">
|
||||||
<div class="comparison-banner">
|
<div class="comparison-banner">
|
||||||
<div class="comparison-item current">
|
<div class="comparison-item current">
|
||||||
<div class="comparison-icon">
|
<div class="comparison-icon">
|
||||||
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span>현재</span>
|
<span>현재</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="comparison-arrow">
|
<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">
|
<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>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<div class="comparison-item proposed">
|
<div class="comparison-item proposed">
|
||||||
<div class="comparison-icon">
|
<div class="comparison-icon">
|
||||||
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span>AI 제안</span>
|
<span>AI 제안</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Before/After Comparison UI -->
|
|
||||||
<div class="comparison-container">
|
<div class="comparison-container">
|
||||||
<!-- Before Panel - Messy State -->
|
|
||||||
<div class="comparison-panel">
|
<div class="comparison-panel">
|
||||||
<div class="comparison-panel-header before">
|
<div class="comparison-panel-header before">
|
||||||
<div class="comparison-panel-icon 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">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -130,352 +60,35 @@
|
|||||||
<div class="comparison-panel-subtitle">지저분한 폴더 구조</div>
|
<div class="comparison-panel-subtitle">지저분한 폴더 구조</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="before-tree">
|
<div class="before-tree" id="beforeTree"></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">사진_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>
|
</div>
|
||||||
|
|
||||||
<!-- After Panel - Organized State -->
|
|
||||||
<div class="comparison-panel">
|
<div class="comparison-panel">
|
||||||
<div class="comparison-panel-header after">
|
<div class="comparison-panel-header after">
|
||||||
<div class="comparison-panel-icon 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>
|
<div>
|
||||||
<div class="comparison-panel-title">정리 후</div>
|
<div class="comparison-panel-title">정리 후</div>
|
||||||
<div class="comparison-panel-subtitle">AI가 제안하는 구조</div>
|
<div class="comparison-panel-subtitle">AI가 제안하는 구조</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="after-tree">
|
<div class="after-tree" id="afterTree"></div>
|
||||||
<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>
|
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 mt-6">
|
||||||
<div class="tree-item" data-category="personal" data-folder="personal">
|
<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">
|
||||||
<div class="tree-item-header">
|
거절
|
||||||
<label class="checkbox-wrapper">
|
</button>
|
||||||
<input type="checkbox" class="checkbox-input" data-type="folder" data-folder="personal">
|
<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">
|
||||||
<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>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="../scripts/pages/visualization.js"></script>
|
||||||
<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>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
109
src/public/assets/icons/icons.svg
Normal file
109
src/public/assets/icons/icons.svg
Normal 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 |
@@ -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();
|
|
||||||
});
|
|
||||||
@@ -1,273 +1,58 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
import { AppState } from './shared/state.js';
|
||||||
const state = {
|
|
||||||
currentScreen: 'onboarding',
|
export const FILE_TYPES = {
|
||||||
onboardingStep: 1,
|
pdf: { label: 'PDF', color: '#ef4444', icon: 'document' },
|
||||||
streak: parseInt(localStorage.getItem('chackly_streak') || '7'),
|
doc: { label: 'Doc', color: '#3b82f6', icon: 'document' },
|
||||||
filesOrganized: parseInt(localStorage.getItem('chackly_files_organized') || '248'),
|
image: { label: 'Image', color: '#8b5cf6', icon: 'image' },
|
||||||
foldersManaged: parseInt(localStorage.getItem('chackly_folders') || '12'),
|
video: { label: 'Video', color: '#ec4899', icon: 'image' },
|
||||||
swipeIndex: 0,
|
excel: { label: 'Excel', color: '#10b981', icon: 'chart-bar' },
|
||||||
swipeHistory: [],
|
ppt: { label: 'PPT', color: '#f97316', icon: 'document' },
|
||||||
theme: localStorage.getItem('chackly_theme') || 'light',
|
archive: { label: 'Archive', color: '#6b7280', icon: 'folder' },
|
||||||
settings: JSON.parse(localStorage.getItem('chackly_settings') || '{"notifications":true,"habits":false,"autoOrganize":false,"confirmDelete":true}')
|
};
|
||||||
|
|
||||||
|
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 = [
|
export { AppState };
|
||||||
{ 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();
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
import { SUGGESTIONS, ACHIEVEMENTS, AppState, getStats } from '../main.js';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const state = {
|
const state = {
|
||||||
streak: AppState.getStreak(),
|
streak: await AppState.getStreak(),
|
||||||
filesOrganized: AppState.getFilesOrganized(),
|
filesOrganized: await AppState.getFilesOrganized(),
|
||||||
foldersManaged: AppState.getFoldersManaged()
|
foldersManaged: await AppState.getFoldersManaged()
|
||||||
};
|
};
|
||||||
|
|
||||||
const suggestions = [
|
const suggestions = [
|
||||||
{ name: '📷 사진 → 2024', detail: '2개 파일 이동', selected: false },
|
{ name: 'Photos to 2024', detail: '2 files moved', selected: false },
|
||||||
{ name: '📄 문서 → quarterly', detail: '3개 파일 이동', selected: true },
|
{ name: 'Docs to quarterly', detail: '3 files moved', selected: true },
|
||||||
{ name: '📦 압축 → 보관', detail: '1개 파일 이동', selected: false }
|
{ name: 'Archives to storage', detail: '1 files moved', selected: false }
|
||||||
];
|
];
|
||||||
|
|
||||||
const achievements = [
|
const achievements = [
|
||||||
{ name: '첫 정리', icon: '🏆', color: 'gold', unlocked: true },
|
{ name: 'First Sort', icon: 'trophy', color: 'gold', unlocked: true },
|
||||||
{ name: '7일 스트릭', icon: '🔥', color: 'gold', unlocked: true },
|
{ name: '7-Day Streak', icon: 'fire', color: 'gold', unlocked: true },
|
||||||
{ name: '파일 마스터', icon: '📁', color: 'silver', unlocked: true },
|
{ name: 'File Master', icon: 'folder', color: 'silver', unlocked: true },
|
||||||
{ name: '정리의 달인', icon: '⭐', color: 'silver', unlocked: true },
|
{ name: 'Organizer Pro', icon: 'star', color: 'silver', unlocked: true },
|
||||||
{ name: '30일 스트릭', icon: '💎', color: 'gold', unlocked: false },
|
{ name: '30-Day Streak', icon: 'star', color: 'gold', unlocked: false },
|
||||||
{ name: 'AI 활용자', icon: '🤖', color: 'bronze', unlocked: false },
|
{ name: 'AI User', icon: 'sparkles', color: 'bronze', unlocked: false },
|
||||||
{ name: '조직력왕', icon: '👑', color: 'silver', unlocked: false },
|
{ name: 'Organized King', icon: 'folder', color: 'silver', unlocked: false },
|
||||||
{ name: '창의命名', icon: '💡', color: 'bronze', unlocked: false }
|
{ name: 'Creative', icon: 'light-bulb', color: 'bronze', unlocked: false }
|
||||||
];
|
];
|
||||||
|
|
||||||
function renderSuggestions() {
|
function renderSuggestions() {
|
||||||
@@ -29,13 +31,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
container.innerHTML = suggestions.map((s, i) => `
|
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-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">
|
<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>
|
||||||
<div class="suggestion-content flex-1">
|
<div class="suggestion-content flex-1">
|
||||||
<div class="suggestion-name font-medium mb-0.5">${s.name}</div>
|
<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 class="suggestion-detail text-sm text-text-secondary">${s.detail}</div>
|
||||||
</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>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
container.innerHTML = achievements.map(a => `
|
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 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'}">
|
<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>
|
||||||
<div class="achievement-name text-[10px] text-center text-text-secondary">${a.name}</div>
|
<div class="achievement-name text-[10px] text-center text-text-secondary">${a.name}</div>
|
||||||
</div>
|
</div>
|
||||||
140
src/scripts/pages/gamification.js
Normal file
140
src/scripts/pages/gamification.js
Normal 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();
|
||||||
|
});
|
||||||
80
src/scripts/pages/onboarding.js
Normal file
80
src/scripts/pages/onboarding.js
Normal 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);
|
||||||
|
});
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
import { AppState } from '../main.js';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const state = {
|
const state = {
|
||||||
theme: AppState.getTheme(),
|
theme: await AppState.getTheme(),
|
||||||
settings: AppState.getSettings()
|
settings: await AppState.getSettings()
|
||||||
};
|
};
|
||||||
|
|
||||||
function initToggles() {
|
function initToggles() {
|
||||||
document.querySelectorAll('.toggle').forEach(toggle => {
|
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.addEventListener('click', () => {
|
||||||
toggle.classList.toggle('active');
|
toggle.classList.toggle('active');
|
||||||
const isActive = toggle.classList.contains('active');
|
const isActive = toggle.classList.contains('active');
|
||||||
@@ -17,14 +25,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
function initThemeOptions() {
|
function initThemeOptions() {
|
||||||
document.querySelectorAll('.theme-option').forEach(option => {
|
document.querySelectorAll('.theme-option').forEach(option => {
|
||||||
option.addEventListener('click', () => {
|
option.addEventListener('click', async () => {
|
||||||
document.querySelectorAll('.theme-option').forEach(o => {
|
document.querySelectorAll('.theme-option').forEach(o => {
|
||||||
o.classList.remove('border-primary');
|
o.classList.remove('border-primary');
|
||||||
o.classList.add('border-transparent');
|
o.classList.add('border-transparent');
|
||||||
});
|
});
|
||||||
option.classList.remove('border-transparent');
|
option.classList.remove('border-transparent');
|
||||||
option.classList.add('border-primary');
|
option.classList.add('border-primary');
|
||||||
AppState.setTheme(option.dataset.theme);
|
await AppState.setTheme(option.dataset.theme);
|
||||||
state.theme = option.dataset.theme;
|
state.theme = option.dataset.theme;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -36,14 +44,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
async function saveSettings() {
|
||||||
const settings = {
|
const settings = {
|
||||||
notifications: document.getElementById('toggle-notifications')?.classList.contains('active'),
|
notifications: document.getElementById('toggle-notifications')?.classList.contains('active'),
|
||||||
habits: document.getElementById('toggle-habits')?.classList.contains('active'),
|
habits: document.getElementById('toggle-habits')?.classList.contains('active'),
|
||||||
autoOrganize: document.getElementById('toggle-autoOrganize')?.classList.contains('active'),
|
autoOrganize: document.getElementById('toggle-autoOrganize')?.classList.contains('active'),
|
||||||
confirmDelete: document.getElementById('toggle-confirmDelete')?.classList.contains('active')
|
confirmDelete: document.getElementById('toggle-confirmDelete')?.classList.contains('active')
|
||||||
};
|
};
|
||||||
AppState.setSettings(settings);
|
await AppState.setSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('export-data')?.addEventListener('click', () => {
|
document.getElementById('export-data')?.addEventListener('click', () => {
|
||||||
@@ -61,8 +69,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('reset-data')?.addEventListener('click', () => {
|
document.getElementById('reset-data')?.addEventListener('click', async () => {
|
||||||
if (confirm('모든 데이터를 초기화하시겠습니까?')) {
|
if (confirm('Reset all data?')) {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
179
src/scripts/pages/swipe.js
Normal file
179
src/scripts/pages/swipe.js
Normal 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();
|
||||||
|
});
|
||||||
92
src/scripts/shared/fileScanner.js
Normal file
92
src/scripts/shared/fileScanner.js
Normal 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
54
src/scripts/shared/nav.js
Normal 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');
|
||||||
|
});
|
||||||
@@ -1,40 +1,50 @@
|
|||||||
/**
|
import { Store } from '@tauri-apps/plugin-store';
|
||||||
* AppState - Shared state management module
|
|
||||||
* Handles all localStorage interactions for the Chakmate app
|
let store = null;
|
||||||
*/
|
|
||||||
|
async function getStore() {
|
||||||
|
if (!store) {
|
||||||
|
store = await Store.load('chakmate-data.json');
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
const AppState = {
|
const AppState = {
|
||||||
// ============ Theme ============
|
async getTheme() {
|
||||||
getTheme() {
|
const s = await getStore();
|
||||||
return localStorage.getItem('chackly_theme') || 'light';
|
return await s.get('theme') || 'light';
|
||||||
},
|
},
|
||||||
|
|
||||||
setTheme(theme) {
|
async setTheme(theme) {
|
||||||
localStorage.setItem('chackly_theme', theme);
|
const s = await getStore();
|
||||||
|
await s.set('theme', theme);
|
||||||
|
await s.save();
|
||||||
document.documentElement.setAttribute('data-theme', theme);
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============ Settings ============
|
async getSettings() {
|
||||||
getSettings() {
|
const s = await getStore();
|
||||||
const defaults = {
|
const defaults = {
|
||||||
notifications: true,
|
notifications: true,
|
||||||
habits: false,
|
habits: false,
|
||||||
autoOrganize: false,
|
autoOrganize: false,
|
||||||
confirmDelete: true
|
confirmDelete: true
|
||||||
};
|
};
|
||||||
try {
|
const stored = await s.get('settings');
|
||||||
return { ...defaults, ...JSON.parse(localStorage.getItem('chackly_settings') || '{}') };
|
if (stored) {
|
||||||
} catch {
|
return { ...defaults, ...stored };
|
||||||
return defaults;
|
|
||||||
}
|
}
|
||||||
|
return defaults;
|
||||||
},
|
},
|
||||||
|
|
||||||
setSettings(settings) {
|
async setSettings(settings) {
|
||||||
localStorage.setItem('chackly_settings', JSON.stringify(settings));
|
const s = await getStore();
|
||||||
|
await s.set('settings', settings);
|
||||||
|
await s.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============ Gamification ============
|
async getGamificationData() {
|
||||||
getGamificationData() {
|
const s = await getStore();
|
||||||
const defaultData = {
|
const defaultData = {
|
||||||
streak: 12,
|
streak: 12,
|
||||||
weeklyProgress: [true, true, true, true, false, false, false],
|
weeklyProgress: [true, true, true, true, false, false, false],
|
||||||
@@ -51,56 +61,84 @@ const AppState = {
|
|||||||
habitReminderEnabled: true,
|
habitReminderEnabled: true,
|
||||||
lastUpdated: new Date().toISOString()
|
lastUpdated: new Date().toISOString()
|
||||||
};
|
};
|
||||||
try {
|
const stored = await s.get('gamification');
|
||||||
const stored = localStorage.getItem('chackly_gamification');
|
|
||||||
if (stored) {
|
if (stored) {
|
||||||
return { ...defaultData, ...JSON.parse(stored) };
|
return { ...defaultData, ...stored };
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
}
|
||||||
return defaultData;
|
return defaultData;
|
||||||
},
|
},
|
||||||
|
|
||||||
setGamificationData(data) {
|
async setGamificationData(data) {
|
||||||
|
const s = await getStore();
|
||||||
data.lastUpdated = new Date().toISOString();
|
data.lastUpdated = new Date().toISOString();
|
||||||
localStorage.setItem('chackly_gamification', JSON.stringify(data));
|
await s.set('gamification', data);
|
||||||
|
await s.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============ Dashboard Stats ============
|
async getStreak() {
|
||||||
getStreak() {
|
const s = await getStore();
|
||||||
return parseInt(localStorage.getItem('chackly_streak') || '7');
|
return (await s.get('streak')) || 7;
|
||||||
},
|
},
|
||||||
|
|
||||||
setStreak(count) {
|
async setStreak(count) {
|
||||||
localStorage.setItem('chackly_streak', count.toString());
|
const s = await getStore();
|
||||||
|
await s.set('streak', count);
|
||||||
|
await s.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
getFilesOrganized() {
|
async getFilesOrganized() {
|
||||||
return parseInt(localStorage.getItem('chackly_files_organized') || '248');
|
const s = await getStore();
|
||||||
|
return (await s.get('filesOrganized')) || 248;
|
||||||
},
|
},
|
||||||
|
|
||||||
setFilesOrganized(count) {
|
async setFilesOrganized(count) {
|
||||||
localStorage.setItem('chackly_files_organized', count.toString());
|
const s = await getStore();
|
||||||
|
await s.set('filesOrganized', count);
|
||||||
|
await s.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
getFoldersManaged() {
|
async getFoldersManaged() {
|
||||||
return parseInt(localStorage.getItem('chackly_folders') || '12');
|
const s = await getStore();
|
||||||
|
return (await s.get('foldersManaged')) || 12;
|
||||||
},
|
},
|
||||||
|
|
||||||
setFoldersManaged(count) {
|
async setFoldersManaged(count) {
|
||||||
localStorage.setItem('chackly_folders', count.toString());
|
const s = await getStore();
|
||||||
|
await s.set('foldersManaged', count);
|
||||||
|
await s.save();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============ Initialization ============
|
async getScanPath() {
|
||||||
init() {
|
const s = await getStore();
|
||||||
// Apply saved theme on page load
|
return await s.get('scanPath') || null;
|
||||||
const theme = this.getTheme();
|
},
|
||||||
|
|
||||||
|
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);
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto-initialize when DOM is ready
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
AppState.init();
|
AppState.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.AppState = AppState;
|
||||||
|
export { AppState };
|
||||||
60
src/scripts/shared/titlebar.js
Normal file
60
src/scripts/shared/titlebar.js
Normal 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
229
src/scripts/shared/util.js
Normal 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);
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@import '@fontsource/noto-color-emoji/400.css';
|
||||||
@import '@fontsource/noto-sans-kr/300.css';
|
@import '@fontsource/noto-sans-kr/300.css';
|
||||||
@import '@fontsource/noto-sans-kr/400.css';
|
@import '@fontsource/noto-sans-kr/400.css';
|
||||||
@import '@fontsource/noto-sans-kr/500.css';
|
@import '@fontsource/noto-sans-kr/500.css';
|
||||||
@@ -11,7 +12,13 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@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"] {
|
[data-theme="dark"] {
|
||||||
--bg-primary: #0a0e1a;
|
--bg-primary: #0a0e1a;
|
||||||
--bg-secondary: #111827;
|
--bg-secondary: #111827;
|
||||||
@@ -54,44 +61,111 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Screen visibility */
|
/* Utility classes */
|
||||||
.screen {
|
.hidden { display: none !important; }
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.screen.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button styles */
|
/* Page Header - Unified pattern for all pages */
|
||||||
.btn {
|
.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;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.5rem;
|
gap: var(--space-2);
|
||||||
padding: 1rem 2rem;
|
padding: var(--space-3) var(--space-6);
|
||||||
border-radius: 9999px;
|
border-radius: var(--radius-full);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
transition: all 0.25s ease;
|
transition: all var(--transition-base);
|
||||||
}
|
}
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%);
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.3);
|
box-shadow: var(--shadow-blue);
|
||||||
}
|
}
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 6px 25px rgba(59, 130, 246, 0.4);
|
box-shadow: 0 6px 25px rgba(59, 130, 246, 0.4);
|
||||||
}
|
}
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background: #e2e8f0;
|
background: var(--bg-secondary);
|
||||||
color: #0f172a;
|
color: var(--text-primary);
|
||||||
}
|
border-radius: var(--radius-md);
|
||||||
.btn-secondary:hover {
|
}
|
||||||
|
.btn-secondary:hover {
|
||||||
background: #cbd5e1;
|
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) */
|
/* Swipe card animations (from scene_swipe.html) */
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
@@ -266,8 +340,80 @@
|
|||||||
box-sizing: border-box;
|
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 {
|
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);
|
background: var(--bg-primary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -1634,3 +1780,421 @@
|
|||||||
.checkbox-input:checked + .checkbox-visual {
|
.checkbox-input:checked + .checkbox-visual {
|
||||||
animation: checkBounce 0.3s ease;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1
vite-profile-0.cpuprofile
Normal file
1
vite-profile-0.cpuprofile
Normal file
File diff suppressed because one or more lines are too long
@@ -8,18 +8,18 @@ export default defineConfig({
|
|||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
|
index: resolve(__dirname, 'src/index.html'),
|
||||||
onboarding: resolve(__dirname, 'src/pages/scene_onboarding.html'),
|
onboarding: resolve(__dirname, 'src/pages/scene_onboarding.html'),
|
||||||
dashboard: resolve(__dirname, 'src/pages/scene_dashboard.html'),
|
dashboard: resolve(__dirname, 'src/pages/scene_dashboard.html'),
|
||||||
swipe: resolve(__dirname, 'src/pages/scene_swipe.html'),
|
swipe: resolve(__dirname, 'src/pages/scene_swipe.html'),
|
||||||
settings: resolve(__dirname, 'src/pages/scene_settings.html'),
|
|
||||||
'ai-classification': resolve(__dirname, 'src/pages/scene_ai_classification.html'),
|
|
||||||
gamification: resolve(__dirname, 'src/pages/scene_gamification.html'),
|
gamification: resolve(__dirname, 'src/pages/scene_gamification.html'),
|
||||||
|
'ai-classification': resolve(__dirname, 'src/pages/scene_ai_classification.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: {
|
output: {
|
||||||
assetFileNames: (assetInfo) => {
|
assetFileNames: (assetInfo) => {
|
||||||
if (assetInfo.name === 'logo.svg') return 'assets/[name][extname]';
|
return assetInfo.name === 'logo.svg' ? 'assets/[name][extname]' : 'assets/[name]-[hash][extname]'
|
||||||
return 'assets/[name]-[hash][extname]';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user