Skip to content

Commit 6a081ef

Browse files
authored
as
1 parent f70a0aa commit 6a081ef

File tree

3 files changed

+12
-343
lines changed

3 files changed

+12
-343
lines changed

_layouts/default.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<a class="btn btn--primary skip-link" href="#content">Skip to content</a>
77
<div class="container container--wide">
88
{% include header.html %}
9-
{% if page.layout == "home" or page.layout == "resume" %}
9+
{% if page.layout == "home" or page.layout == "archive" %}
1010
<main id="content">
1111
{{ content }}
1212
</main>

_layouts/post.html

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

5-
<!--
6-
================================================================================
7-
POST LAYOUT WITH INTEGRATED VIDEO PLAYER
8-
================================================================================
9-
This layout is designed for course lessons. It checks for a 'videoid' in the
10-
front matter. If found, it displays a custom, lazy-loading YouTube player
11-
before the main content.
12-
-->
13-
145
{% include adsense.html type="leaderboard" %}
156

167
<article class="l-post">
17-
18-
<!-- ============================================= -->
19-
<!-- 1. POST HEADING -->
20-
<!-- ============================================= -->
21-
<div class="post-heading">
8+
<div class="post-heading">
9+
<!-- Breadcrumbs can be made dynamic later with a plugin, for now we can simulate it -->
2210
<ol class="breadcrumb-list post-heading__breadcrumb">
2311
<li><a href="{{ '/' | relative_url }}">Home</a></li>
24-
<li><a href="{{ '/categories/' | relative_url }}#{{ page.category | slugify }}">{{ page.category }}</a></li>
12+
<li><a href="#">{{ page.category }}</a></li>
2513
<li><span aria-current="page">{{ page.title }}</span></li>
2614
</ol>
2715
<h1 class="post-heading__title">{{ page.title }}</h1>
28-
{% if page.description %}
29-
<p class="post-heading__description">{{ page.description }}</p>
30-
{% endif %}
3116
</div>
32-
33-
<!-- ============================================= -->
34-
<!-- 2. LEARNING PATH SIDEBAR -->
17+
<!-- ============================================= -->
18+
<!-- 2. TABLE OF CONTENTS (TOC) - Placeholder -->
3519
<!-- ============================================= -->
3620
<div class="l-post__toc toc" aria-labelledby="toc-title">
37-
<div class="learning-path">
38-
<h2 class="toc__title" id="toc-title">Learning Path</h2>
39-
{% assign sorted_lessons = site.posts | sort: "order" %}
40-
{% assign grouped_by_section = sorted_lessons | group_by: "category" %}
41-
{% for section in grouped_by_section %}
42-
<div class="learning-path__section">
43-
<h3 class="learning-path__category">{{ section.name }}</h3>
44-
<ul class="learning-path__list">
45-
{% for lesson in section.items %}
46-
<li class="learning-path__lesson">
47-
<a href="{{ lesson.url }}" {% if page.url == lesson.url %}class="is-active" aria-current="page"{% endif %}>
48-
<span>{{ lesson.order }} - {{ lesson.title }}</span>
49-
</a>
50-
</li>
51-
{% endfor %}
52-
</ul>
53-
</div>
54-
{% endfor %}
55-
</div>
21+
{% include toc.html html=content h_min=2 h_max=3 %}
5622
</div>
5723

58-
<!-- ============================================= -->
59-
<!-- 3. MAIN CONTENT AREA -->
60-
<!-- ============================================= -->
6124
<div class="l-post__content post-content">
62-
63-
<!-- VIDEO PLAYER (only renders if 'videoid' exists) -->
64-
{% if page.type == "video" and page.videoid %}
65-
<div class="video-player"
66-
id="video-player-container"
67-
data-videoid="{{ page.videoid }}"
68-
data-start="{{ page.start_time | default: 0 }}"
69-
data-end="{{ page.end_time }}">
70-
71-
<!-- 1. The Custom Thumbnail (Visible by default) -->
72-
<div class="video-player__poster" id="video-player-poster">
73-
<div class="video-player__poster-shape-1"></div>
74-
<div class="video-player__poster-shape-2"></div>
75-
76-
<div class="video-player__poster-content">
77-
<p class="video-player__poster-headline">{{ page.category | upcase }}</p>
78-
<h2 class="video-player__poster-title">{{ page.title }}</h2>
79-
</div>
80-
81-
<div class="video-player__play-button">
82-
<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>
83-
</div>
84-
<div class="video-player__completed-badge">
85-
<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>
86-
<span>Lesson Complete</span>
87-
</div>
88-
</div>
89-
90-
<!-- 2. The YouTube Iframe will be created here by JS -->
91-
<div id="video-player-iframe-target"></div>
92-
</div>
93-
94-
<div class="ad ad-under-video">
95-
{% include adsense.html format="horizontal" %}
96-
</div>
97-
{% endif %}
25+
{% include adsense.html type="article" %}
9826

9927
{{ content }}
10028
{% include post-comments.html %}
29+
10130
</div>
10231

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

archive/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Archive
33
description: "Browse through all our posts"
4-
layout: default
4+
layout: base
55
---
66
<style>
77
// =============================================================================

0 commit comments

Comments
 (0)