Skip to content

Commit 34fdb26

Browse files
authored
as
1 parent 6a081ef commit 34fdb26

File tree

1 file changed

+317
-10
lines changed

1 file changed

+317
-10
lines changed

_layouts/post.html

Lines changed: 317 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,341 @@
11
---
2-
layout: default
2+
layout: default
33
---
44

5+
<!--
6+
================================================================================
7+
UNIFIED POST LAYOUT
8+
================================================================================
9+
This layout intelligently adapts based on the post's front matter.
10+
- If 'videoid' is present, it shows the video player and learning path.
11+
- Otherwise, it shows a standard blog post with a Table of Contents.
12+
-->
13+
514
{% include adsense.html type="leaderboard" %}
615

716
<article class="l-post">
8-
<div class="post-heading">
9-
<!-- Breadcrumbs can be made dynamic later with a plugin, for now we can simulate it -->
17+
18+
<!-- ============================================= -->
19+
<!-- 1. POST HEADING (Same for all post types) -->
20+
<!-- ============================================= -->
21+
<div class="post-heading">
1022
<ol class="breadcrumb-list post-heading__breadcrumb">
1123
<li><a href="{{ '/' | relative_url }}">Home</a></li>
12-
<li><a href="#">{{ page.category }}</a></li>
24+
<li><a href="{{ '/categories/' | relative_url }}#{{ page.category | slugify }}">{{ page.category }}</a></li>
1325
<li><span aria-current="page">{{ page.title }}</span></li>
1426
</ol>
1527
<h1 class="post-heading__title">{{ page.title }}</h1>
28+
{% if page.description %}
29+
<p class="post-heading__description">{{ page.description }}</p>
30+
{% endif %}
1631
</div>
17-
<!-- ============================================= -->
18-
<!-- 2. TABLE OF CONTENTS (TOC) - Placeholder -->
32+
33+
<!-- ============================================= -->
34+
<!-- 2. CONDITIONAL SIDEBAR (TOC or Learning Path) -->
1935
<!-- ============================================= -->
2036
<div class="l-post__toc toc" aria-labelledby="toc-title">
21-
{% include toc.html html=content h_min=2 h_max=3 %}
37+
38+
{% if page.videoid and page.type == 'video' %}
39+
<!-- RENDER LEARNING PATH FOR VIDEO POSTS -->
40+
<div class="learning-path">
41+
<h2 class="toc__title" id="toc-title">Learning Path</h2>
42+
{% assign sorted_lessons = site.posts | sort: "order" %}
43+
{% assign grouped_by_section = sorted_lessons | group_by: "category" %}
44+
{% for section in grouped_by_section %}
45+
<div class="learning-path__section">
46+
<h3 class="learning-path__category">{{ section.name }}</h3>
47+
<ul class="learning-path__list">
48+
{% for lesson in section.items %}
49+
<li class="learning-path__lesson">
50+
<a href="{{ lesson.url | relative_url }}" {% if page.url == lesson.url %}class="is-active" aria-current="page"{% endif %}>
51+
<span>{{ lesson.order }} - {{ lesson.title }}</span>
52+
</a>
53+
</li>
54+
{% endfor %}
55+
</ul>
56+
</div>
57+
{% endfor %}
58+
</div>
59+
{% else %}
60+
<!-- RENDER TABLE OF CONTENTS FOR STANDARD POSTS -->
61+
{% include toc.html html=content h_min=2 h_max=3 %}
62+
{% endif %}
63+
2264
</div>
2365

66+
<!-- ============================================= -->
67+
<!-- 3. MAIN CONTENT AREA -->
68+
<!-- ============================================= -->
2469
<div class="l-post__content post-content">
25-
{% include adsense.html type="article" %}
70+
71+
{% if page.videoid and page.type == 'video' %}
72+
<!-- RENDER VIDEO PLAYER -->
73+
<div class="video-player"
74+
id="video-player-container"
75+
data-videoid="{{ page.videoid }}"
76+
data-start="{{ page.start_time | default: 0 }}"
77+
data-end="{{ page.end_time }}">
78+
79+
<div class="video-player__poster" id="video-player-poster">
80+
<div class="video-player__poster-shape-1"></div>
81+
<div class="video-player__poster-shape-2"></div>
82+
<div class="video-player__poster-content">
83+
<p class="video-player__poster-headline">{{ page.category | upcase }}</p>
84+
<h2 class="video-player__poster-title">{{ page.title }}</h2>
85+
</div>
86+
<div class="video-player__play-button">
87+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8,5.14V19.14L19,12.14L8,5.14Z" /></svg>
88+
</div>
89+
<div class="video-player__completed-badge">
90+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z" /></svg>
91+
<span>Lesson Complete</span>
92+
</div>
93+
</div>
94+
<div id="video-player-iframe-target"></div>
95+
</div>
96+
<div class="ad ad-under-video">
97+
{% include adsense.html format="horizontal" %}
98+
</div>
99+
{% else %}
100+
<!-- RENDER STANDARD IN-ARTICLE AD -->
101+
{% include adsense.html type="article" %}
102+
{% endif %}
26103

