210 lines
7.5 KiB
JavaScript
210 lines
7.5 KiB
JavaScript
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();
|
|
}); |