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 != "" {