27104
{{ content }}
28105
{% include post-comments.html %}
29-
30106
</div>
31107

32108
</article>
109+
33110
{% include adsense.html type="multiplex" %}
34-
{% include post-paginate.html %}
111+
{% include post-paginate.html %}
112+
113+
<!-- ===================================================================== -->
114+
<!-- EMBEDDED CSS & JAVASCRIPT FOR THE VIDEO PLAYER (Unchanged) -->
115+
<!-- ===================================================================== -->
116+
<style>
117+
/* --- Video Player Styles --- */
118+
.video-player {
119+
position: relative;
120+
padding-bottom: 56.25%; /* 16:9 aspect ratio */
121+
height: 0;
122+
overflow: hidden;
123+
background-color: #000;
124+
border-radius: var(--spruce-border-radius-lg);
125+
margin-block-end: 2rem;
126+
border: 1px solid var(--spruce-base-color-border);
127+
}
128+
.video-player iframe {
129+
position: absolute;
130+
top: 0;
131+
left: 0;
132+
width: 100%;
133+
height: 100%;
134+
border: 0;
135+
}
136+
.ad-under-video { margin-block: 2rem 2.5rem; }
137+
138+
/* --- Custom Thumbnail Design (using SpruceCSS variables) --- */
139+
.video-player__poster {
140+
position: absolute;
141+
top: 0;
142+
left: 0;
143+
width: 100%;
144+
height: 100%;
145+
z-index: 10;
146+
cursor: pointer;
147+
background-color: var(--spruce-base-color-code-background);
148+
display: flex;
149+
justify-content: center;
150+
align-items: center;
151+
font-family: var(--spruce-font-family-heading);
152+
transition: opacity 0.3s ease-in-out;
153+
overflow: hidden;
154+
}
155+
.video-player__poster.is-hidden { display: none; }
156+
157+
/* Decorative Shapes */
158+
.video-player__poster-shape-1, .video-player__poster-shape-2 {
159+
position: absolute;
160+
border-radius: 50%;
161+
mix-blend-mode: multiply;
162+
opacity: 0.6;
163+
}
164+
.video-player__poster-shape-1 {
165+
width: 45%;
166+
height: 70%;
167+
background: var(--spruce-base-color-secondary);
168+
top: -20%;
169+
left: -10%;
170+
}
171+
.video-player__poster-shape-2 {
172+
width: 40%;
173+
height: 60%;
174+
background: var(--spruce-base-color-primary);
175+
bottom: -25%;
176+
right: -15%;
177+
}
178+
[data-theme-mode=dark] .video-player__poster-shape-1,
179+
[data-theme-mode=dark] .video-player__poster-shape-2 {
180+
mix-blend-mode: screen;
181+
opacity: 0.5;
182+
}
183+
184+
/* Text Content */
185+
.video-player__poster-content {
186+
position: relative;
187+
z-index: 2;
188+
text-align: center;
189+
color: var(--spruce-base-color-heading);
190+
padding: 1rem;
191+
}
192+
.video-player__poster-headline {
193+
font-size: clamp(0.8rem, 1.5vw, 1rem);
194+
font-weight: 700;
195+
letter-spacing: 3px;
196+
opacity: 0.8;
197+
color: var(--spruce-base-color-primary);
198+
}
199+
.video-player__poster-title {
200+
font-size: clamp(1.5rem, 3.5vw, 2.5rem);
201+
font-weight: 700;
202+
line-height: 1.2;
203+
margin-top: 0.5rem;
204+
max-width: 25ch;
205+
}
206+
207+
/* Play Button */
208+
.video-player__play-button {
209+
position: absolute;
210+
z-index: 3;
211+
top: 50%;
212+
left: 50%;
213+
transform: translate(-50%, -50%);
214+
color: var(--spruce-btn-color-primary-foreground);
215+
background-color: var(--spruce-btn-color-primary-background);
216+
width: clamp(50px, 10vw, 64px);
217+
height: clamp(50px, 10vw, 64px);
218+
border-radius: 50%;
219+
display: flex;
220+
justify-content: center;
221+
align-items: center;
222+
transition: all 0.2s ease;
223+
box-shadow: var(--spruce-box-shadow);
224+
}
225+
.video-player__play-button svg {
226+
width: 50%; height: 50%;
227+
}
228+
.video-player__poster:hover .video-player__play-button {
229+
transform: translate(-50%, -50%) scale(1.1);
230+
background-color: var(--spruce-btn-color-primary-background-hover);
231+
}
232+
233+
/* Completed Badge */
234+
.video-player__completed-badge {
235+
position: absolute;
236+
z-index: 12;
237+
display: none;
238+
align-items: center;
239+
justify-content: center;
240+
transform: translate(-50%, -50%);
241+
top: 50%; left: 50%;
242+
background: hsla(0, 0%, 10%, 0.8);
243+
color: #fff;
244+
padding: 1rem 2rem;
245+
border-radius: 50px;
246+
font-size: clamp(1rem, 2vw, 1.25rem);
247+
font-weight: 600;
248+
backdrop-filter: blur(5px);
249+
}
250+
.video-player__poster.is-completed { cursor: default; }
251+
.video-player__poster.is-completed .video-player__play-button,
252+
.video-player__poster.is-completed .video-player__poster-content { display: none; }
253+
.video-player__poster.is-completed .video-player__completed-badge { display: flex; }
254+
.video-player__completed-badge svg {
255+
width: 1.5em;
256+
height: 1.5em;
257+
margin-right: 0.75rem;
258+
color: var(--spruce-alert-color-success);
259+
}
260+
261+
/* --- Learning Path Sidebar Styles --- */
262+
.learning-path__section { margin-block-end: 2rem; }
263+
.learning-path__category {
264+
font-size: 1rem;
265+
font-weight: 700;
266+
color: var(--spruce-base-color-heading);
267+
margin-block-end: 0.75rem;
268+
}
269+
.learning-path__list { list-style: none; margin: 0; padding: 0; }
270+
.learning-path__lesson a {
271+
display: block;
272+
padding: 0.5rem 1rem;
273+
border-radius: var(--spruce-border-radius-sm);
274+
text-decoration: none;
275+
color: var(--spruce-base-color-text);
276+
line-height: var(--spruce-line-height-sm);
277+
}
278+
.learning-path__lesson a:hover {
279+
background-color: var(--spruce-base-color-border);
280+
color: var(--spruce-base-color-heading);
281+
}
282+
.learning-path__lesson a.is-active {
283+
background-color: var(--spruce-base-color-primary);
284+
color: var(--spruce-btn-color-primary-foreground);
285+
font-weight: 600;
286+
}
287+
</style>
288+
289+
<script>
290+
// This script remains exactly the same as it's already robust.
291+
function onYouTubeIframeAPIReady() {
292+
const playerContainer = document.querySelector('.video-player[data-init-player="true"]');
293+
if (!playerContainer) return;
294+
playerContainer.removeAttribute('data-init-player');
295+
const videoId = playerContainer.dataset.videoid;
296+
const startTime = parseInt(playerContainer.dataset.start, 10);
297+
const endTime = playerContainer.dataset.end ? parseInt(playerContainer.dataset.end, 10) : null;
298+
let player;
299+
let progressChecker;
300+
function cleanupPlayer() {
301+
clearInterval(progressChecker);
302+
if (player && typeof player.destroy === 'function') { player.destroy(); }
303+
const poster = playerContainer.querySelector('.video-player__poster');
304+
poster.classList.remove('is-hidden');
305+
poster.classList.add('is-completed');
306+
}
307+
player = new YT.Player('video-player-iframe-target', {
308+
videoId: videoId,
309+
playerVars: { 'autoplay': 1, 'controls': 1, 'rel': 0, 'start': startTime, 'showinfo': 0, 'modestbranding': 1 },
310+
events: {
311+
'onStateChange': (event) => {
312+
if (event.data === YT.PlayerState.PLAYING && endTime) {
313+
progressChecker = setInterval(() => { if (player.getCurrentTime() >= endTime) { cleanupPlayer(); } }, 1000);
314+
} else if (event.data === YT.PlayerState.ENDED) {
315+
cleanupPlayer();
316+
} else {
317+
clearInterval(progressChecker);
318+
}
319+
}
320+
}
321+
});
322+
}
323+
document.addEventListener('DOMContentLoaded', () => {
324+
const playerContainer = document.getElementById('video-player-container');
325+
const poster = document.getElementById('video-player-poster');
326+
if (!playerContainer || !poster) return;
327+
poster.addEventListener('click', () => {
328+
if (poster.classList.contains('is-completed')) return;
329+
poster.classList.add('is-hidden');
330+
playerContainer.setAttribute('data-init-player', 'true');
331+
if (typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') {
332+
const tag = document.createElement('script');
333+
tag.src = "https://www.youtube.com/iframe_api";
334+
const firstScriptTag = document.getElementsByTagName('script')[0];
335+
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
336+
} else {
337+
onYouTubeIframeAPIReady();
338+
}
339+
});
340+
});
341+
</script>

0 commit comments

Comments
 (0)