Skip to content

Commit 3775297

Browse files
committed
added google analytics tag
1 parent d96c882 commit 3775297

5 files changed

Lines changed: 155 additions & 694 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"liveServer.settings.port": 5501
3+
}

app.js

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -40,58 +40,75 @@ function embedUrl(video, opts = {}) {
4040
base.enablejsapi = "1";
4141
base.origin = location.origin;
4242
}
43+
if (opts.autoplay) {
44+
base.autoplay = "1";
45+
}
4346
const params = new URLSearchParams(base);
4447
let path;
4548
if (parsed.videoId) {
4649
path = parsed.videoId;
47-
if (playlistId) params.set("list", playlistId); // play this video within the (unlisted) playlist
48-
} else if (playlistId) {
49-
// Playlist-only: use the playlist embed endpoint
50+
// Do NOT add list= here — it causes YouTube to handle playlist navigation internally,
51+
// which prevents the ENDED event from firing for our auto-advance logic.
52+
// We manage playlist navigation ourselves via setupAutoAdvance.
53+
} else if (playlistId && !parsed.videoId) {
54+
// Playlist-only (no specific video): use the playlist embed endpoint.
5055
return `https://www.youtube-nocookie.com/embed/videoseries?${params.toString()}&list=${encodeURIComponent(playlistId)}`;
5156
} else {
5257
path = "";
5358
}
5459
return `https://www.youtube-nocookie.com/embed/${path}?${params.toString()}`;
5560
}
5661

