clarkeplay / javascript
AptlyDigital's picture
Rename javascript.js to javascript
765022d verified
// Clarke Player Pro - Core Application
class ClarkePlayerPro {
constructor() {
this.state = {
playlists: [],
currentPlaylist: null,
channels: [],
filteredChannels: [],
favorites: JSON.parse(localStorage.getItem('clarke_favs')) || {},
currentChannel: null,
hls: null,
isPlaying: false,
quality: 'Auto',
searchQuery: '',
filterCategory: 'all'
};
this.elements = {
video: document.getElementById('videoPlayer'),
playlistUrl: document.getElementById('playlistUrl'),
fileInput: document.getElementById('fileInput'),
uploadArea: document.getElementById('uploadArea'),
loadPlaylistBtn: document.getElementById('loadPlaylistBtn'),
clearBtn: document.getElementById('clearBtn'),
channelsContainer: document.getElementById('channelsContainer'),
currentChannel: document.getElementById('currentChannel'),
currentProgram: document.getElementById('currentProgram'),
nowPlayingChannel: document.getElementById('nowPlayingChannel'),
nowPlayingRegion: document.getElementById('nowPlayingRegion'),
statusText: document.getElementById('statusText'),
channelCount: document.getElementById('channelCount'),
lastUpdate: document.getElementById('lastUpdate'),
avgQuality: document.getElementById('avgQuality'),
loadingOverlay: document.getElementById('loadingOverlay'),
channelSearch: document.getElementById('channelSearch'),
toggleFav: document.getElementById('toggleFav'),
playPauseBtn: document.getElementById('playPauseBtn'),
prevChannel: document.getElementById('prevChannel'),
nextChannel: document.getElementById('nextChannel'),
volumeSlider: document.getElementById('volumeSlider'),
favoritesList: document.getElementById('favoritesList'),
categoryFilter: document.getElementById('categoryFilter'),
fullscreenBtn: document.getElementById('fullscreenBtn'),
settingsBtn: document.getElementById('settingsBtn'),
bitrateStat: document.getElementById('bitrateStat'),
bufferStat: document.getElementById('bufferStat'),
healthStat: document.getElementById('healthStat'),
qualityBadge: document.getElementById('qualityBadge'),
bufferProgress: document.getElementById('bufferProgress'),
currentTime: document.getElementById('currentTime'),
totalTime: document.getElementById('totalTime')
};
this.init();
}
init() {
console.log('🚀 Clarke Player Pro v2.1 Initializing...');
this.setupEventListeners();
this.setupKeyboardControls();
this.loadDefaultPlaylist();
this.updateStats();
this.renderFavorites();
// Set initial volume
this.elements.video.volume = this.elements.volumeSlider.value / 100;
}
setupEventListeners() {
// Load playlist from URL
this.elements.loadPlaylistBtn.addEventListener('click', () => {
this.loadPlaylistFromUrl();
});
// Quick preset buttons
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const url = e.currentTarget.dataset.url;
this.elements.playlistUrl.value = url;
this.loadPlaylistFromUrl();
});
});
// File upload
this.elements.uploadArea.addEventListener('click', () => {
this.elements.fileInput.click();
});
this.elements.fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
this.loadPlaylistFromFile(e.target.files[0]);
}
});
// Drag and drop for file upload
this.elements.uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
e.currentTarget.style.borderColor = 'var(--primary)';
e.currentTarget.style.background = 'rgba(139, 92, 246, 0.1)';
});
this.elements.uploadArea.addEventListener('dragleave', (e) => {
e.currentTarget.style.borderColor = 'var(--border)';
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.02)';
});
this.elements.uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
e.currentTarget.style.borderColor = 'var(--border)';
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.02)';
const file = e.dataTransfer.files[0];
if (file && (file.name.endsWith('.m3u') || file.name.endsWith('.m3u8'))) {
this.loadPlaylistFromFile(file);
} else {
this.showNotification('Please drop a valid .m3u or .m3u8 file', 'error');
}
});
// Clear button
this.elements.clearBtn.addEventListener('click', () => {
this.clearPlaylist();
});
// Search
this.elements.channelSearch.addEventListener('input', (e) => {
this.state.searchQuery = e.target.value.toLowerCase();
this.filterChannels();
});
// Category filter
this.elements.categoryFilter.addEventListener('change', (e) => {
this.state.filterCategory = e.target.value;
this.filterChannels();
});
// Player controls
this.elements.playPauseBtn.addEventListener('click', () => {
this.togglePlayPause();
});
this.elements.prevChannel.addEventListener('click', () => {
this.selectPreviousChannel();
});
this.elements.nextChannel.addEventListener('click', () => {
this.selectNextChannel();
});
this.elements.toggleFav.addEventListener('click', () => {
if (this.state.currentChannel) {
this.toggleFavorite(this.state.currentChannel.id);
}
});
// Volume control
this.elements.volumeSlider.addEventListener('input', (e) => {
this.elements.video.volume = e.target.value / 100;
});
// Fullscreen
this.elements.fullscreenBtn.addEventListener('click', () => {
this.toggleFullscreen();
});
// Video events
this.elements.video.addEventListener('play', () => {
this.state.isPlaying = true;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
this.updateStatus('Playing');
});
this.elements.video.addEventListener('pause', () => {
this.state.isPlaying = false;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
this.updateStatus('Paused');
});
this.elements.video.addEventListener('waiting', () => {
this.showLoading(true);
this.updateStatus('Buffering...');
});
this.elements.video.addEventListener('playing', () => {
this.showLoading(false);
this.updateStatus('Playing');
});
this.elements.video.addEventListener('timeupdate', () => {
this.updatePlaybackInfo();
});
this.elements.video.addEventListener('loadedmetadata', () => {
this.updatePlaybackInfo();
});
// Auto-hide controls
let controlsTimeout;
this.elements.video.addEventListener('mousemove', () => {
const overlay = document.querySelector('.video-overlay');
const controls = document.querySelector('.player-controls');
overlay.style.opacity = '1';
controls.style.opacity = '1';
clearTimeout(controlsTimeout);
controlsTimeout = setTimeout(() => {
if (!document.fullscreenElement) {
overlay.style.opacity = '0';
controls.style.opacity = '0';
}
}, 3000);
});
}
setupKeyboardControls() {
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT') return;
switch (e.key.toLowerCase()) {
case ' ':
e.preventDefault();
this.togglePlayPause();
break;
case 'arrowleft':
e.preventDefault();
this.seek(-10);
break;
case 'arrowright':
e.preventDefault();
this.seek(10);
break;
case 'arrowup':
e.preventDefault();
this.selectPreviousChannel();
break;
case 'arrowdown':
e.preventDefault();
this.selectNextChannel();
break;
case 'f':
e.preventDefault();
this.toggleFullscreen();
break;
case 'm':
e.preventDefault();
this.toggleMute();
break;
case 'l':
e.preventDefault();
this.loadPlaylistFromUrl();
break;
case 'escape':
if (document.fullscreenElement) {
document.exitFullscreen();
}
break;
}
});
}
async loadDefaultPlaylist() {
const defaultUrl = this.elements.playlistUrl.value;
if (defaultUrl) {
await this.loadPlaylistFromUrl();
}
}
async loadPlaylistFromUrl() {
const url = this.elements.playlistUrl.value.trim();
if (!url) {
this.showNotification('Please enter a playlist URL', 'error');
return;
}
this.updateStatus('Loading playlist...');
this.showLoading(true);
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const text = await response.text();
await this.parsePlaylist(text, url);
this.showNotification('Playlist loaded successfully', 'success');
this.elements.lastUpdate.textContent = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load playlist:', error);
this.showNotification('Failed to load playlist. Please check the URL.', 'error');
this.updateStatus('Load failed');
} finally {
this.showLoading(false);
}
}
async loadPlaylistFromFile(file) {
this.updateStatus('Reading playlist file...');
this.showLoading(true);
try {
const text = await file.text();
await this.parsePlaylist(text, file.name);
this.showNotification(`Loaded playlist: ${file.name}`, 'success');
this.elements.lastUpdate.textContent = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load file:', error);
this.showNotification('Failed to load playlist file', 'error');
} finally {
this.showLoading(false);
}
}
async parsePlaylist(content, source) {
const channels = [];
const lines = content.split('\n');
let currentChannel = {};
let channelNumber = 1;
for (let line of lines) {
line = line.trim();
if (line.startsWith('#EXTINF')) {
const titleMatch = line.match(/,(.*)$/);
const logoMatch = line.match(/tvg-logo="([^"]+)"/);
const groupMatch = line.match(/group-title="([^"]+)"/);
currentChannel = {
id: `channel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
title: titleMatch ? this.cleanTitle(titleMatch[1]) : `Channel ${channelNumber}`,
logo: logoMatch ? logoMatch[1] : '',
group: groupMatch ? groupMatch[1] : 'General',
number: channelNumber,
source: source,
url: '',
isFavorite: false,
category: this.detectCategory(titleMatch ? titleMatch[1] : '')
};
channelNumber++;
}
else if (line.startsWith('http')) {
currentChannel.url = line;
channels.push({...currentChannel});
}
}
this.state.channels = channels;
this.state.filteredChannels = [...channels];
this.updateChannelCount();
this.renderChannels();
if (channels.length > 0) {
this.selectChannel(0);
}
}
cleanTitle(title) {
return title
.replace(/^Pluto TV\s*/i, '')
.replace(/^\[.*?\]\s*/, '')
.replace(/\|.*$/, '')
.trim();
}
detectCategory(title) {
const lowerTitle = title.toLowerCase();
if (lowerTitle.includes('news') || lowerTitle.includes('cnn') || lowerTitle.includes('bbc')) {
return 'news';
} else if (lowerTitle.includes('sport') || lowerTitle.includes('espn') || lowerTitle.includes('football')) {
return 'sports';
} else if (lowerTitle.includes('movie') || lowerTitle.includes('cinema') || lowerTitle.includes('film')) {
return 'movies';
} else if (lowerTitle.includes('music') || lowerTitle.includes('mtv') || lowerTitle.includes('vibe')) {
return 'music';
} else if (lowerTitle.includes('kids') || lowerTitle.includes('cartoon') || lowerTitle.includes('disney')) {
return 'kids';
} else {
return 'entertainment';
}
}
renderChannels() {
const container = this.elements.channelsContainer;
if (this.state.filteredChannels.length === 0) {
container.innerHTML = `
<div class="welcome-state">
<i class="fas fa-broadcast-tower"></i>
<h3>No Channels Found</h3>
<p>Try a different search term or filter</p>
</div>
`;
return;
}
let html = '';
this.state.filteredChannels.forEach((channel, index) => {
const isActive = this.state.currentChannel &&
this.state.currentChannel.id === channel.id;
const isFavorite = this.state.favorites[channel.id];
html += `
<div class="channel-card ${isActive ? 'active' : ''}"
data-index="${index}"
data-id="${channel.id}">
<div class="channel-logo">
${channel.logo ?
`<img src="${channel.logo}" alt="${channel.title}"
onerror="this.style.display='none'; this.parentElement.innerHTML='<i class=\"fas fa-tv\"></i>'">` :
`<i class="fas fa-tv"></i>`}
</div>
<div class="channel-info">
<h4>${channel.title}</h4>
<div class="channel-meta">
<span class="channel-number">${channel.number}</span>
<span class="channel-category">${channel.category}</span>
</div>
</div>
<div class="channel-fav ${isFavorite ? 'active' : ''}"
data-id="${channel.id}">
<i class="fas fa-star"></i>
</div>
</div>
`;
});
container.innerHTML = html;
// Add event listeners
container.querySelectorAll('.channel-card').forEach(card => {
card.addEventListener('click', (e) => {
if (!e.target.closest('.channel-fav')) {
const index = parseInt(card.dataset.index);
this.selectChannel(index);
}
});
});
container.querySelectorAll('.channel-fav').forEach(fav => {
fav.addEventListener('click', (e) => {
e.stopPropagation();
const channelId = fav.dataset.id;
this.toggleFavorite(channelId);
fav.classList.toggle('active');
});
});
}
filterChannels() {
let filtered = this.state.channels;
// Apply search filter
if (this.state.searchQuery) {
filtered = filtered.filter(ch =>
ch.title.toLowerCase().includes(this.state.searchQuery) ||
ch.group.toLowerCase().includes(this.state.searchQuery) ||
ch.category.toLowerCase().includes(this.state.searchQuery)
);
}
// Apply category filter
if (this.state.filterCategory !== 'all') {
filtered = filtered.filter(ch =>
ch.category === this.state.filterCategory
);
}
this.state.filteredChannels = filtered;
this.renderChannels();
this.updateChannelCount();
}
selectChannel(index) {
if (index < 0 || index >= this.state.filteredChannels.length) return;
const channel = this.state.filteredChannels[index];
this.state.currentChannel = channel;
// Update UI
this.elements.currentChannel.textContent = channel.title;
this.elements.currentProgram.textContent = `Now Playing • ${channel.group}`;
this.elements.nowPlayingChannel.textContent = channel.title;
this.elements.nowPlayingRegion.textContent = channel.group;
// Update favorite button
this.elements.toggleFav.classList.toggle('active', this.state.favorites[channel.id]);
// Update active state in list
document.querySelectorAll('.channel-card').forEach(card => {
card.classList.remove('active');
});
const selectedCard = document.querySelector(`.channel-card[data-index="${index}"]`);
if (selectedCard) {
selectedCard.classList.add('active');
selectedCard.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// Play the channel
this.playChannel(channel.url);
this.updateStatus(`Playing: ${channel.title}`);
}
selectNextChannel() {
if (!this.state.currentChannel || this.state.filteredChannels.length === 0) return;
const currentIndex = this.state.filteredChannels.findIndex(
ch => ch.id === this.state.currentChannel.id
);
if (currentIndex >= 0) {
const nextIndex = (currentIndex + 1) % this.state.filteredChannels.length;
this.selectChannel(nextIndex);
} else if (this.state.filteredChannels.length > 0) {
this.selectChannel(0);
}
}
selectPreviousChannel() {
if (!this.state.currentChannel || this.state.filteredChannels.length === 0) return;
const currentIndex = this.state.filteredChannels.findIndex(
ch => ch.id === this.state.currentChannel.id
);
if (currentIndex >= 0) {
const prevIndex = currentIndex - 1 >= 0 ?
currentIndex - 1 :
this.state.filteredChannels.length - 1;
this.selectChannel(prevIndex);
} else if (this.state.filteredChannels.length > 0) {
this.selectChannel(0);
}
}
playChannel(url) {
this.showLoading(true);
// Destroy previous HLS instance
if (this.state.hls) {
this.state.hls.destroy();
this.state.hls = null;
}
// Stop current video
this.elements.video.pause();
this.elements.video.src = '';
if (Hls.isSupported()) {
this.state.hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 30,
maxBufferLength: 60,
debug: false,
liveSyncDurationCount: 3,
liveMaxLatencyDurationCount: 10
});
this.state.hls.loadSource(url);
this.state.hls.attachMedia(this.elements.video);
this.state.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.elements.video.play().then(() => {
this.showLoading(false);
this.state.isPlaying = true;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
}).catch(error => {
console.log('Autoplay prevented:', error);
this.showLoading(false);
this.updateStatus('Click play button to start');
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
});
});
this.state.hls.on(Hls.Events.ERROR, (event, data) => {
console.error('HLS Error:', data);
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
this.updateStatus('Network error - retrying...');
this.state.hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
this.updateStatus('Media error - recovering...');
this.state.hls.recoverMediaError();
break;
default:
this.state.hls.destroy();
this.showNotification('Playback failed. Trying next channel...', 'error');
this.selectNextChannel();
break;
}
}
});
this.state.hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
const level = this.state.hls.levels[data.level];
if (level) {
const bitrate = Math.round(level.bitrate / 1000);
const height = level.height || 'Auto';
this.state.quality = height === 'Auto' ? 'Auto' : `${height}p`;
this.elements.qualityBadge.textContent = this.state.quality;
this.elements.bitrateStat.textContent = `${bitrate} Mbps`;
this.updateQualityStats();
}
});
this.state.hls.on(Hls.Events.BUFFER_CREATED, () => {
this.updateBufferStats();
});
} else if (this.elements.video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari native HLS support
this.elements.video.src = url;
this.elements.video.play().then(() => {
this.showLoading(false);
this.state.isPlaying = true;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
}).catch(error => {
console.log('Native HLS autoplay prevented:', error);
this.showLoading(false);
this.updateStatus('Click play button to start');
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
});
} else {
this.showLoading(false);
this.showNotification('Your browser does not support HLS streaming', 'error');
}
}
togglePlayPause() {
if (this.elements.video.paused) {
this.elements.video.play();
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
} else {
this.elements.video.pause();
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
}
}
toggleFavorite(channelId) {
if (this.state.favorites[channelId]) {
delete this.state.favorites[channelId];
this.showNotification('Removed from favorites', 'info');
} else {
this.state.favorites[channelId] = true;
this.showNotification('Added to favorites', 'success');
}
localStorage.setItem('clarke_favs', JSON.stringify(this.state.favorites));
this.renderFavorites();
// Update favorite button if this is the current channel
if (this.state.currentChannel && this.state.currentChannel.id === channelId) {
this.elements.toggleFav.classList.toggle('active', this.state.favorites[channelId]);
}
}
renderFavorites() {
const container = this.elements.favoritesList;
const favoriteChannels = this.state.channels.filter(ch => this.state.favorites[ch.id]);
if (favoriteChannels.length === 0) {
container.innerHTML = `
<div class="empty-favorites">
<i class="fas fa-star"></i>
<p>No favorites yet</p>
</div>
`;
return;
}
let html = '';
favoriteChannels.forEach(channel => {
html += `
<div class="favorite-item" data-id="${channel.id}">
<i class="fas fa-star"></i>
<span>${channel.title}</span>
</div>
`;
});
container.innerHTML = html;
// Add click listeners
container.querySelectorAll('.favorite-item').forEach(item => {
item.addEventListener('click', () => {
const channelId = item.dataset.id;
const channelIndex = this.state.filteredChannels.findIndex(ch => ch.id === channelId);
if (channelIndex >= 0) {
this.selectChannel(channelIndex);
}
});
});
}
toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.log(`Fullscreen error: ${err.message}`);
});
} else {
document.exitFullscreen();
}
}
toggleMute() {
this.elements.video.muted = !this.elements.video.muted;
this.updateStatus(this.elements.video.muted ? 'Muted' : 'Unmuted');
}
seek(seconds) {
this.elements.video.currentTime += seconds;
}
updateStatus(text) {
this.elements.statusText.textContent = text;
}
updateChannelCount() {
const count = this.state.filteredChannels.length;
const total = this.state.channels.length;
this.elements.channelCount.textContent = `${count}`;
// Update average quality estimation
if (total > 0) {
const hdCount = this.state.channels.filter(ch =>
ch.title.toLowerCase().includes('hd') ||
ch.title.toLowerCase().includes('1080') ||
ch.title.toLowerCase().includes('4k')
).length;
const hdPercentage = Math.round((hdCount / total) * 100);
this.elements.avgQuality.textContent = `${hdPercentage}% HD`;
}
}
updatePlaybackInfo() {
const current = this.elements.video.currentTime;
const duration = this.elements.video.duration || 0;
// Format time
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
this.elements.currentTime.textContent = formatTime(current);
this.elements.totalTime.textContent = formatTime(duration);
// Update buffer progress
if (this.elements.video.buffered.length > 0) {
const bufferedEnd = this.elements.video.buffered.end(this.elements.video.buffered.length - 1);
const bufferPercentage = duration > 0 ? (bufferedEnd / duration) * 100 : 0;
this.elements.bufferProgress.style.width = `${bufferPercentage}%`;
}
}
updateStats() {
// Update health based on buffering
if (this.state.hls) {
const bufferLength = this.state.hls.media.buffered.length;
const health = bufferLength > 0 ? 100 : 80;
this.elements.healthStat.textContent = `${health}%`;
}
// Update buffer time
if (this.elements.video.buffered.length > 0) {
const bufferedEnd = this.elements.video.buffered.end(this.elements.video.buffered.length - 1);
const bufferTime = Math.round(bufferedEnd - this.elements.video.currentTime);
this.elements.bufferStat.textContent = `${bufferTime}s`;
}
// Update every second
setTimeout(() => this.updateStats(), 1000);
}
updateBufferStats() {
if (this.state.hls) {
const bufferInfo = this.state.hls.media.buffered;
if (bufferInfo.length > 0) {
const bufferTime = bufferInfo.end(bufferInfo.length - 1) - this.elements.video.currentTime;
this.elements.bufferStat.textContent = `${Math.round(bufferTime)}s`;
}
}
}
updateQualityStats() {
// Update quality distribution
if (this.state.channels.length > 0) {
const hdChannels = this.state.channels.filter(ch =>
ch.title.toLowerCase().includes('hd') ||
ch.title.toLowerCase().includes('1080') ||
ch.title.toLowerCase().includes('4k')
).length;
const hdPercentage = Math.round((hdChannels / this.state.channels.length) * 100);
this.elements.avgQuality.textContent = `${hdPercentage}% HD`;
}
}
clearPlaylist() {
this.state.channels = [];
this.state.filteredChannels = [];
this.state.currentChannel = null;
if (this.state.hls) {
this.state.hls.destroy();
this.state.hls = null;
}
this.elements.video.pause();
this.elements.video.src = '';
this.renderChannels();
this.updateChannelCount();
this.updateStatus('Playlist cleared');
this.elements.currentChannel.textContent = 'Welcome to Clarke Player Pro';
this.elements.currentProgram.textContent = 'Load a playlist to start streaming';
this.elements.nowPlayingChannel.textContent = '--';
this.elements.nowPlayingRegion.textContent = '--';
this.showNotification('Playlist cleared', 'info');
}
showLoading(show) {
this.elements.loadingOverlay.style.display = show ? 'flex' : 'none';
}
showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
<span>${message}</span>
`;
// Add styles for notification
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? 'var(--accent)' : type === 'error' ? 'var(--danger)' : 'var(--info)'};
color: white;
padding: 1rem 1.5rem;
border-radius: 12px;
display: flex;
align-items: center;
gap: 0.75rem;
z-index: 10000;
animation: slideIn 0.3s ease;
box-shadow: var(--shadow-lg);
max-width: 300px;
`;
document.body.appendChild(notification);
// Remove after 3 seconds
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
// Add animation keyframes
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
if (!document.querySelector('#notification-styles')) {
style.id = 'notification-styles';
document.head.appendChild(style);
}
}
}
// Initialize Clarke Player Pro
let player;
document.addEventListener('DOMContentLoaded', () => {
player = new ClarkePlayerPro();
});