Implement Porn Cinema media player

- Add dedicated porn-cinema.html page with professional media player UI
- Create PornCinema class with full video player functionality
- Implement VideoLibrary class for grid/list video management
- Add comprehensive one-handed keyboard shortcuts
- Support playlist creation, management, and auto-advance
- Include quality selection and theater/fullscreen modes
- Add cinema-specific CSS styling with dark theme
- Integrate with existing video management system
- Add button on home screen to launch cinema mode

Features:
 Separate dedicated page
 Video thumbnails using existing patterns
 Playlist support from start
 Multiple quality options for performance
 One-handed keyboard shortcuts for easy control
 Professional media player controls
 Grid/list library views
 Theater mode and fullscreen support
This commit is contained in:
dilgenfritz 2025-10-30 16:43:03 -05:00
parent f703f428d5
commit c524f3bc46
5 changed files with 2174 additions and 0 deletions

View File

@ -92,6 +92,7 @@
<!-- Main Action Buttons -->
<div class="main-actions">
<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="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>
@ -2626,6 +2627,16 @@
});
}
// Set up porn cinema button (only once)
const pornCinemaBtn = document.getElementById('porn-cinema-btn');
if (pornCinemaBtn && !pornCinemaBtn.hasAttribute('data-handler-attached')) {
pornCinemaBtn.setAttribute('data-handler-attached', 'true');
pornCinemaBtn.addEventListener('click', () => {
console.log('🎬 Opening Porn Cinema...');
window.location.href = 'porn-cinema.html';
});
}
// Set up clear overall XP button (debug tool)
const clearXpBtn = document.getElementById('clear-overall-xp-btn');
if (clearXpBtn && !clearXpBtn.hasAttribute('data-handler-attached')) {

229
porn-cinema.html Normal file
View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: file: blob: http://localhost:* https:; connect-src 'self' http://localhost:* https: ws://localhost:*; img-src 'self' data: file: blob:; media-src 'self' data: file: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;">
<title>Porn Cinema - Gooner Training Academy</title>
<link rel="stylesheet" href="src/styles/styles.css">
<link rel="stylesheet" href="src/styles/porn-cinema.css">
<link href="https://fonts.googleapis.com/css2?family=Audiowide&display=swap" rel="stylesheet">
</head>
<body class="cinema-mode">
<!-- Loading Overlay -->
<div id="cinema-loading" class="cinema-loading">
<div class="loading-content">
<div class="loading-spinner"></div>
<h2>Loading Cinema...</h2>
<p>Preparing your video library...</p>
</div>
</div>
<!-- Cinema Header -->
<header class="cinema-header">
<div class="cinema-nav">
<button id="back-to-home" class="btn btn-secondary">← Back to Home</button>
<h1>🎬 Porn Cinema</h1>
<div class="cinema-controls">
<button id="theater-mode" class="btn btn-secondary" title="Theater Mode (Dim UI)">🎭</button>
<button id="fullscreen-toggle" class="btn btn-secondary" title="Fullscreen (F)"></button>
</div>
</div>
<!-- Keyboard Shortcuts Help -->
<div class="shortcuts-help" id="shortcuts-help">
<div class="shortcuts-content">
<h3>🎹 One-Handed Controls</h3>
<div class="shortcuts-grid">
<div class="shortcut-item"><kbd>Space</kbd> Play/Pause</div>
<div class="shortcut-item"><kbd></kbd><kbd></kbd> Seek ±10s</div>
<div class="shortcut-item"><kbd></kbd><kbd></kbd> Volume ±10%</div>
<div class="shortcut-item"><kbd>F</kbd> Fullscreen</div>
<div class="shortcut-item"><kbd>M</kbd> Mute/Unmute</div>
<div class="shortcut-item"><kbd>1-4</kbd> Quality</div>
<div class="shortcut-item"><kbd>Enter</kbd> Add to Playlist</div>
<div class="shortcut-item"><kbd>N</kbd> Next Video</div>
<div class="shortcut-item"><kbd>P</kbd> Previous Video</div>
<div class="shortcut-item"><kbd>S</kbd> Shuffle Playlist</div>
<div class="shortcut-item"><kbd>Escape</kbd> Exit Fullscreen</div>
<div class="shortcut-item"><kbd>?</kbd> Toggle This Help</div>
</div>
</div>
</div>
</header>
<!-- Main Cinema Content -->
<main class="cinema-main">
<!-- Video Player Section -->
<section class="video-player-section">
<div class="video-container" id="video-container">
<video id="main-video-player" class="main-video" preload="metadata">
<source id="video-source" src="" type="video/mp4">
Your browser does not support the video tag.
</video>
<!-- Video Overlay Controls -->
<div class="video-overlay" id="video-overlay">
<div class="video-title" id="video-title">Select a video to begin</div>
<div class="video-info" id="video-info"></div>
<!-- Play Button Overlay -->
<div class="play-overlay" id="play-overlay">
<button class="play-button-large" id="play-button-large"></button>
</div>
<!-- Loading Spinner -->
<div class="video-loading" id="video-loading" style="display: none;">
<div class="loading-spinner"></div>
<p>Loading video...</p>
</div>
</div>
<!-- Custom Video Controls -->
<div class="video-controls" id="video-controls">
<div class="progress-container">
<div class="progress-bar" id="progress-bar">
<div class="progress-filled" id="progress-filled"></div>
<div class="progress-thumb" id="progress-thumb"></div>
</div>
<div class="time-display">
<span id="current-time">0:00</span>
<span class="time-separator">/</span>
<span id="total-time">0:00</span>
</div>
</div>
<div class="controls-row">
<div class="controls-left">
<button id="play-pause-btn" class="control-btn"></button>
<button id="prev-video-btn" class="control-btn" title="Previous Video (P)"></button>
<button id="next-video-btn" class="control-btn" title="Next Video (N)"></button>
<div class="volume-control">
<button id="mute-btn" class="control-btn" title="Mute (M)">🔊</button>
<input type="range" id="volume-slider" class="volume-slider" min="0" max="100" value="70">
<span id="volume-percentage">70%</span>
</div>
</div>
<div class="controls-center">
<div class="quality-selector">
<select id="quality-select" class="quality-dropdown">
<option value="auto">Auto Quality</option>
<option value="1080p">1080p (1)</option>
<option value="720p">720p (2)</option>
<option value="480p">480p (3)</option>
<option value="360p">360p (4)</option>
</select>
</div>
<div class="speed-control">
<select id="speed-select" class="speed-dropdown">
<option value="0.5">0.5x</option>
<option value="0.75">0.75x</option>
<option value="1" selected>1x</option>
<option value="1.25">1.25x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
</select>
</div>
</div>
<div class="controls-right">
<button id="add-to-playlist-btn" class="control-btn" title="Add to Playlist (Enter)"></button>
<button id="theater-mode-btn" class="control-btn" title="Theater Mode">🎭</button>
<button id="fullscreen-btn" class="control-btn" title="Fullscreen (F)"></button>
</div>
</div>
</div>
</div>
</section>
<!-- Playlist Section -->
<section class="playlist-section" id="playlist-section">
<div class="playlist-header">
<h3>📝 Current Playlist</h3>
<div class="playlist-controls">
<button id="shuffle-playlist" class="btn btn-mini" title="Shuffle (S)">🔀</button>
<button id="clear-playlist" class="btn btn-mini btn-danger">🗑️ Clear</button>
<button id="save-playlist" class="btn btn-mini">💾 Save</button>
<button id="load-playlist" class="btn btn-mini">📁 Load</button>
</div>
</div>
<div class="playlist-content" id="playlist-content">
<div class="playlist-empty">
<p>Playlist is empty. Add videos by clicking or pressing Enter while a video is selected.</p>
</div>
</div>
</section>
<!-- Video Library Section -->
<section class="video-library-section">
<div class="library-header">
<h3>📁 Video Library</h3>
<div class="library-controls">
<div class="view-toggle">
<button id="grid-view-btn" class="view-btn active" title="Grid View"></button>
<button id="list-view-btn" class="view-btn" title="List View"></button>
</div>
<div class="sort-controls">
<select id="sort-select" class="sort-dropdown">
<option value="name">Sort by Name</option>
<option value="date">Sort by Date</option>
<option value="duration">Sort by Duration</option>
<option value="size">Sort by Size</option>
</select>
<button id="sort-direction" class="btn btn-mini" title="Sort Direction">↕️</button>
</div>
<div class="search-controls">
<input type="text" id="library-search" class="search-input" placeholder="Search videos...">
<button id="refresh-library" class="btn btn-mini" title="Refresh Library">🔄</button>
</div>
</div>
</div>
<div class="library-content" id="library-content">
<div class="library-loading">
<div class="loading-spinner"></div>
<p>Loading video library...</p>
</div>
</div>
</section>
</main>
<!-- Scripts -->
<script src="src/utils/desktop-file-manager.js"></script>
<script src="src/features/media/videoLibrary.js"></script>
<script src="src/features/media/pornCinema.js"></script>
<script>
// Initialize cinema when page loads
document.addEventListener('DOMContentLoaded', function() {
console.log('🎬 Initializing Porn Cinema...');
// Initialize the cinema
window.pornCinema = new PornCinema();
window.pornCinema.initialize();
// Hide loading overlay
setTimeout(() => {
document.getElementById('cinema-loading').style.display = 'none';
}, 1500);
});
// Back to home functionality
document.getElementById('back-to-home').addEventListener('click', () => {
if (confirm('Return to home screen? Current playback will stop.')) {
window.location.href = 'index.html';
}
});
// Keyboard shortcut to toggle help
document.addEventListener('keydown', (e) => {
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
const help = document.getElementById('shortcuts-help');
help.style.display = help.style.display === 'none' ? 'block' : 'none';
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,738 @@
/**
* Porn Cinema Media Player
* Full-featured video player with playlist support and one-handed controls
*/
class PornCinema {
constructor() {
this.currentVideo = null;
this.currentVideoElement = null;
this.isPlaying = false;
this.isFullscreen = false;
this.theaterMode = false;
this.volume = 0.7;
this.playbackRate = 1.0;
this.currentQuality = 'auto';
// Playlist
this.playlist = [];
this.currentPlaylistIndex = -1;
this.shuffleMode = false;
this.originalPlaylistOrder = [];
// Video library
this.videoLibrary = null;
// Control elements
this.controls = {};
// Keyboard shortcuts
this.shortcuts = {
' ': () => this.togglePlayPause(),
'ArrowLeft': () => this.seek(-10),
'ArrowRight': () => this.seek(10),
'ArrowUp': () => this.adjustVolume(0.1),
'ArrowDown': () => this.adjustVolume(-0.1),
'f': () => this.toggleFullscreen(),
'F': () => this.toggleFullscreen(),
'm': () => this.toggleMute(),
'M': () => this.toggleMute(),
'n': () => this.nextVideo(),
'N': () => this.nextVideo(),
'p': () => this.previousVideo(),
'P': () => this.previousVideo(),
's': () => this.shufflePlaylist(),
'S': () => this.shufflePlaylist(),
'1': () => this.setQuality('1080p'),
'2': () => this.setQuality('720p'),
'3': () => this.setQuality('480p'),
'4': () => this.setQuality('360p'),
'Enter': () => this.addCurrentToPlaylist(),
'Escape': () => this.exitFullscreen()
};
this.initializeElements();
this.attachEventListeners();
}
async initialize() {
console.log('🎬 Initializing Porn Cinema...');
// Initialize video library
this.videoLibrary = new VideoLibrary(this);
await this.videoLibrary.loadVideoLibrary();
// Set initial volume
this.setVolume(this.volume);
console.log('✅ Porn Cinema initialized');
}
initializeElements() {
// Video elements
this.videoContainer = document.getElementById('video-container');
this.videoElement = document.getElementById('main-video-player');
this.videoSource = document.getElementById('video-source');
this.videoOverlay = document.getElementById('video-overlay');
this.videoTitle = document.getElementById('video-title');
this.videoInfo = document.getElementById('video-info');
this.playOverlay = document.getElementById('play-overlay');
this.playButtonLarge = document.getElementById('play-button-large');
this.videoLoading = document.getElementById('video-loading');
// Control elements
this.controls.container = document.getElementById('video-controls');
this.controls.playPause = document.getElementById('play-pause-btn');
this.controls.prevVideo = document.getElementById('prev-video-btn');
this.controls.nextVideo = document.getElementById('next-video-btn');
this.controls.mute = document.getElementById('mute-btn');
this.controls.volume = document.getElementById('volume-slider');
this.controls.volumePercentage = document.getElementById('volume-percentage');
this.controls.progressBar = document.getElementById('progress-bar');
this.controls.progressFilled = document.getElementById('progress-filled');
this.controls.progressThumb = document.getElementById('progress-thumb');
this.controls.currentTime = document.getElementById('current-time');
this.controls.totalTime = document.getElementById('total-time');
this.controls.quality = document.getElementById('quality-select');
this.controls.speed = document.getElementById('speed-select');
this.controls.addToPlaylist = document.getElementById('add-to-playlist-btn');
this.controls.theater = document.getElementById('theater-mode-btn');
this.controls.fullscreen = document.getElementById('fullscreen-btn');
// Playlist elements
this.playlistContent = document.getElementById('playlist-content');
this.shuffleBtn = document.getElementById('shuffle-playlist');
this.clearPlaylistBtn = document.getElementById('clear-playlist');
this.savePlaylistBtn = document.getElementById('save-playlist');
this.loadPlaylistBtn = document.getElementById('load-playlist');
}
attachEventListeners() {
// Video events
this.videoElement.addEventListener('loadstart', () => this.onLoadStart());
this.videoElement.addEventListener('loadedmetadata', () => this.onLoadedMetadata());
this.videoElement.addEventListener('loadeddata', () => this.onLoadedData());
this.videoElement.addEventListener('canplay', () => this.onCanPlay());
this.videoElement.addEventListener('play', () => this.onPlay());
this.videoElement.addEventListener('pause', () => this.onPause());
this.videoElement.addEventListener('ended', () => this.onEnded());
this.videoElement.addEventListener('timeupdate', () => this.onTimeUpdate());
this.videoElement.addEventListener('volumechange', () => this.onVolumeChange());
this.videoElement.addEventListener('error', (e) => this.onError(e));
// Large play button
this.playButtonLarge.addEventListener('click', () => this.togglePlayPause());
// Control buttons
this.controls.playPause.addEventListener('click', () => this.togglePlayPause());
this.controls.prevVideo.addEventListener('click', () => this.previousVideo());
this.controls.nextVideo.addEventListener('click', () => this.nextVideo());
this.controls.mute.addEventListener('click', () => this.toggleMute());
this.controls.addToPlaylist.addEventListener('click', () => this.addCurrentToPlaylist());
this.controls.theater.addEventListener('click', () => this.toggleTheaterMode());
this.controls.fullscreen.addEventListener('click', () => this.toggleFullscreen());
// Volume control
this.controls.volume.addEventListener('input', (e) => this.setVolume(e.target.value / 100));
// Progress bar
this.controls.progressBar.addEventListener('click', (e) => this.seekToPosition(e));
this.controls.progressBar.addEventListener('mousedown', () => this.startSeeking());
// Quality and speed controls
this.controls.quality.addEventListener('change', (e) => this.setQuality(e.target.value));
this.controls.speed.addEventListener('change', (e) => this.setPlaybackRate(e.target.value));
// Playlist controls
this.shuffleBtn.addEventListener('click', () => this.shufflePlaylist());
this.clearPlaylistBtn.addEventListener('click', () => this.clearPlaylist());
this.savePlaylistBtn.addEventListener('click', () => this.savePlaylist());
this.loadPlaylistBtn.addEventListener('click', () => this.loadPlaylist());
// Keyboard shortcuts
document.addEventListener('keydown', (e) => this.handleKeyboardShortcut(e));
// Fullscreen events
document.addEventListener('fullscreenchange', () => this.onFullscreenChange());
document.addEventListener('webkitfullscreenchange', () => this.onFullscreenChange());
document.addEventListener('mozfullscreenchange', () => this.onFullscreenChange());
// Theater mode buttons
document.getElementById('theater-mode').addEventListener('click', () => this.toggleTheaterMode());
document.getElementById('fullscreen-toggle').addEventListener('click', () => this.toggleFullscreen());
// Video container hover for controls
this.videoContainer.addEventListener('mouseenter', () => this.showControls());
this.videoContainer.addEventListener('mouseleave', () => this.hideControls());
this.videoContainer.addEventListener('mousemove', () => this.showControls());
}
// Video loading and playback methods
selectVideo(video) {
this.currentVideo = video;
this.updateVideoInfo();
this.updateVideoTitle();
}
async playVideo(video) {
try {
console.log(`🎬 Playing video: ${video.name}`);
this.currentVideo = video;
this.showLoading();
// Update video source
this.videoSource.src = video.path;
this.videoSource.type = `video/${video.format || 'mp4'}`;
// Load and play
this.videoElement.load();
// Update UI
this.updateVideoInfo();
this.updateVideoTitle();
this.updatePlaylistSelection();
} catch (error) {
console.error('Error playing video:', error);
this.showError('Failed to load video');
}
}
togglePlayPause() {
if (!this.videoElement.src) {
// No video loaded, try to play first video from library or playlist
this.playFirstAvailable();
return;
}
if (this.videoElement.paused) {
this.play();
} else {
this.pause();
}
}
play() {
const playPromise = this.videoElement.play();
if (playPromise !== undefined) {
playPromise.then(() => {
this.isPlaying = true;
this.updatePlayButton();
this.hidePlayOverlay();
}).catch(error => {
console.error('Error playing video:', error);
this.showError('Failed to play video');
});
}
}
pause() {
this.videoElement.pause();
this.isPlaying = false;
this.updatePlayButton();
this.showPlayOverlay();
}
seek(seconds) {
if (this.videoElement.duration) {
const newTime = Math.max(0, Math.min(this.videoElement.duration, this.videoElement.currentTime + seconds));
this.videoElement.currentTime = newTime;
}
}
seekToPosition(event) {
if (this.videoElement.duration) {
const rect = this.controls.progressBar.getBoundingClientRect();
const pos = (event.clientX - rect.left) / rect.width;
this.videoElement.currentTime = pos * this.videoElement.duration;
}
}
// Volume and audio controls
setVolume(volume) {
this.volume = Math.max(0, Math.min(1, volume));
this.videoElement.volume = this.volume;
this.controls.volume.value = this.volume * 100;
this.controls.volumePercentage.textContent = Math.round(this.volume * 100) + '%';
this.updateMuteButton();
}
adjustVolume(delta) {
this.setVolume(this.volume + delta);
}
toggleMute() {
if (this.videoElement.muted) {
this.videoElement.muted = false;
this.setVolume(this.volume > 0 ? this.volume : 0.7);
} else {
this.videoElement.muted = true;
}
this.updateMuteButton();
}
updateMuteButton() {
const isMuted = this.videoElement.muted || this.volume === 0;
this.controls.mute.textContent = isMuted ? '🔇' : '🔊';
}
// Quality and playback controls
setQuality(quality) {
this.currentQuality = quality;
this.controls.quality.value = quality;
// In a real implementation, you would switch video sources here
// For now, we'll just update the UI
console.log(`Quality set to: ${quality}`);
}
setPlaybackRate(rate) {
this.playbackRate = parseFloat(rate);
this.videoElement.playbackRate = this.playbackRate;
this.controls.speed.value = rate;
}
// Fullscreen and theater mode
toggleFullscreen() {
if (!this.isFullscreen) {
this.enterFullscreen();
} else {
this.exitFullscreen();
}
}
enterFullscreen() {
const element = this.videoContainer;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
}
}
exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
}
}
toggleTheaterMode() {
this.theaterMode = !this.theaterMode;
document.body.classList.toggle('theater-mode', this.theaterMode);
const button = document.getElementById('theater-mode');
if (button) {
button.textContent = this.theaterMode ? '💡' : '🎭';
button.title = this.theaterMode ? 'Exit Theater Mode' : 'Theater Mode (Dim UI)';
}
}
// Playlist management
addToPlaylist(video) {
if (!this.playlist.find(v => v.path === video.path)) {
this.playlist.push({...video});
this.updatePlaylistDisplay();
console.log(` Added to playlist: ${video.name}`);
}
}
addCurrentToPlaylist() {
if (this.currentVideo) {
this.addToPlaylist(this.currentVideo);
} else if (this.videoLibrary && this.videoLibrary.getSelectedVideo()) {
this.addToPlaylist(this.videoLibrary.getSelectedVideo());
}
}
removeFromPlaylist(index) {
if (index >= 0 && index < this.playlist.length) {
const removed = this.playlist.splice(index, 1)[0];
console.log(` Removed from playlist: ${removed.name}`);
// Adjust current index if necessary
if (this.currentPlaylistIndex > index) {
this.currentPlaylistIndex--;
} else if (this.currentPlaylistIndex === index) {
this.currentPlaylistIndex = -1;
}
this.updatePlaylistDisplay();
}
}
clearPlaylist() {
if (confirm('Clear entire playlist?')) {
this.playlist = [];
this.currentPlaylistIndex = -1;
this.updatePlaylistDisplay();
console.log('🗑️ Playlist cleared');
}
}
shufflePlaylist() {
if (this.playlist.length <= 1) return;
this.shuffleMode = !this.shuffleMode;
if (this.shuffleMode) {
// Save original order
this.originalPlaylistOrder = [...this.playlist];
// Shuffle playlist
for (let i = this.playlist.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this.playlist[i], this.playlist[j]] = [this.playlist[j], this.playlist[i]];
}
this.shuffleBtn.textContent = '🔀';
this.shuffleBtn.title = 'Disable Shuffle';
console.log('🔀 Playlist shuffled');
} else {
// Restore original order
this.playlist = [...this.originalPlaylistOrder];
this.shuffleBtn.textContent = '🔀';
this.shuffleBtn.title = 'Shuffle Playlist';
console.log('📝 Playlist order restored');
}
this.currentPlaylistIndex = this.playlist.findIndex(v =>
this.currentVideo && v.path === this.currentVideo.path
);
this.updatePlaylistDisplay();
}
nextVideo() {
if (this.playlist.length === 0) return;
let nextIndex = this.currentPlaylistIndex + 1;
if (nextIndex >= this.playlist.length) {
nextIndex = 0; // Loop back to start
}
this.playVideoFromPlaylist(nextIndex);
}
previousVideo() {
if (this.playlist.length === 0) return;
let prevIndex = this.currentPlaylistIndex - 1;
if (prevIndex < 0) {
prevIndex = this.playlist.length - 1; // Loop to end
}
this.playVideoFromPlaylist(prevIndex);
}
playVideoFromPlaylist(index) {
if (index >= 0 && index < this.playlist.length) {
this.currentPlaylistIndex = index;
this.playVideo(this.playlist[index]);
}
}
updatePlaylistDisplay() {
if (this.playlist.length === 0) {
this.playlistContent.innerHTML = `
<div class="playlist-empty">
<p>Playlist is empty. Add videos by clicking or pressing Enter while a video is selected.</p>
</div>
`;
return;
}
this.playlistContent.innerHTML = this.playlist.map((video, index) => `
<div class="playlist-item ${index === this.currentPlaylistIndex ? 'current' : ''}"
data-index="${index}">
<div class="playlist-thumbnail">
${video.thumbnail ?
`<img src="${video.thumbnail}" alt="${video.name}" class="playlist-thumbnail">` :
`<div class="playlist-thumbnail" style="display: flex; align-items: center; justify-content: center; font-size: 1.2rem;">🎬</div>`
}
</div>
<div class="playlist-details">
<div class="playlist-title">${video.name}</div>
<div class="playlist-duration">${this.formatDuration(video.duration)}</div>
</div>
<div class="playlist-actions">
<button class="btn btn-mini play-playlist-item" title="Play"></button>
<button class="btn btn-mini remove-playlist-item" title="Remove"></button>
</div>
</div>
`).join('');
// Attach playlist item events
this.attachPlaylistEvents();
}
attachPlaylistEvents() {
const playlistItems = this.playlistContent.querySelectorAll('.playlist-item');
playlistItems.forEach((item, index) => {
item.addEventListener('click', (e) => {
if (e.target.closest('.playlist-actions')) return;
this.playVideoFromPlaylist(index);
});
});
const playButtons = this.playlistContent.querySelectorAll('.play-playlist-item');
playButtons.forEach((button, index) => {
button.addEventListener('click', (e) => {
e.stopPropagation();
this.playVideoFromPlaylist(index);
});
});
const removeButtons = this.playlistContent.querySelectorAll('.remove-playlist-item');
removeButtons.forEach((button, index) => {
button.addEventListener('click', (e) => {
e.stopPropagation();
this.removeFromPlaylist(index);
});
});
}
updatePlaylistSelection() {
if (this.currentVideo) {
this.currentPlaylistIndex = this.playlist.findIndex(v => v.path === this.currentVideo.path);
this.updatePlaylistDisplay();
}
}
// Keyboard shortcuts
handleKeyboardShortcut(event) {
// Don't handle shortcuts if typing in an input
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
return;
}
const key = event.key;
if (this.shortcuts[key]) {
event.preventDefault();
this.shortcuts[key]();
}
}
// Video event handlers
onLoadStart() {
this.showLoading();
}
onLoadedMetadata() {
this.updateTimeDisplay();
this.hideLoading();
}
onLoadedData() {
this.hideLoading();
}
onCanPlay() {
this.hideLoading();
this.updatePlayButton();
}
onPlay() {
this.isPlaying = true;
this.updatePlayButton();
this.hidePlayOverlay();
}
onPause() {
this.isPlaying = false;
this.updatePlayButton();
this.showPlayOverlay();
}
onEnded() {
this.isPlaying = false;
this.updatePlayButton();
this.showPlayOverlay();
// Auto-play next video in playlist
if (this.playlist.length > 0 && this.currentPlaylistIndex >= 0) {
setTimeout(() => this.nextVideo(), 1000);
}
}
onTimeUpdate() {
this.updateProgressBar();
this.updateTimeDisplay();
}
onVolumeChange() {
this.updateMuteButton();
}
onError(event) {
console.error('Video error:', event);
this.hideLoading();
this.showError('Error loading video');
}
onFullscreenChange() {
this.isFullscreen = !!(document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement);
this.controls.fullscreen.textContent = this.isFullscreen ? '🗗' : '⛶';
this.controls.fullscreen.title = this.isFullscreen ? 'Exit Fullscreen (Esc)' : 'Fullscreen (F)';
}
// UI update methods
updateVideoInfo() {
if (this.currentVideo) {
const info = `${this.currentVideo.resolution || 'Unknown'}${this.formatFileSize(this.currentVideo.size)}`;
this.videoInfo.textContent = info;
} else {
this.videoInfo.textContent = '';
}
}
updateVideoTitle() {
if (this.currentVideo) {
this.videoTitle.textContent = this.currentVideo.name;
} else {
this.videoTitle.textContent = 'Select a video to begin';
}
}
updatePlayButton() {
const playText = this.isPlaying ? '⏸' : '▶';
this.controls.playPause.textContent = playText;
this.playButtonLarge.textContent = this.isPlaying ? '⏸' : '▶';
}
updateProgressBar() {
if (this.videoElement.duration) {
const progress = (this.videoElement.currentTime / this.videoElement.duration) * 100;
this.controls.progressFilled.style.width = progress + '%';
this.controls.progressThumb.style.left = progress + '%';
}
}
updateTimeDisplay() {
this.controls.currentTime.textContent = this.formatDuration(this.videoElement.currentTime);
this.controls.totalTime.textContent = this.formatDuration(this.videoElement.duration);
}
// UI state methods
showLoading() {
this.videoLoading.style.display = 'block';
}
hideLoading() {
this.videoLoading.style.display = 'none';
}
showPlayOverlay() {
this.playOverlay.style.display = 'block';
}
hidePlayOverlay() {
this.playOverlay.style.display = 'none';
}
showControls() {
this.controls.container.classList.add('visible');
}
hideControls() {
if (!this.videoElement.paused) {
this.controls.container.classList.remove('visible');
}
}
showError(message) {
this.hideLoading();
this.videoTitle.textContent = message;
this.videoInfo.textContent = '';
}
// Utility methods
playFirstAvailable() {
if (this.playlist.length > 0) {
this.playVideoFromPlaylist(0);
} else if (this.videoLibrary && this.videoLibrary.getFilteredVideos().length > 0) {
this.playVideo(this.videoLibrary.getFilteredVideos()[0]);
}
}
startSeeking() {
// Add mouse move listener for seeking
// This would be implemented for smooth seeking while dragging
}
savePlaylist() {
if (this.playlist.length === 0) {
alert('Playlist is empty');
return;
}
const playlistData = {
name: prompt('Enter playlist name:') || 'Untitled Playlist',
videos: this.playlist,
created: new Date().toISOString()
};
// Save to localStorage (in a real app, you might save to a file)
const savedPlaylists = JSON.parse(localStorage.getItem('pornCinemaPlaylists') || '[]');
savedPlaylists.push(playlistData);
localStorage.setItem('pornCinemaPlaylists', JSON.stringify(savedPlaylists));
console.log(`💾 Playlist saved: ${playlistData.name}`);
}
loadPlaylist() {
const savedPlaylists = JSON.parse(localStorage.getItem('pornCinemaPlaylists') || '[]');
if (savedPlaylists.length === 0) {
alert('No saved playlists found');
return;
}
// Create a simple selection dialog
const playlistNames = savedPlaylists.map((p, i) => `${i + 1}. ${p.name}`).join('\n');
const selection = prompt(`Select playlist to load:\n${playlistNames}\n\nEnter number:`);
if (selection) {
const index = parseInt(selection) - 1;
if (index >= 0 && index < savedPlaylists.length) {
this.playlist = savedPlaylists[index].videos;
this.currentPlaylistIndex = -1;
this.updatePlaylistDisplay();
console.log(`📁 Playlist loaded: ${savedPlaylists[index].name}`);
}
}
}
formatDuration(seconds) {
if (!seconds || isNaN(seconds)) return '0:00';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
}
formatFileSize(bytes) {
if (!bytes || bytes === 0) return '0 B';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
}
}

