diff --git a/achievements.js b/achievements.js index 3c30efb..248b4d3 100644 --- a/achievements.js +++ b/achievements.js @@ -127,14 +127,6 @@ async function checkAchievements() { unlockAchievement('super_fan'); localStorage.setItem('super_fan_' + visitorId, 'true'); } - - // Apply highest unlocked achievement theme - const unlocked = Array.from(unlockedAchievements); - if (unlocked.length > 0) { - const highestAchievement = unlocked[unlocked.length - 1]; - currentTheme = ACHIEVEMENTS[highestAchievement].theme; - applyTheme(); - } } catch (error) { console.error('Error checking achievements:', error); } @@ -142,72 +134,84 @@ async function checkAchievements() { // Unlock achievement and show toast function unlockAchievement(achievement) { - const achievementId = Object.keys(ACHIEVEMENTS).find(key => - ACHIEVEMENTS[key].name === achievement.name - ); - - if (!unlockedAchievements.has(achievementId)) { - unlockedAchievements.add(achievementId); - showAchievementToast(achievement); - } -} - -// Show only unlocked achievements -function showAchievements() { - const achievementsDisplay = document.getElementById('achievementsDisplay'); - if (achievementsDisplay) { - // Clear existing achievements - achievementsDisplay.innerHTML = ''; - - // Show only unlocked achievements - Array.from(unlockedAchievements).forEach(achievementId => { - const achievement = ACHIEVEMENTS[achievementId]; - const achievementItem = document.createElement('div'); - achievementItem.className = 'achievement-item flex items-center p-3 rounded-lg mb-2'; - achievementItem.style.backgroundColor = achievement.theme.backgroundColor; - achievementItem.style.color = achievement.theme.textColor; - - achievementItem.innerHTML = ` - -
-

${achievement.name}

-

${achievement.description}

-
- `; - - achievementsDisplay.appendChild(achievementItem); - }); - - achievementsDisplay.style.display = 'block'; - } + unlockedAchievements.add(achievement); + showAchievementToast(achievement); + showAchievements(); } // Show achievement toast function showAchievementToast(achievement) { const toast = document.createElement('div'); - toast.className = `fixed bottom-4 right-4 bg-white rounded-lg shadow-lg p-4 w-64 flex items-center ${achievement.color}`; - + toast.className = 'fixed bottom-4 right-4 bg-white rounded-lg shadow-lg p-4 w-64 flex items-center text-green-500'; toast.innerHTML = `
-

${achievement.name}

-

${achievement.description}

+

Achievement Unlocked!

+

${ACHIEVEMENTS[achievement].name}

