Implement Comprehensive User Profile System

PROFILE FOUNDATION:
- Created user-profile.html with professional card-based layout
- Customizable username, bio, join date, and theme preferences
- Dynamic avatar system with first letter of username display
- Persistent localStorage storage for all profile data

STATISTICS INTEGRATION:
- Seamlessly integrated with existing PlayerStats infrastructure
- Real-time stats display with auto-refresh every 30 seconds
- Quick stats overview: watch time, videos watched, level, streaks
- Level progression system with XP-based advancement and visual progress bars

ACHIEVEMENT SYSTEM:
- 8 unique achievements: First Steps, Early Bird, Marathon Viewer, etc.
- Dynamic unlocking based on actual player statistics and behaviors
- Visual feedback with unlocked achievements highlighted in success colors
- Variety of challenges: watch count, completion rate, streaks, playlists

PROFILE DATA MANAGEMENT:
- Export/import functionality for complete profile and statistics backup
- Save profile changes with success notification system
- Cross-page data synchronization with stats dashboard

NAVIGATION ENHANCEMENT:
- Added ' Profile' button to main navigation menu
- Cross-linking between Profile  Stats  Home pages
- Updated player-stats.html with profile navigation link
- Consistent UI/UX matching existing game design patterns

RESPONSIVE DESIGN:
- Mobile-optimized grid layouts and card-based information architecture
- Professional dark theme with glassmorphism styling effects
- Smooth animations and hover effects throughout interface
- Achievement grid with visual unlock states and progress tracking

The profile system transforms raw statistics into personalized gaming
identity with visual progress tracking, achievement motivation, and
comprehensive user customization options.
This commit is contained in:
dilgenfritz 2025-10-31 20:14:25 -05:00
parent c25eb3ecd4
commit 184842a8e8
4 changed files with 803 additions and 0 deletions

View File

@ -39,6 +39,38 @@
- ✅ **Storage System Enhancement**: Enhanced unified video library fallback mechanisms for reliable video access
- ✅ **Console Cleanup**: Removed excessive debug logging for cleaner user experience
- ✅ **Cross-Component Synchronization**: Fixed timing issues between main game and Porn Cinema video data access
- **✅ 🎬 Complete Playlist System Implementation (October 31, 2025)**
- ✅ **Playlist Management UI**: Professional sidebar with playlist tab, controls, and real-time display updates
- ✅ **Add/Remove/Play Operations**: Full CRUD operations with /✕/▶ buttons and direct playlist item interaction
- ✅ **Smart Playlist Features**: Shuffle, clear, duplicate prevention, and current video highlighting
- ✅ **Save/Load System**: Custom naming dialog, localStorage persistence, and JSON export/import functionality
- ✅ **Video Library Playlist Creation**: Multi-selection system with checkboxes, batch playlist creation, and immediate switching
- ✅ **Professional UI/UX**: Toast notifications, modal dialogs, responsive design, and consistent theming
- **✅ 📊 Player Statistics System (October 31, 2025)**
- ✅ **Real-time Watch Time Tracking**: Precise play/pause detection with cumulative timer that never resets
- ✅ **Comprehensive Viewing Metrics**: Videos watched, completed (90%+), skipped (<10%), and completion rates
- ✅ **Session Analytics**: Session counting, average length, longest session, and binge session detection (2+ hours)
- ✅ **Engagement Statistics**: Playlists created, videos added to playlists, and most watched video tracking
- ✅ **Achievement Metrics**: Days active, consecutive day streaks, and daily activity pattern analysis
- ✅ **Professional Stats Dashboard**: Beautiful card-based visualization with export/reset functionality and main navigation integration
- **✅ 👤 User Profile System (October 31, 2025)**
- ✅ **Profile Information Management**: Customizable username, bio, join date, theme preferences with localStorage persistence
- ✅ **Dynamic Avatar System**: Auto-generated avatar from username with visual profile identity
- ✅ **Statistics Integration**: Seamless integration with PlayerStats infrastructure for real-time data display
- ✅ **Level Progression System**: XP-based leveling with visual progress bars and level calculation
- ✅ **Achievement System**: 8 unique achievements with dynamic unlocking based on player statistics
- ✅ **Profile Data Management**: Export/import functionality for profile and statistics backup
- ✅ **Navigation Integration**: Added profile button to main menu with cross-page navigation links
- ✅ **Professional UI Design**: Card-based responsive layout with glassmorphism styling and notifications
- **🎬 User Profile System** *(📋 Planned - November 2025)*
- ✅ **Navigation Integration**: Added profile button to main menu with cross-page navigation links
- ✅ **Professional UI Design**: Card-based responsive layout with glassmorphism styling and notifications
- **🎬 Porn Cinema Player Enhancements** *(📋 Planned - November 2025)*
- 📋 **Pause Button Fix**: Debug and repair non-functioning pause button in video player
- 📋 **Autoplay Next Video**: Implement automatic progression to next playlist video on completion
- 📋 **Repeat/Loop Options**: Add single video repeat and full playlist loop functionality
- 📋 **Enhanced Playlist Controls**: Improved playlist navigation and playback options
- 📋 **Video Player Reliability**: Ensure all basic controls function correctly across all scenarios
- **🎬 Porn Cinema Media Player** *(✅ Major Progress - October 30-31, 2025)*
- ✅ **Complete Layout Implementation**: Professional two-column design with main content area and right sidebar
- ✅ **Header Navigation**: Slim, modern header with Home, Settings, Theater, and Fullscreen controls

View File

@ -96,6 +96,7 @@
<button id="start-btn" class="btn btn-primary">Start Game</button>
<button id="porn-cinema-btn" class="btn btn-primary">🎬 Porn Cinema</button>
<button id="player-stats-btn" class="btn btn-secondary">📊 Player Stats</button>
<button id="user-profile-btn" class="btn btn-secondary">👤 Profile</button>
<button id="manage-tasks-btn" class="btn btn-secondary">Manage Tasks</button>
<button id="manage-images-btn" class="btn btn-secondary">Manage Images</button>
<button id="manage-audio-btn" class="btn btn-secondary">🎵 Manage Audio</button>
@ -2990,6 +2991,16 @@
});
}
// Set up user profile button (only once)
const userProfileBtn = document.getElementById('user-profile-btn');
if (userProfileBtn && !userProfileBtn.hasAttribute('data-handler-attached')) {
userProfileBtn.setAttribute('data-handler-attached', 'true');
userProfileBtn.addEventListener('click', () => {
console.log('👤 Opening User Profile...');
window.location.href = 'user-profile.html';
});
}
// Set up clear overall XP button (debug tool)
const clearXpBtn = document.getElementById('clear-overall-xp-btn');
if (clearXpBtn && !clearXpBtn.hasAttribute('data-handler-attached')) {

View File

@ -97,6 +97,7 @@
<div class="navigation">
<a href="index.html" class="btn">🏠 Home</a>
<a href="user-profile.html" class="btn">👤 Profile</a>
<a href="porn-cinema.html" class="btn">🎬 Cinema</a>
<button id="reset-stats" class="btn" style="background: rgba(220, 53, 69, 0.3);">🗑️ Reset Stats</button>
<button id="export-stats" class="btn">📁 Export Data</button>

759
user-profile.html Normal file
View File

@ -0,0 +1,759 @@
<!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>