View File

@ -0,0 +1,402 @@
/**
* Video Library Manager for Porn Cinema
* Handles video library display, search, and organization
*/
class VideoLibrary {
constructor(pornCinema) {
this.pornCinema = pornCinema;
this.videos = [];
this.filteredVideos = [];
this.currentView = 'grid'; // 'grid' or 'list'
this.currentSort = 'name';
this.sortDirection = 'asc';
this.searchQuery = '';
this.selectedVideo = null;
this.initializeElements();
this.attachEventListeners();
}
initializeElements() {
// View toggle buttons
this.gridViewBtn = document.getElementById('grid-view-btn');
this.listViewBtn = document.getElementById('list-view-btn');
// Sort controls
this.sortSelect = document.getElementById('sort-select');
this.sortDirectionBtn = document.getElementById('sort-direction');
// Search controls
this.searchInput = document.getElementById('library-search');
this.refreshBtn = document.getElementById('refresh-library');
// Content container
this.libraryContent = document.getElementById('library-content');
}
attachEventListeners() {
// View toggle
this.gridViewBtn.addEventListener('click', () => this.setView('grid'));
this.listViewBtn.addEventListener('click', () => this.setView('list'));
// Sort controls
this.sortSelect.addEventListener('change', (e) => this.setSortBy(e.target.value));
this.sortDirectionBtn.addEventListener('click', () => this.toggleSortDirection());
// Search
this.searchInput.addEventListener('input', (e) => this.setSearchQuery(e.target.value));
this.refreshBtn.addEventListener('click', () => this.refreshLibrary());
}
async loadVideoLibrary() {
try {
console.log('📁 Loading video library...');
// Check if desktop file manager is available
if (!window.desktopFileManager) {
console.error('Desktop file manager not available');
this.displayEmptyLibrary('Desktop file manager not available');
return;
}
// Get video files from the desktop file manager
const videoData = await window.desktopFileManager.getVideoList();
if (!videoData || !videoData.videos) {
console.log('No videos found');
this.displayEmptyLibrary('No videos found');
return;
}
// Process video data
this.videos = videoData.videos.map(video => ({
name: video.name,
path: video.path,
size: video.size || 0,
duration: video.duration || 0,
thumbnail: video.thumbnail || null,
resolution: video.resolution || 'Unknown',
format: video.format || 'mp4',
bitrate: video.bitrate || 0,
dateAdded: video.dateAdded || new Date().toISOString(),
qualities: this.detectVideoQualities(video)
}));
console.log(`📁 Loaded ${this.videos.length} videos`);
// Apply current filters and display
this.applyFiltersAndSort();
this.displayLibrary();
} catch (error) {
console.error('Error loading video library:', error);
this.displayEmptyLibrary('Error loading video library');
}
}
detectVideoQualities(video) {
// Detect available qualities for a video
// This is a placeholder - in a real implementation, you might
// check for multiple files with quality indicators in the name
const qualities = ['auto'];
if (video.resolution) {
const resolution = video.resolution.toLowerCase();
if (resolution.includes('1080') || resolution.includes('1920')) {
qualities.push('1080p');
}
if (resolution.includes('720') || resolution.includes('1280')) {
qualities.push('720p');
}
if (resolution.includes('480') || resolution.includes('854')) {
qualities.push('480p');
}
if (resolution.includes('360') || resolution.includes('640')) {
qualities.push('360p');
}
} else {
// Default to common qualities if resolution unknown
qualities.push('1080p', '720p', '480p', '360p');
}
return qualities;
}
setView(view) {
this.currentView = view;
// Update button states
this.gridViewBtn.classList.toggle('active', view === 'grid');
this.listViewBtn.classList.toggle('active', view === 'list');
// Re-display library
this.displayLibrary();
}
setSortBy(sortBy) {
this.currentSort = sortBy;
this.applyFiltersAndSort();
this.displayLibrary();
}
toggleSortDirection() {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
this.sortDirectionBtn.textContent = this.sortDirection === 'asc' ? '↑' : '↓';
this.applyFiltersAndSort();
this.displayLibrary();
}
setSearchQuery(query) {
this.searchQuery = query.toLowerCase();
this.applyFiltersAndSort();
this.displayLibrary();
}
applyFiltersAndSort() {
// Apply search filter
this.filteredVideos = this.videos.filter(video =>
video.name.toLowerCase().includes(this.searchQuery)
);
// Apply sorting
this.filteredVideos.sort((a, b) => {
let aValue, bValue;
switch (this.currentSort) {
case 'name':
aValue = a.name.toLowerCase();
bValue = b.name.toLowerCase();
break;
case 'date':
aValue = new Date(a.dateAdded);
bValue = new Date(b.dateAdded);
break;
case 'duration':
aValue = a.duration;
bValue = b.duration;
break;
case 'size':
aValue = a.size;
bValue = b.size;
break;
default:
aValue = a.name.toLowerCase();
bValue = b.name.toLowerCase();
}
if (aValue < bValue) return this.sortDirection === 'asc' ? -1 : 1;
if (aValue > bValue) return this.sortDirection === 'asc' ? 1 : -1;
return 0;
});
}
displayLibrary() {
if (this.filteredVideos.length === 0) {
this.displayEmptyLibrary('No videos match your search');
return;
}
const containerClass = this.currentView === 'grid' ? 'library-grid' : 'library-list';
this.libraryContent.innerHTML = `
<div class="${containerClass}">
${this.filteredVideos.map(video => this.createVideoElement(video)).join('')}
</div>
`;
// Attach click events to video elements
this.attachVideoEvents();
}
createVideoElement(video) {
const duration = this.formatDuration(video.duration);
const fileSize = this.formatFileSize(video.size);
if (this.currentView === 'grid') {
return `
<div class="video-card" data-video-path="${video.path}">
<div class="video-thumbnail">
${video.thumbnail ?
`<img src="${video.thumbnail}" alt="${video.name}">` :
'🎬'
}
<div class="video-duration">${duration}</div>
</div>
<div class="video-details">
<div class="video-name" title="${video.name}">${video.name}</div>
<div class="video-meta">
<span>${video.resolution}</span>
<span>${fileSize}</span>
</div>
</div>
<div class="video-actions">
<button class="btn btn-mini play-video" title="Play"></button>
<button class="btn btn-mini add-to-playlist" title="Add to Playlist"></button>
</div>
</div>
`;
} else {
return `
<div class="video-list-item" data-video-path="${video.path}">
<div class="list-thumbnail">
${video.thumbnail ?
`<img src="${video.thumbnail}" alt="${video.name}" class="list-thumbnail">` :
`<div class="list-thumbnail" style="display: flex; align-items: center; justify-content: center; font-size: 1.5rem;">🎬</div>`
}
</div>
<div class="list-details">
<div class="list-name">${video.name}</div>
<div class="list-meta">${video.resolution}</div>
<div class="list-meta">${duration}</div>
<div class="list-meta">${fileSize}</div>
</div>
<div class="video-actions">
<button class="btn btn-mini play-video" title="Play"></button>
<button class="btn btn-mini add-to-playlist" title="Add to Playlist"></button>
</div>
</div>
`;
}
}
attachVideoEvents() {
// Video card/item click events
const videoElements = this.libraryContent.querySelectorAll('.video-card, .video-list-item');
videoElements.forEach(element => {
element.addEventListener('click', (e) => {
if (e.target.closest('.video-actions')) return; // Don't trigger on action buttons
const videoPath = element.dataset.videoPath;
this.selectVideo(videoPath);
});
});
// Play button events
const playButtons = this.libraryContent.querySelectorAll('.play-video');
playButtons.forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
const videoPath = button.closest('[data-video-path]').dataset.videoPath;
this.playVideo(videoPath);
});
});
// Add to playlist button events
const playlistButtons = this.libraryContent.querySelectorAll('.add-to-playlist');
playlistButtons.forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
const videoPath = button.closest('[data-video-path]').dataset.videoPath;
this.addToPlaylist(videoPath);
});
});
}
selectVideo(videoPath) {
// Remove previous selection
const previousSelected = this.libraryContent.querySelector('.selected');
if (previousSelected) {
previousSelected.classList.remove('selected');
}
// Add selection to clicked video
const videoElement = this.libraryContent.querySelector(`[data-video-path="${videoPath}"]`);
if (videoElement) {
videoElement.classList.add('selected');
}
// Update selected video
this.selectedVideo = this.videos.find(video => video.path === videoPath);
// Notify porn cinema
if (this.pornCinema) {
this.pornCinema.selectVideo(this.selectedVideo);
}
}
playVideo(videoPath) {
const video = this.videos.find(video => video.path === videoPath);
if (video && this.pornCinema) {
this.pornCinema.playVideo(video);
}
}
addToPlaylist(videoPath) {
const video = this.videos.find(video => video.path === videoPath);
if (video && this.pornCinema) {
this.pornCinema.addToPlaylist(video);
}
}
refreshLibrary() {
console.log('🔄 Refreshing video library...');
this.videos = [];
this.filteredVideos = [];
this.selectedVideo = null;
// Show loading state
this.libraryContent.innerHTML = `
<div class="library-loading">
<div class="loading-spinner"></div>
<p>Refreshing video library...</p>
</div>
`;
// Reload library
setTimeout(() => {
this.loadVideoLibrary();
}, 500);
}
displayEmptyLibrary(message) {
this.libraryContent.innerHTML = `
<div class="library-loading">
<p>${message}</p>
<button class="btn btn-secondary" onclick="window.location.href='index.html'">
📂 Manage Videos
</button>
</div>
`;
}
// Utility functions
formatDuration(seconds) {
if (!seconds || seconds === 0) return '--:--';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
}
formatFileSize(bytes) {
if (!bytes || bytes === 0) return '--';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
}
// Public methods for external access
getSelectedVideo() {
return this.selectedVideo;
}
getVideoByPath(path) {
return this.videos.find(video => video.path === path);
}
getAllVideos() {
return this.videos;
}
getFilteredVideos() {
return this.filteredVideos;
}
}