57-
// Load the YouTube IFrame API once and auto-advance to `nextHash` when the video ends.
58-
let ytApiPromise = null;
59-
function loadYouTubeAPI() {
60-
if (window.YT && window.YT.Player) return Promise.resolve();
61-
if (ytApiPromise) return ytApiPromise;
62-
ytApiPromise = new Promise((resolve) => {
63-
const tag = document.createElement("script");
64-
tag.src = "https://www.youtube.com/iframe_api";
65-
window.onYouTubeIframeAPIReady = () => resolve();
66-
document.head.appendChild(tag);
67-
});
68-
return ytApiPromise;
62+
// Auto-advance: load YT IFrame API once, attach player after iframe loads.
63+
let _ytReady = false;
64+
let _ytReadyCbs = [];
65+
function onYouTubeIframeAPIReady() {
66+
_ytReady = true;
67+
_ytReadyCbs.forEach(fn => fn());
68+
_ytReadyCbs = [];
69+
}
70+
function whenYTReady(fn) {
71+
if (_ytReady) { fn(); return; }
72+
_ytReadyCbs.push(fn);
73+
if (!document.querySelector('script[src*="iframe_api"]')) {
74+
const s = document.createElement("script");
75+
s.src = "https://www.youtube.com/iframe_api";
76+
document.head.appendChild(s);
77+
}
6978
}
7079

71-
function setupAutoAdvance(nextHash, currentVid) {
72-
const iframe = document.getElementById("yt-player");
73-
if (!iframe) return;
74-
loadYouTubeAPI().then(() => {
75-
// Re-check: user may have navigated away before the API loaded.
76-
if (document.getElementById("yt-player") !== iframe) return;
77-
new window.YT.Player("yt-player", {
80+
let _currentPlayer = null;
81+
let _autoplayNext = false; // set true when auto-advancing so next video autoplays
82+
83+
function setupAutoAdvance(nextHash, shouldAutoplay) {
84+
// Destroy previous player instance cleanly.
85+
if (_currentPlayer) {
86+
try { _currentPlayer.destroy(); } catch(e) {}
87+
_currentPlayer = null;
88+
}
89+
90+
whenYTReady(() => {
91+
const iframe = document.getElementById("yt-player");
92+
if (!iframe) return; // user navigated away
93+
_currentPlayer = new YT.Player(iframe, {
7894
events: {
79-
onStateChange: (e) => {
80-
if (e.data === window.YT.PlayerState.ENDED && nextHash) {
81-
// Navigate to next video — render() will re-render the playlist with correct highlight
82-
location.hash = nextHash;
83-
}
84-
if (e.data === window.YT.PlayerState.PLAYING) {
85-
// Ensure the correct playlist item is highlighted (handles edge cases)
86-
document.querySelectorAll(".pl-item").forEach((el) => {
87-
const isActive = el.dataset.vid === currentVid;
88-
el.classList.toggle("active", isActive);
89-
const nowBadge = el.querySelector(".pl-now");
90-
if (nowBadge) nowBadge.style.display = isActive ? "" : "none";
91-
});
95+
onReady: (e) => {
96+
// If this render was triggered by auto-advance, start playing immediately.
97+
if (shouldAutoplay) {
98+
try { e.target.playVideo(); } catch(err) {}
9299
}
93100
},
94-
},
101+
onStateChange: (e) => {
102+
if (e.data === YT.PlayerState.ENDED && nextHash) {
103+
if (location.hash === nextHash) {
104+
render(true); // pass autoplay flag
105+
} else {
106+
_autoplayNext = true;
107+
location.hash = nextHash;
108+
}
109+
}
110+
}
111+
}
95112
});
96113
});
97114
}
@@ -103,13 +120,13 @@ function thumbUrl(video) {
103120
: "https://img.youtube.com/vi/videoseries/hqdefault.jpg";
104121
}
105122

106-
function render() {
123+
function render(forceAutoplay) {
107124
const hash = location.hash.slice(1); // e.g. /product/crm/video/crm-1
108125
const parts = hash.split("/").filter(Boolean);
109126
const isHome = parts.length === 0 || parts[0] !== "product";
110127
document.querySelector(".navbar").classList.toggle("is-home", isHome);
111128
if (parts[0] === "product" && parts[2] === "video") {
112-
renderVideo(parts[1], parts[3]);
129+
renderVideo(parts[1], parts[3], forceAutoplay || _autoplayNext);
113130
} else if (parts[0] === "product") {
114131
renderDashboard(parts[1]);
115132
} else {
@@ -125,9 +142,10 @@ function renderHome() {
125142
<div class="icon">${p.iconSvg ? `<img src="${p.iconSvg}" alt="${esc(p.name)}" style="width:32px;height:32px;object-fit:contain;" />` : p.icon}</div>
126143
<h3>${esc(p.name)}</h3>
127144
<p>${esc(p.tagline)}</p>
128-
<div class="count">${p.videos.length} video${p.videos.length === 1 ? "" : "s"} &rarr;</div>
145+
${p.videos && p.videos.length > 0 ? `<div class="count">${p.videos.length} video${p.videos.length === 1 ? "" : "s"} &rarr;</div>` : ""}
129146
</div>`
130147
).join("");
148+
131149
app.innerHTML = `
132150
<div class="home-hero-wrap fade">
133151
<div class="hero-orb orb-1"></div>
@@ -197,7 +215,7 @@ function renderDashboard(pid) {
197215
<div class="grid fade">${cards}</div>`;
198216
}
199217

200-
function renderVideo(pid, vid) {
218+
function renderVideo(pid, vid, autoplay) {
201219
const p = PRODUCTS.find((x) => x.id === pid);
202220
const idx = p ? p.videos.findIndex((x) => x.id === vid) : -1;
203221
const v = idx >= 0 ? p.videos[idx] : null;
@@ -236,7 +254,7 @@ function renderVideo(pid, vid) {
236254
</div>
237255
<div class="detail fade">
238256
<div class="glass player-wrap">
239-
<iframe id="yt-player" src="${embedUrl(v, { jsapi: true })}" title="${esc(v.title)}" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
257+
<iframe id="yt-player" src="${embedUrl(v, { jsapi: true, autoplay: _autoplayNext })}" title="${esc(v.title)}" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
240258
</div>
241259
<div class="glass panel desc-panel">
242260
<h2>${esc(v.title)}</h2>
@@ -257,9 +275,11 @@ function renderVideo(pid, vid) {
257275
</div>
258276
</div>
259277
</div>`;
260-
// Auto-advance to the next video when this one ends.
278+
// Consume the autoplay flag (reset after use so manual navigation doesn't autoplay).
279+
_autoplayNext = false;
280+
// Auto-advance to the next video when this one ends; pass autoplay flag to start playing.
261281
const next = p.videos[idx + 1];
262-
setupAutoAdvance(next ? `#/product/${p.id}/video/${next.id}` : null, v.id);
282+
setupAutoAdvance(next ? `#/product/${p.id}/video/${next.id}` : null, !!autoplay);
263283
// Keep the active playlist item in view.
264284
const active = document.querySelector(".pl-item.active");
265285
if (active) active.scrollIntoView({ block: "nearest" });

0 commit comments

Comments
 (0)