Implemented SVG icons

This commit is contained in:
Trevor Mears
2025-12-28 21:27:21 -08:00
parent 44365c4dad
commit 29a7996ecd
8 changed files with 151 additions and 49 deletions
+38 -7
View File
@@ -141,16 +141,25 @@ h4 {
}
.logo {
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.75rem;
color: #fff;
}
.brand-text {
font-size: 1.25rem;
font-weight: 700;
background: linear-gradient(135deg, var(--color-accent), #a855f7);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-family: 'Outfit', sans-serif;
font-size: 1.5rem;
font-weight: 800;
letter-spacing: -0.03em;
color: #fff;
/* Visual correction: shift text slightly down to align optically with icon center */
padding-top: 2px;
}
.brand-accent {
color: var(--color-accent);
}
.navbar-menu {
@@ -1992,4 +2001,26 @@ kbd {
flex-shrink: 0;
font-size: 0.875rem;
color: var(--color-text-muted);
}
/* Icons */
.icon {
width: 1.25em;
height: 1.25em;
display: inline-block;
vertical-align: middle;
fill: currentColor;
}
.nav-link .icon {
width: 1.5em;
height: 1.5em;
}
.icon.text-warning {
color: var(--color-warning);
}
.icon.text-error {
color: var(--color-error);
}
+58 -13
View File
@@ -7,6 +7,9 @@
<title>NodeCast TV | IPTV Player</title>
<meta name="description" content="Stream live TV, movies, and series with EPG support">
<link rel="stylesheet" href="css/main.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<!-- HLS.js for video playback -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7/dist/hls.min.js"></script>
</head>
@@ -16,28 +19,50 @@
<!-- Navigation -->
<nav class="navbar">
<div class="navbar-brand">
<span class="logo">📺</span>
<span class="brand-text">NodeCast TV</span>
<span class="logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
<path
d="M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8a2 2 0 0 0-2-2zm0 14H3V8h18v12zM9 10v8l7-4z" />
</svg></span>
<span class="brand-text">NodeCast <span class="brand-accent">TV</span></span>
</div>
<div class="navbar-menu">
<a href="#" class="nav-link active" data-page="home">
<span class="nav-icon">📡</span>
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
class="icon">
<path
d="M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8a2 2 0 0 0-2-2zm0 14H3V8h18v12zM9 10v8l7-4z" />
</svg></span>
<span>Live TV</span>
</a>
<a href="#" class="nav-link" data-page="guide">
<span class="nav-icon">📋</span>
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
class="icon">
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z" />
</svg></span>
<span>TV Guide</span>
</a>
<a href="#" class="nav-link" data-page="movies">
<span class="nav-icon">🎬</span>
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
class="icon">
<path
d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V4h-4z" />
</svg></span>
<span>Movies</span>
</a>
<a href="#" class="nav-link" data-page="series">
<span class="nav-icon">📺</span>
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
class="icon">
<path
d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z" />
</svg></span>
<span>Series</span>
</a>
<a href="#" class="nav-link" data-page="settings">
<span class="nav-icon">⚙️</span>
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
class="icon">
<path
d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L3.16 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.04.64.09.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.58 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
</svg></span>
<span>Settings</span>
</a>
</div>
@@ -57,7 +82,9 @@
<option value="">All Sources</option>
</select>
<button id="toggle-groups" class="btn btn-sm btn-ghost" title="Expand/Collapse All">
<span>⊟</span>
<span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
<path d="M19 13H5v-2h14v2z" />
</svg></span>
</button>
</div>
@@ -132,7 +159,10 @@
</select>
<input type="text" id="movies-search" placeholder="Search movies..." class="search-input">
<button id="movies-favorites-btn" class="btn btn-icon btn-ghost" title="Show Favorites Only">
♥ Favorites
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
<path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg> Favorites
</button>
</div>
</div>
@@ -156,7 +186,10 @@
</select>
<input type="text" id="series-search" placeholder="Search series..." class="search-input">
<button id="series-favorites-btn" class="btn btn-icon btn-ghost" title="Show Favorites Only">
♥ Favorites
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
<path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
</svg> Favorites
</button>
</div>
</div>
@@ -351,7 +384,11 @@
<div class="content-actions">
<button class="btn btn-sm btn-secondary" id="content-show-all">Show All</button>
<button class="btn btn-sm btn-secondary" id="content-hide-all">Hide All</button>
<button class="btn btn-sm btn-primary" id="content-save">💾 Save Changes</button>
<button class="btn btn-sm btn-primary" id="content-save"><svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" fill="currentColor" class="icon">
<path
d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z" />
</svg> Save Changes</button>
</div>
</div>
<p class="hint" style="margin-bottom: var(--space-sm);">✓ Checked = Visible, ☐ Unchecked = Hidden</p>
@@ -379,11 +416,19 @@
<!-- Context Menu Template -->
<div id="context-menu" class="context-menu">
<button class="context-item" data-action="play">▶ Play</button>
<button class="context-item" data-action="hide">👁 Hide</button>
<button class="context-item" data-action="play"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="currentColor" class="icon">
<path d="M8 5v14l11-7z" />
</svg> Play</button>
<button class="context-item" data-action="hide"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="currentColor" class="icon">
<path
d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-4.01.7l2.16 2.16C10.85 7.18 11.4 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" />
</svg> Hide</button>
</div>
<!-- JavaScript -->
<script src="/js/icons.js"></script>
<script src="/js/api.js?v=2"></script>
<script src="/js/components/VideoPlayer.js?v=2"></script>
<script src="/js/components/ChannelList.js?v=3"></script>
+6 -6
View File
@@ -357,7 +357,7 @@ class ChannelList {
html += `
<div class="channel-group">
<div class="group-header ${this.collapsedGroups.has(groupName) ? 'collapsed' : ''} ${isFavoritesGroup ? 'favorites-group' : ''}" data-group="${groupName}">
<span class="group-toggle">▼</span>
<span class="group-toggle">${Icons.chevronDown}</span>
<span class="group-name">${groupName}</span>
<span class="group-count">${visibleChannels.length}</span>
</div>
@@ -393,7 +393,7 @@ class ChannelList {
<div class="channel-program">${this.escapeHtml(this.getProgramInfo(channel) || '')}</div>
</div>
<button class="favorite-btn ${isFavorite ? 'active' : ''}" title="${isFavorite ? 'Remove from Favorites' : 'Add to Favorites'}">
${isFavorite ? '❤️' : '♡'}
${isFavorite ? Icons.favorite : Icons.favoriteOutline}
</button>
</div>
`;
@@ -708,14 +708,14 @@ class ChannelList {
this.visibleFavorites.delete(key);
btns.forEach(btn => {
btn.classList.remove('active');
btn.innerHTML = '♡';
btn.innerHTML = Icons.favoriteOutline;
btn.title = 'Add to Favorites';
});
} else {
this.visibleFavorites.add(key);
btns.forEach(btn => {
btn.classList.add('active');
btn.innerHTML = '❤️';
btn.innerHTML = Icons.favorite;
btn.title = 'Remove from Favorites';
});
}
@@ -745,7 +745,7 @@ class ChannelList {
this.visibleFavorites.add(key);
btns.forEach(btn => {
btn.classList.add('active');
btn.innerHTML = '❤️';
btn.innerHTML = Icons.favorite;
});
// Revert group update
const channel = this.channels.find(c => c.sourceId == sourceId && c.id == channelId);
@@ -754,7 +754,7 @@ class ChannelList {
this.visibleFavorites.delete(key);
btns.forEach(btn => {
btn.classList.remove('active');
btn.innerHTML = '♡';
btn.innerHTML = Icons.favoriteOutline;
});
// Revert group update
const channel = this.channels.find(c => c.sourceId == sourceId && c.id == channelId);
+7 -7
View File
@@ -449,7 +449,7 @@ class EpgGuide {
data-source-id="${sourceChannel.sourceId}">
<div class="epg-channel-info">
<button class="favorite-btn ${isFavorite ? 'active' : ''}" title="${isFavorite ? 'Remove from Favorites' : 'Add to Favorites'}">
${isFavorite ? '❤️' : '♡'}
${isFavorite ? Icons.favorite : Icons.favoriteOutline}
</button>
<img class="epg-channel-logo" src="${logo}"
alt="" onerror="this.onerror=null;this.src='/img/placeholder.png'">
@@ -536,11 +536,11 @@ class EpgGuide {
if (btn) {
if (isNowFavorite) {
btn.classList.add('active');
btn.innerHTML = '❤️';
btn.innerHTML = Icons.favorite;
btn.title = 'Remove from Favorites';
} else {
btn.classList.remove('active');
btn.innerHTML = '♡';
btn.innerHTML = Icons.favoriteOutline;
btn.title = 'Add to Favorites';
}
}
@@ -578,11 +578,11 @@ class EpgGuide {
if (btn) {
if (wasFavorite) {
btn.classList.add('active');
btn.innerHTML = '❤️';
btn.innerHTML = Icons.favorite;
btn.title = 'Remove from Favorites';
} else {
btn.classList.remove('active');
btn.innerHTML = '♡';
btn.innerHTML = Icons.favoriteOutline;
btn.title = 'Add to Favorites';
}
}
@@ -619,11 +619,11 @@ class EpgGuide {
if (btn) {
if (isFavorite) {
btn.classList.add('active');
btn.innerHTML = '❤️';
btn.innerHTML = Icons.favorite;
btn.title = 'Remove from Favorites';
} else {
btn.classList.remove('active');
btn.innerHTML = '♡';
btn.innerHTML = Icons.favoriteOutline;
btn.title = 'Add to Favorites';
}
}
+9 -9
View File
@@ -52,7 +52,7 @@ class SourceManager {
return;
}
const icons = { xtream: '📡', m3u: '📋', epg: '📺' };
const icons = { xtream: Icons.live, m3u: Icons.guide, epg: Icons.series };
container.innerHTML = sources.map(source => `
<div class="source-item ${source.enabled ? '' : 'disabled'}" data-id="${source.id}">
@@ -62,13 +62,13 @@ class SourceManager {
<div class="source-url">${source.url}</div>
</div>
<div class="source-actions">
<button class="btn btn-sm btn-secondary" data-action="refresh" title="Refresh Data">🔄</button>
<button class="btn btn-sm btn-secondary" data-action="test" title="Test Connection">🔗</button>
<button class="btn btn-sm btn-secondary" data-action="refresh" title="Refresh Data">${Icons.play}</button>
<button class="btn btn-sm btn-secondary" data-action="test" title="Test Connection">${Icons.search}</button>
<button class="btn btn-sm btn-secondary" data-action="toggle" title="${source.enabled ? 'Disable' : 'Enable'}">
${source.enabled ? '✓' : '○'}
${source.enabled ? Icons.favorite : Icons.favoriteOutline}
</button>
<button class="btn btn-sm btn-secondary" data-action="edit" title="Edit">✏️</button>
<button class="btn btn-sm btn-danger" data-action="delete" title="Delete">🗑️</button>
<button class="btn btn-sm btn-secondary" data-action="edit" title="Edit">${Icons.settings}</button>
<button class="btn btn-sm btn-danger" data-action="delete" title="Delete">${Icons.close}</button>
</div>
</div>
`).join('');
@@ -294,7 +294,7 @@ class SourceManager {
const btn = document.querySelector(`.source-item[data-id="${id}"] [data-action="refresh"]`);
if (btn) {
btn.disabled = true;
btn.textContent = '⏳';
btn.innerHTML = '<div class="loading-spinner" style="width:1em;height:1em;border-width:2px"></div>';
}
// Clear cache for this source first
@@ -322,7 +322,7 @@ class SourceManager {
if (btn) {
btn.disabled = false;
btn.textContent = '🔄';
btn.innerHTML = Icons.play;
}
} catch (err) {
console.error('Error refreshing source:', err);
@@ -546,7 +546,7 @@ class SourceManager {
return `
<div class="content-group ${isExpanded ? '' : 'collapsed'}" data-group-id="${this.escapeHtml(group.id)}">
<div class="content-group-header">
<span class="group-expander">▼</span>
<span class="group-expander">${Icons.chevronDown}</span>
<label class="checkbox-label" onclick="event.stopPropagation()">
<input type="checkbox" class="group-checkbox"
data-type="group"
+26
View File
@@ -0,0 +1,26 @@
/**
* Application Icons (SVG)
* Replaces emojis for consistent cross-platform appearance
*/
const Icons = {
logo: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8a2 2 0 0 0-2-2zm0 14H3V8h18v12zM9 10v8l7-4z"/></svg>`, // TV with Play
live: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M21 6h-7.59l3.29-3.29L16 2l-4 4-4-4-.71.71L10.59 6H3a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8a2 2 0 0 0-2-2zm0 14H3V8h18v12zM9 10v8l7-4z"/></svg>`, // TV with Play (Same as Logo)
guide: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>`, // List
movies: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V4h-4z"/></svg>`, // Film
series: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"/></svg>`, // TV Generic
settings: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L3.16 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.04.64.09.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.58 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>`,
search: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>`,
favorite: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>`,
favoriteOutline: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"/></svg>`,
play: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M8 5v14l11-7z"/></svg>`,
hide: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-4.01.7l2.16 2.16C10.85 7.18 11.4 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/></svg>`,
save: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>`,
star: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon text-warning"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>`,
chevronDown: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"/></svg>`,
chevronRight: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>`,
collapseAll: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M19 13H5v-2h14v2z"/></svg>`, // Minus
close: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>`,
};
window.Icons = Icons;
+3 -3
View File
@@ -280,7 +280,7 @@ class MoviesPage {
const poster = movie.stream_icon || movie.cover || '/img/placeholder.png';
const year = movie.year || movie.releaseDate?.substring(0, 4) || '';
const rating = movie.rating ? `⭐ ${movie.rating}` : '';
const rating = movie.rating ? `${Icons.star} ${movie.rating}` : '';
const isFav = this.favoriteIds.has(`${movie.sourceId}:${movie.stream_id}`);
@@ -289,10 +289,10 @@ class MoviesPage {
<img src="${poster}" alt="${movie.name}"
onerror="this.onerror=null;this.src='/img/placeholder.png'" loading="lazy">
<div class="movie-play-overlay">
<span class="play-icon">▶</span>
<span class="play-icon">${Icons.play}</span>
</div>
<button class="favorite-btn ${isFav ? 'active' : ''}" title="${isFav ? 'Remove from Favorites' : 'Add to Favorites'}">
<span class="fav-icon">♥</span>
<span class="fav-icon">${Icons.favorite}</span>
</button>
</div>
<div class="movie-info">
+4 -4
View File
@@ -290,7 +290,7 @@ class SeriesPage {
const poster = series.cover || '/img/placeholder.png';
const year = series.year || series.releaseDate?.substring(0, 4) || '';
const rating = series.rating ? `⭐ ${series.rating}` : '';
const rating = series.rating ? `${Icons.star} ${series.rating}` : '';
const isFav = this.favoriteIds.has(`${series.sourceId}:${series.series_id}`);
@@ -299,10 +299,10 @@ class SeriesPage {
<img src="${poster}" alt="${series.name}"
onerror="this.onerror=null;this.src='/img/placeholder.png'" loading="lazy">
<div class="series-play-overlay">
<span class="play-icon">▶</span>
<span class="play-icon">${Icons.play}</span>
</div>
<button class="favorite-btn ${isFav ? 'active' : ''}" title="${isFav ? 'Remove from Favorites' : 'Add to Favorites'}">
<span class="fav-icon">♥</span>
<span class="fav-icon">${Icons.favorite}</span>
</button>
</div>
<div class="series-card-info">
@@ -375,7 +375,7 @@ class SeriesPage {
html += `
<div class="season-group">
<div class="season-header">
<span class="season-expander">▼</span>
<span class="season-expander">${Icons.chevronDown}</span>
<span class="season-name">Season ${seasonNum} (${episodes.length} episodes)</span>
</div>
<div class="episode-list">