759 lines
25 KiB
HTML
759 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>User Profile - webGame</title>
|
|
<style>
|
|
:root {
|
|
--primary-color: #8B5CF6;
|
|
--primary-dark: #7C3AED;
|
|
--primary-light: #A78BFA;
|
|
--secondary-color: #10B981;
|
|
--background: #0F172A;
|
|
--surface: #1E293B;
|
|
--surface-light: #334155;
|
|
--text-primary: #F1F5F9;
|
|
--text-secondary: #94A3B8;
|
|
--border: #475569;
|
|
--success: #10B981;
|
|
--warning: #F59E0B;
|
|
--error: #EF4444;
|
|
--gradient: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: var(--background);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
padding: 20px;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.header {
|
|
text-align: center;
|
|
margin-bottom: 40px;
|
|
padding: 30px 20px;
|
|
background: var(--gradient);
|
|
border-radius: 20px;
|
|
box-shadow: 0 10px 30px rgba(139, 92, 246, 0.3);
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 10px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.header p {
|
|
font-size: 1.1rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.profile-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: 1fr 2fr;
|
|
gap: 30px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.profile-info {
|
|
background: var(--surface);
|
|
border-radius: 15px;
|
|
padding: 30px;
|
|
border: 1px solid var(--border);
|
|
height: fit-content;
|
|
}
|
|
|
|
.profile-avatar {
|
|
width: 120px;
|
|
height: 120px;
|
|
background: var(--gradient);
|
|
border-radius: 50%;
|
|
margin: 0 auto 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 3rem;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.profile-avatar:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.profile-details h2 {
|
|
font-size: 1.8rem;
|
|
margin-bottom: 15px;
|
|
text-align: center;
|
|
}
|
|
|
|
.profile-field {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.profile-field label {
|
|
display: block;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.profile-field input, .profile-field textarea {
|
|
width: 100%;
|
|
padding: 12px;
|
|
background: var(--background);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
color: var(--text-primary);
|
|
font-size: 1rem;
|
|
transition: border-color 0.3s ease;
|
|
}
|
|
|
|
.profile-field input:focus, .profile-field textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.profile-field textarea {
|
|
min-height: 80px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.stats-overview {
|
|
background: var(--surface);
|
|
border-radius: 15px;
|
|
padding: 30px;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.stats-overview h3 {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.quick-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.quick-stat {
|
|
background: var(--background);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.quick-stat-value {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: var(--primary-light);
|
|
display: block;
|
|
}
|
|
|
|
.quick-stat-label {
|
|
font-size: 0.9rem;
|
|
color: var(--text-secondary);
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.level-progress {
|
|
background: var(--background);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.level-info {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.level-bar {
|
|
background: var(--surface-light);
|
|
height: 20px;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.level-fill {
|
|
background: var(--gradient);
|
|
height: 100%;
|
|
border-radius: 10px;
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.level-text {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-size: 0.8rem;
|
|
font-weight: bold;
|
|
color: white;
|
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.7);
|
|
}
|
|
|
|
.achievements {
|
|
background: var(--surface);
|
|
border-radius: 15px;
|
|
padding: 30px;
|
|
border: 1px solid var(--border);
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.achievements h3 {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.achievement-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
}
|
|
|
|
.achievement {
|
|
background: var(--background);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
border: 1px solid var(--border);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.achievement.unlocked {
|
|
border-color: var(--success);
|
|
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1), rgba(16, 185, 129, 0.05));
|
|
}
|
|
|
|
.achievement.locked {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.achievement:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.achievement-icon {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 10px;
|
|
display: block;
|
|
}
|
|
|
|
.achievement-title {
|
|
font-weight: bold;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.achievement-desc {
|
|
font-size: 0.9rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin-top: 30px;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
|
|
.btn {
|
|
padding: 12px 25px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--gradient);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(139, 92, 246, 0.4);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--surface-light);
|
|
color: var(--text-primary);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--surface);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.profile-container {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.quick-stats {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.achievement-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.export-section {
|
|
background: var(--surface);
|
|
border-radius: 15px;
|
|
padding: 30px;
|
|
border: 1px solid var(--border);
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.export-section h3 {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>👤 User Profile</h1>
|
|
<p>Customize your gaming experience and track your progress</p>
|
|
</div>
|
|
|
|
<div class="profile-container">
|
|
<div class="profile-info">
|
|
<div class="profile-avatar" id="profile-avatar">
|
|
<span id="avatar-text">U</span>
|
|
</div>
|
|
<div class="profile-details">
|
|
<h2>Profile Information</h2>
|
|
|
|
<div class="profile-field">
|
|
<label for="username">Username</label>
|
|
<input type="text" id="username" placeholder="Enter your username">
|
|
</div>
|
|
|
|
<div class="profile-field">
|
|
<label for="bio">Bio</label>
|
|
<textarea id="bio" placeholder="Tell us about yourself..."></textarea>
|
|
</div>
|
|
|
|
<div class="profile-field">
|
|
<label for="join-date">Member Since</label>
|
|
<input type="text" id="join-date" readonly>
|
|
</div>
|
|
|
|
<div class="profile-field">
|
|
<label for="theme">Preferred Theme</label>
|
|
<select id="theme" style="width: 100%; padding: 12px; background: var(--background); border: 1px solid var(--border); border-radius: 8px; color: var(--text-primary);">
|
|
<option value="dark">Dark Theme</option>
|
|
<option value="light">Light Theme</option>
|
|
<option value="auto">Auto (System)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-overview">
|
|
<h3>📊 Statistics Overview</h3>
|
|
|
|
<div class="quick-stats">
|
|
<div class="quick-stat">
|
|
<span class="quick-stat-value" id="total-watch-time">0h 0m</span>
|
|
<div class="quick-stat-label">Total Watch Time</div>
|
|
</div>
|
|
<div class="quick-stat">
|
|
<span class="quick-stat-value" id="videos-watched">0</span>
|
|
<div class="quick-stat-label">Videos Watched</div>
|
|
</div>
|
|
<div class="quick-stat">
|
|
<span class="quick-stat-value" id="current-level">1</span>
|
|
<div class="quick-stat-label">Current Level</div>
|
|
</div>
|
|
<div class="quick-stat">
|
|
<span class="quick-stat-value" id="current-streak">0</span>
|
|
<div class="quick-stat-label">Day Streak</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="level-progress">
|
|
<div class="level-info">
|
|
<span><strong>Level Progress</strong></span>
|
|
<span id="level-xp">0 / 100 XP</span>
|
|
</div>
|
|
<div class="level-bar">
|
|
<div class="level-fill" id="level-fill" style="width: 0%"></div>
|
|
<div class="level-text" id="level-text">0%</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="achievements">
|
|
<h3>🏆 Achievements</h3>
|
|
<div class="achievement-grid" id="achievement-grid">
|
|
<!-- Achievements will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="export-section">
|
|
<h3>💾 Profile Data</h3>
|
|
<p>Manage your profile and statistics data</p>
|
|
<div class="action-buttons">
|
|
<button class="btn btn-secondary" onclick="viewDetailedStats()">📈 Detailed Statistics</button>
|
|
<button class="btn btn-secondary" onclick="exportProfile()">📤 Export Profile</button>
|
|
<button class="btn btn-secondary" onclick="importProfile()">📥 Import Profile</button>
|
|
<button class="btn btn-primary" onclick="saveProfile()">💾 Save Changes</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="action-buttons">
|
|
<a href="index.html" class="btn btn-secondary">🏠 Back to Main Menu</a>
|
|
<a href="player-stats.html" class="btn btn-secondary">📊 Detailed Stats</a>
|
|
</div>
|
|
|
|
<script src="src/features/stats/playerStats.js"></script>
|
|
<script>
|
|
class UserProfile {
|
|
constructor() {
|
|
this.playerStats = window.PlayerStats;
|
|
this.profile = this.loadProfile();
|
|
this.achievements = this.initializeAchievements();
|
|
this.init();
|
|
}
|
|
|
|
loadProfile() {
|
|
const saved = localStorage.getItem('userProfile');
|
|
if (saved) {
|
|
return JSON.parse(saved);
|
|
}
|
|
|
|
// Default profile
|
|
return {
|
|
username: 'Player',
|
|
bio: '',
|
|
joinDate: new Date().toISOString().split('T')[0],
|
|
theme: 'dark',
|
|
avatar: 'P'
|
|
};
|
|
}
|
|
|
|
saveProfile() {
|
|
this.profile.username = document.getElementById('username').value || 'Player';
|
|
this.profile.bio = document.getElementById('bio').value;
|
|
this.profile.theme = document.getElementById('theme').value;
|
|
this.profile.avatar = this.profile.username.charAt(0).toUpperCase();
|
|
|
|
localStorage.setItem('userProfile', JSON.stringify(this.profile));
|
|
|
|
// Show success message
|
|
this.showNotification('Profile saved successfully!', 'success');
|
|
this.updateDisplay();
|
|
}
|
|
|
|
init() {
|
|
this.updateDisplay();
|
|
this.updateStats();
|
|
this.renderAchievements();
|
|
|
|
// Update stats every 30 seconds
|
|
setInterval(() => this.updateStats(), 30000);
|
|
}
|
|
|
|
updateDisplay() {
|
|
document.getElementById('username').value = this.profile.username;
|
|
document.getElementById('bio').value = this.profile.bio;
|
|
document.getElementById('join-date').value = this.formatDate(this.profile.joinDate);
|
|
document.getElementById('theme').value = this.profile.theme;
|
|
document.getElementById('avatar-text').textContent = this.profile.avatar;
|
|
}
|
|
|
|
updateStats() {
|
|
if (!this.playerStats) return;
|
|
|
|
const stats = this.playerStats.getStats();
|
|
|
|
// Update quick stats
|
|
document.getElementById('total-watch-time').textContent = this.formatTime(stats.totalWatchTime);
|
|
document.getElementById('videos-watched').textContent = stats.videosWatched;
|
|
document.getElementById('current-streak').textContent = stats.streaks.current;
|
|
|
|
// Calculate level based on XP
|
|
const level = this.calculateLevel(stats.totalWatchTime);
|
|
document.getElementById('current-level').textContent = level;
|
|
|
|
// Update level progress
|
|
this.updateLevelProgress(stats.totalWatchTime);
|
|
|
|
// Update achievements
|
|
this.updateAchievements(stats);
|
|
}
|
|
|
|
calculateLevel(watchTimeSeconds) {
|
|
// Level up every hour of watch time
|
|
return Math.floor(watchTimeSeconds / 3600) + 1;
|
|
}
|
|
|
|
updateLevelProgress(watchTimeSeconds) {
|
|
const currentLevel = this.calculateLevel(watchTimeSeconds);
|
|
const currentLevelStart = (currentLevel - 1) * 3600;
|
|
const nextLevelStart = currentLevel * 3600;
|
|
const progressInLevel = watchTimeSeconds - currentLevelStart;
|
|
const levelDuration = nextLevelStart - currentLevelStart;
|
|
const progressPercent = (progressInLevel / levelDuration) * 100;
|
|
|
|
document.getElementById('level-fill').style.width = progressPercent + '%';
|
|
document.getElementById('level-text').textContent = Math.round(progressPercent) + '%';
|
|
document.getElementById('level-xp').textContent = `${Math.round(progressInLevel)} / ${levelDuration} seconds`;
|
|
}
|
|
|
|
initializeAchievements() {
|
|
return [
|
|
{
|
|
id: 'first-video',
|
|
icon: '🎬',
|
|
title: 'First Steps',
|
|
description: 'Watch your first video',
|
|
condition: stats => stats.videosWatched >= 1
|
|
},
|
|
{
|
|
id: 'early-bird',
|
|
icon: '🌅',
|
|
title: 'Early Bird',
|
|
description: 'Watch 10 videos',
|
|
condition: stats => stats.videosWatched >= 10
|
|
},
|
|
{
|
|
id: 'marathon',
|
|
icon: '🏃',
|
|
title: 'Marathon Viewer',
|
|
description: 'Watch for 2+ hours in one session',
|
|
condition: stats => stats.sessions.longest >= 7200
|
|
},
|
|
{
|
|
id: 'curator',
|
|
icon: '📚',
|
|
title: 'Playlist Curator',
|
|
description: 'Create 5 playlists',
|
|
condition: stats => stats.playlistsCreated >= 5
|
|
},
|
|
{
|
|
id: 'consistent',
|
|
icon: '🔥',
|
|
title: 'Consistency King',
|
|
description: 'Maintain a 7-day streak',
|
|
condition: stats => stats.streaks.longest >= 7
|
|
},
|
|
{
|
|
id: 'completionist',
|
|
icon: '✅',
|
|
title: 'Completionist',
|
|
description: 'Complete 50 videos (90%+)',
|
|
condition: stats => stats.videosCompleted >= 50
|
|
},
|
|
{
|
|
id: 'night-owl',
|
|
icon: '🦉',
|
|
title: 'Night Owl',
|
|
description: 'Watch after midnight',
|
|
condition: stats => stats.sessions.total > 0 // Simplified for demo
|
|
},
|
|
{
|
|
id: 'collector',
|
|
icon: '💎',
|
|
title: 'Video Collector',
|
|
description: 'Watch 100 different videos',
|
|
condition: stats => stats.videosWatched >= 100
|
|
}
|
|
];
|
|
}
|
|
|
|
updateAchievements(stats) {
|
|
this.achievements.forEach(achievement => {
|
|
const element = document.getElementById(`achievement-${achievement.id}`);
|
|
if (element) {
|
|
if (achievement.condition(stats)) {
|
|
element.classList.add('unlocked');
|
|
element.classList.remove('locked');
|
|
} else {
|
|
element.classList.add('locked');
|
|
element.classList.remove('unlocked');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
renderAchievements() {
|
|
const grid = document.getElementById('achievement-grid');
|
|
grid.innerHTML = '';
|
|
|
|
this.achievements.forEach(achievement => {
|
|
const element = document.createElement('div');
|
|
element.className = 'achievement locked';
|
|
element.id = `achievement-${achievement.id}`;
|
|
element.innerHTML = `
|
|
<span class="achievement-icon">${achievement.icon}</span>
|
|
<div class="achievement-title">${achievement.title}</div>
|
|
<div class="achievement-desc">${achievement.description}</div>
|
|
`;
|
|
grid.appendChild(element);
|
|
});
|
|
}
|
|
|
|
formatTime(seconds) {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
return `${hours}h ${minutes}m`;
|
|
}
|
|
|
|
formatDate(dateString) {
|
|
return new Date(dateString).toLocaleDateString();
|
|
}
|
|
|
|
showNotification(message, type = 'info') {
|
|
// Create notification element
|
|
const notification = document.createElement('div');
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: ${type === 'success' ? 'var(--success)' : 'var(--primary-color)'};
|
|
color: white;
|
|
padding: 15px 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
|
|
z-index: 1000;
|
|
font-weight: 600;
|
|
transform: translateX(100%);
|
|
transition: transform 0.3s ease;
|
|
`;
|
|
notification.textContent = message;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// Animate in
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(0)';
|
|
}, 100);
|
|
|
|
// Remove after 3 seconds
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.parentNode.removeChild(notification);
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
// Global functions for buttons
|
|
function saveProfile() {
|
|
if (window.userProfile) {
|
|
window.userProfile.saveProfile();
|
|
}
|
|
}
|
|
|
|
function viewDetailedStats() {
|
|
window.location.href = 'player-stats.html';
|
|
}
|
|
|
|
function exportProfile() {
|
|
const profile = localStorage.getItem('userProfile');
|
|
const stats = localStorage.getItem('playerStats');
|
|
|
|
const exportData = {
|
|
profile: profile ? JSON.parse(profile) : null,
|
|
stats: stats ? JSON.parse(stats) : null,
|
|
exportDate: new Date().toISOString()
|
|
};
|
|
|
|
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `webgame-profile-${new Date().toISOString().split('T')[0]}.json`;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
function importProfile() {
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.accept = '.json';
|
|
|
|
input.onchange = function(e) {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
try {
|
|
const data = JSON.parse(e.target.result);
|
|
|
|
if (data.profile) {
|
|
localStorage.setItem('userProfile', JSON.stringify(data.profile));
|
|
}
|
|
|
|
if (data.stats) {
|
|
localStorage.setItem('playerStats', JSON.stringify(data.stats));
|
|
}
|
|
|
|
// Reload page to apply changes
|
|
window.location.reload();
|
|
} catch (error) {
|
|
alert('Invalid profile file format');
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
};
|
|
|
|
input.click();
|
|
}
|
|
|
|
// Initialize when page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.userProfile = new UserProfile();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |