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