794
src/styles/porn-cinema.css Normal file
View File

@ -0,0 +1,794 @@
/* Porn Cinema Styles */
/* ===== CINEMA LAYOUT ===== */
.cinema-mode {
background: #000;
color: #fff;
overflow-x: hidden;
}
.cinema-loading {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
}
.cinema-header {
background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
border-bottom: 2px solid #333;
padding: 15px 20px;
position: sticky;
top: 0;
z-index: 1000;
}
.cinema-nav {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1400px;
margin: 0 auto;
}
.cinema-nav h1 {
margin: 0;
font-size: 2rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.7);
background: linear-gradient(45deg, #ff6b9d, #c471ed, #12c2e9);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.cinema-controls {
display: flex;
gap: 10px;
}
/* ===== KEYBOARD SHORTCUTS HELP ===== */
.shortcuts-help {
position: fixed;
top: 80px;
right: 20px;
background: rgba(0, 0, 0, 0.9);
border: 2px solid #444;
border-radius: 10px;
padding: 20px;
max-width: 350px;
z-index: 2000;
display: none;
backdrop-filter: blur(10px);
}
.shortcuts-help h3 {
margin: 0 0 15px 0;
color: #ff6b9d;
text-align: center;
}
.shortcuts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 0.9rem;
}
.shortcut-item {
display: flex;
align-items: center;
gap: 8px;
padding: 4px;
}
.shortcut-item kbd {
background: #333;
border: 1px solid #555;
border-radius: 4px;
padding: 2px 6px;
font-size: 0.8rem;
min-width: 24px;
text-align: center;
font-family: monospace;
}
/* ===== MAIN CINEMA LAYOUT ===== */
.cinema-main {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
display: grid;
grid-template-rows: auto auto 1fr;
gap: 20px;
min-height: calc(100vh - 120px);
}
/* ===== VIDEO PLAYER SECTION ===== */
.video-player-section {
background: #111;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.video-container {
position: relative;
width: 100%;
height: 60vh;
min-height: 400px;
background: #000;
}
.main-video {
width: 100%;
height: 100%;
object-fit: contain;
background: #000;
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
transition: opacity 0.3s ease;
}
.video-overlay.hidden {
opacity: 0;
}
.video-title {
font-size: 1.8rem;
font-weight: bold;
text-align: center;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
margin-bottom: 10px;
padding: 0 20px;
}
.video-info {
font-size: 1rem;
color: #ccc;
text-align: center;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
.play-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: auto;
}
.play-button-large {
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
border: none;
font-size: 2rem;
color: #333;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}
.play-button-large:hover {
background: rgba(255, 255, 255, 1);
transform: scale(1.1);
}
.video-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: #fff;
}
/* ===== VIDEO CONTROLS ===== */
.video-controls {
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.8));
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
transform: translateY(100%);
transition: transform 0.3s ease;
}
.video-container:hover .video-controls,
.video-controls:hover,
.video-controls.visible {
transform: translateY(0);
}
.progress-container {
margin-bottom: 15px;
}
.progress-bar {
position: relative;
height: 6px;
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
cursor: pointer;
margin-bottom: 8px;
}
.progress-filled {
height: 100%;
background: linear-gradient(90deg, #ff6b9d, #c471ed);
border-radius: 3px;
width: 0%;
transition: width 0.1s ease;
}
.progress-thumb {
position: absolute;
top: -4px;
width: 14px;
height: 14px;
background: #fff;
border-radius: 50%;
transform: translateX(-50%);
left: 0%;
opacity: 0;
transition: opacity 0.2s ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}
.progress-bar:hover .progress-thumb {
opacity: 1;
}
.time-display {
display: flex;
justify-content: space-between;
font-size: 0.9rem;
color: #ccc;
}
.controls-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
}
.controls-left,
.controls-center,
.controls-right {
display: flex;
align-items: center;
gap: 15px;
}
.control-btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #fff;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
}
.volume-control {
display: flex;
align-items: center;
gap: 10px;
}
.volume-slider {
width: 80px;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
outline: none;
cursor: pointer;
}
.volume-slider::-webkit-slider-thumb {
width: 14px;
height: 14px;
background: #fff;
border-radius: 50%;
cursor: pointer;
-webkit-appearance: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}
.volume-percentage {
color: #ccc;
font-size: 0.9rem;
min-width: 35px;
}
.quality-dropdown,
.speed-dropdown {
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #fff;
padding: 5px 8px;
border-radius: 4px;
font-size: 0.9rem;
}
/* ===== PLAYLIST SECTION ===== */
.playlist-section {
background: #1a1a1a;
border-radius: 10px;
padding: 20px;
max-height: 300px;
overflow-y: auto;
}
.playlist-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
border-bottom: 2px solid #333;
padding-bottom: 10px;
}
.playlist-header h3 {
margin: 0;
color: #ff6b9d;
}
.playlist-controls {
display: flex;
gap: 10px;
}
.playlist-content {
min-height: 100px;
}
.playlist-empty {
text-align: center;
color: #666;
padding: 30px;
}
.playlist-item {
display: flex;
align-items: center;
padding: 10px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.playlist-item:hover {
background: rgba(255, 255, 255, 0.1);
}
.playlist-item.current {
background: rgba(255, 107, 157, 0.2);
border: 1px solid #ff6b9d;
}
.playlist-thumbnail {
width: 60px;
height: 40px;
background: #333;
border-radius: 4px;
margin-right: 15px;
object-fit: cover;
}
.playlist-details {
flex: 1;
}
.playlist-title {
font-weight: bold;
margin-bottom: 4px;
}
.playlist-duration {
color: #999;
font-size: 0.9rem;
}
.playlist-actions {
display: flex;
gap: 8px;
}
/* ===== VIDEO LIBRARY SECTION ===== */
.video-library-section {
background: #1a1a1a;
border-radius: 10px;
padding: 20px;
}
.library-header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 15px;
margin-bottom: 20px;
border-bottom: 2px solid #333;
padding-bottom: 15px;
}
.library-header h3 {
margin: 0;
color: #ff6b9d;
}
.view-toggle {
display: flex;
border: 1px solid #444;
border-radius: 6px;
overflow: hidden;
}
.view-btn {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #ccc;
padding: 8px 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.view-btn.active,
.view-btn:hover {
background: #ff6b9d;
color: #fff;
}
.sort-controls {
display: flex;
align-items: center;
gap: 10px;
}
.sort-dropdown {
background: rgba(0, 0, 0, 0.7);
border: 1px solid #444;
color: #fff;
padding: 8px 12px;
border-radius: 6px;
}
.search-controls {
display: flex;
align-items: center;
gap: 10px;
}
.search-input {
background: rgba(0, 0, 0, 0.7);
border: 1px solid #444;
color: #fff;
padding: 8px 12px;
border-radius: 6px;
width: 200px;
}
.search-input::placeholder {
color: #666;
}
.library-content {
min-height: 200px;
}
.library-loading {
text-align: center;
padding: 50px;
color: #666;
}
/* ===== LIBRARY GRID VIEW ===== */
.library-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.video-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.video-card:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
}
.video-card.selected {
border-color: #ff6b9d;
background: rgba(255, 107, 157, 0.1);
}
.video-thumbnail {
width: 100%;
height: 120px;
background: #333;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 2rem;
position: relative;
overflow: hidden;
}
.video-thumbnail img,
.video-thumbnail video {
width: 100%;
height: 100%;
object-fit: cover;
}
.video-duration {
position: absolute;
bottom: 5px;
right: 5px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.8rem;
}
.video-details {
padding: 12px;
}
.video-name {
font-weight: bold;
margin-bottom: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.video-meta {
color: #999;
font-size: 0.85rem;
display: flex;
justify-content: space-between;
}
.video-actions {
padding: 0 12px 12px;
display: flex;
justify-content: space-between;
opacity: 0;
transition: opacity 0.3s ease;
}
.video-card:hover .video-actions {
opacity: 1;
}
/* ===== LIBRARY LIST VIEW ===== */
.library-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.video-list-item {
display: flex;
align-items: center;
padding: 12px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid transparent;
}
.video-list-item:hover {
background: rgba(255, 255, 255, 0.1);
}
.video-list-item.selected {
border-color: #ff6b9d;
background: rgba(255, 107, 157, 0.1);
}
.list-thumbnail {
width: 80px;
height: 50px;
background: #333;
border-radius: 6px;
margin-right: 15px;
object-fit: cover;
}
.list-details {
flex: 1;
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: 15px;
align-items: center;
}
.list-name {
font-weight: bold;
}
.list-meta {
color: #999;
font-size: 0.9rem;
}
/* ===== THEATER MODE ===== */
.theater-mode .cinema-header {
opacity: 0.3;
transition: opacity 0.3s ease;
}
.theater-mode .cinema-header:hover {
opacity: 1;
}
.theater-mode .playlist-section,
.theater-mode .video-library-section {
opacity: 0.5;
transition: opacity 0.3s ease;
}
.theater-mode .playlist-section:hover,
.theater-mode .video-library-section:hover {
opacity: 1;
}
/* ===== FULLSCREEN STYLES ===== */
.video-container:-webkit-full-screen {
width: 100vw;
height: 100vh;
}
.video-container:-moz-full-screen {
width: 100vw;
height: 100vh;
}
.video-container:fullscreen {
width: 100vw;
height: 100vh;
}
/* ===== RESPONSIVE DESIGN ===== */
@media (max-width: 1024px) {
.cinema-main {
padding: 15px;
gap: 15px;
}
.video-container {
height: 50vh;
min-height: 300px;
}
.library-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
}
.shortcuts-help {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 90vw;
}
}
@media (max-width: 768px) {
.cinema-nav {
flex-direction: column;
gap: 10px;
text-align: center;
}
.controls-row {
flex-direction: column;
gap: 15px;
}
.library-header {
flex-direction: column;
align-items: stretch;
}
.search-input {
width: 100%;
}
.list-details {
grid-template-columns: 1fr;
gap: 5px;
}
.shortcuts-grid {
grid-template-columns: 1fr;
}
}
/* ===== UTILITY CLASSES ===== */
.hidden {
display: none !important;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-left: 4px solid #ff6b9d;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* ===== SCROLLBAR STYLING ===== */
.playlist-section::-webkit-scrollbar,
.library-content::-webkit-scrollbar {
width: 8px;
}
.playlist-section::-webkit-scrollbar-track,
.library-content::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
.playlist-section::-webkit-scrollbar-thumb,
.library-content::-webkit-scrollbar-thumb {
background: rgba(255, 107, 157, 0.7);
border-radius: 4px;
}
.playlist-section::-webkit-scrollbar-thumb:hover,
.library-content::-webkit-scrollbar-thumb:hover {
background: rgba(255, 107, 157, 1);
}