- + `; - document.body.appendChild(toast); + // Remove toast after 3 seconds setTimeout(() => { toast.remove(); - }, 5000); + }, 3000); } -// Show achievements display +// Show only unlocked achievements function showAchievements() { const achievementsDisplay = document.getElementById('achievementsDisplay'); - if (achievementsDisplay) { - achievementsDisplay.style.display = 'block'; + const achievementItems = achievementsDisplay.querySelectorAll('.achievement-item'); + + achievementItems.forEach(item => { + const achievementId = item.querySelector('.achievement-icon').classList[1]; + const achievement = ACHIEVEMENTS[achievementId.replace('achievement-icon-', '')]; + if (achievement && unlockedAchievements.has(achievementId.replace('achievement-icon-', ''))) { + item.style.display = 'block'; + // Apply achievement theme + Object.entries(achievement.theme).forEach(([key, value]) => { + item.classList.add(value); + }); + } else { + item.style.display = 'none'; + } + }); + + // Show achievements display if any achievements are unlocked + if (unlockedAchievements.size > 0) { + achievementsDisplay.classList.remove('hidden'); + } else { + achievementsDisplay.classList.add('hidden'); } + + // Apply highest unlocked achievement theme to the page + applyHighestAchievementTheme(); +} + +// Apply highest unlocked achievement theme +function applyHighestAchievementTheme() { + const unlocked = Array.from(unlockedAchievements); + if (unlocked.length === 0) return; + + // Sort achievements by threshold to find the highest + const sortedAchievements = Object.entries(ACHIEVEMENTS) + .filter(([id]) => unlocked.includes(id)) + .sort(([, a], [, b]) => b.threshold - a.threshold); + + const highest = sortedAchievements[0][1]; + + // Apply theme to body + const body = document.body; + // Remove existing theme classes + ['bg-', 'text-', 'border-', 'hover:'].forEach(prefix => { + const classes = Array.from(body.classList).filter(cls => !cls.startsWith(prefix)); + body.className = classes.join(' '); + }); + + // Add new theme classes + Object.entries(highest.theme).forEach(([key, value]) => { + body.classList.add(value); + }); } // Hide achievements display @@ -257,14 +261,11 @@ function initializeAchievements() { `; document.body.appendChild(achievementToast); - // Enable achievements - toggleAchievements(); + achievementsEnabled = true; + localStorage.setItem('achievementsEnabled', true); - // Reset cheat code - cheatCodeIndex = 0; - - // Remove achievement toast after 3 seconds + // Remove toast after 3 seconds setTimeout(() => { achievementToast.remove(); }, 3000); @@ -275,7 +276,7 @@ function initializeAchievements() { } }); - // Check achievements when enabled + // Initialize achievements if enabled if (achievementsEnabled) { checkAchievements(); showAchievements(); diff --git a/main.go b/main.go index c79484e..cf4bfb6 100644 --- a/main.go +++ b/main.go @@ -22,12 +22,81 @@ import ( "gopkg.in/gomail.v2" ) +// Czech day names +var czechDayNames = []string{ + "Pondělí", + "Úterý", + "Středa", + "Čtvrtek", + "Pátek", + "Sobota", + "Neděle", +} + +// Format time in Czech style (DD.MM.YYYY HH:mm) +func formatCzechTime(t time.Time) string { + return t.Format("02.01.2006 15:04") +} + +// Format time with Czech day name (DD.MM.YYYY HH:mm - DayName) +func formatCzechTimeWithDay(t time.Time) string { + day := czechDayNames[t.Weekday()] + return fmt.Sprintf("%s - %s", formatCzechTime(t), day) +} + +// Detect device type from User-Agent +func detectDevice(userAgent string) string { + userAgent = strings.ToLower(userAgent) + + // Mobile devices + if strings.Contains(userAgent, "iphone") || strings.Contains(userAgent, "ipod") { + return "iPhone" + } else if strings.Contains(userAgent, "ipad") { + return "iPad" + } else if strings.Contains(userAgent, "android") { + if strings.Contains(userAgent, "mobile") { + return "Android Phone" + } + return "Android Tablet" + } else if strings.Contains(userAgent, "windows phone") { + return "Windows Phone" + } + + // Desktop devices + if strings.Contains(userAgent, "windows") { + return "Windows PC" + } else if strings.Contains(userAgent, "macintosh") || strings.Contains(userAgent, "mac os x") { + return "Mac" + } else if strings.Contains(userAgent, "linux") { + return "Linux PC" + } + + // Other devices + if strings.Contains(userAgent, "playstation") { + return "PlayStation" + } else if strings.Contains(userAgent, "xbox") { + return "Xbox" + } else if strings.Contains(userAgent, "nintendo") { + return "Nintendo" + } + + // Bots and crawlers + if strings.Contains(userAgent, "bot") || strings.Contains(userAgent, "crawler") { + return "Bot/Crawler" + } + + return "Unknown Device" +} + type Visitor struct { FirstVisit time.Time `json:"first_visit"` LastVisit time.Time `json:"last_visit"` Visits int `json:"visits"` IP string `json:"ip"` UserAgent string `json:"user_agent"` + Device string `json:"device"` + Browser string `json:"browser"` + OS string `json:"os"` } type VisitorStats struct { @@ -51,6 +120,60 @@ type VisitorStats struct { ReferrerStats map[string]int `json:"referrer_stats"` } +// Format VisitorStats with Czech dates +func (v *VisitorStats) FormatForDisplay() map[string]interface{} { + displayStats := map[string]interface{}{ + "total_visits": v.TotalVisits, + "today_visits": v.TodayVisits, + "last_visit": formatCzechTimeWithDay(v.LastVisit), + "monthly_visits": v.MonthlyVisits, + "weekly_visits": v.WeeklyVisits, + "last_updated": formatCzechTimeWithDay(v.LastUpdated), + "unique_visitors": make(map[string]interface{}), + "most_active_hours": make([]map[string]interface{}, len(v.MostActiveHours)), + "most_active_days": make([]map[string]interface{}, len(v.MostActiveDays)), + "browser_stats": v.BrowserStats, + "os_stats": v.OSStats, + "referrer_stats": v.ReferrerStats, + } + + // Format unique visitors + for id, visitor := range v.UniqueVisitors { + displayStats["unique_visitors"].(map[string]interface{})[id] = map[string]interface{}{ + "first_visit": formatCzechTimeWithDay(visitor.FirstVisit), + "last_visit": formatCzechTimeWithDay(visitor.LastVisit), + "visits": visitor.Visits, + "ip": visitor.IP, + "user_agent": visitor.UserAgent, + } + } + + // Format most active hours + for i, hour := range v.MostActiveHours { + displayStats["most_active_hours"].([]map[string]interface{})[i] = map[string]interface{}{ + "hour": hour.Hour, + "count": hour.Count, + } + } + + // Format most active days with Czech names + for i, day := range v.MostActiveDays { + weekday := time.Weekday(0) // Start from Monday + for _, czechDay := range czechDayNames { + if day.Day == weekday.String() { + displayStats["most_active_days"].([]map[string]interface{})[i] = map[string]interface{}{ + "day": czechDay, + "count": day.Count, + } + break + } + weekday++ + } + } + + return displayStats +} + // Initialize VisitorStats with proper struct types func (v *VisitorStats) init() { if v.TotalVisits == 0 { @@ -163,6 +286,11 @@ func trackVisit(w http.ResponseWriter, r *http.Request) { ip := r.RemoteAddr userAgent := r.UserAgent() + // Detect device information + device := detectDevice(userAgent) + browser := detectBrowser(userAgent) + os := detectOS(userAgent) + // Update visit counts stats.TotalVisits++ stats.TodayVisits++ @@ -191,6 +319,9 @@ func trackVisit(w http.ResponseWriter, r *http.Request) { Visits: 1, IP: ip, UserAgent: userAgent, + Device: device, + Browser: browser, + OS: os, } } else { visitor := stats.UniqueVisitors[visitorId] @@ -199,20 +330,24 @@ func trackVisit(w http.ResponseWriter, r *http.Request) { stats.UniqueVisitors[visitorId] = visitor } - // Update browser stats - browser := detectBrowser(userAgent) + // Update device stats if stats.BrowserStats == nil { stats.BrowserStats = make(map[string]int) } stats.BrowserStats[browser]++ // Update OS stats - os := detectOS(userAgent) if stats.OSStats == nil { stats.OSStats = make(map[string]int) } stats.OSStats[os]++ + // Update device stats + if stats.ReferrerStats == nil { + stats.ReferrerStats = make(map[string]int) + } + stats.ReferrerStats[device]++ + // Update referrer stats referrer := r.Referer() if referrer != "" {