Skip to content

Commit 8d82d95

Browse files
feat: update homepage to display two featured videos with captions
1 parent 4d61f32 commit 8d82d95

2 files changed

Lines changed: 57 additions & 16 deletions

File tree

src/data/videos.ts

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,18 +1297,55 @@ export function latestVideos(limit = LATEST_SLUGS.length): Video[] {
12971297
}
12981298

12991299
/**
1300-
* Homepage hero — the single video featured at the top of the homepage, currently the
1301-
* channel's most-viewed video (caption: "most viewed"). DECOUPLED from LATEST_SLUGS on
1302-
* purpose: the most-viewed video is not necessarily the newest upload, so it does NOT belong
1303-
* in the upload-ordered "Latest videos" list. Update this slug when the featured video changes.
1300+
* Homepage hero — the TWO videos featured side-by-side at the top of the homepage.
1301+
* ORDER IS MEANINGFUL: index 0 = LEFT column (desktop) / TOP (mobile stack), index 1 =
1302+
* RIGHT / BOTTOM. Ordered oldest→newest so the mobile stack reads chronologically.
1303+
*
1304+
* Each pick carries its own editorial `caption` because only ONE of the two is the
1305+
* channel's most-viewed video — a shared "most viewed" label would be inaccurate.
1306+
* Keep captions short (they render on one line under a fixed 16:9 thumbnail).
1307+
*
1308+
* DECOUPLED from LATEST_SLUGS on purpose: an evergreen/most-viewed pick is not
1309+
* necessarily a recent upload, so it does NOT belong in "Latest videos".
1310+
* Update these slugs/captions when the featured picks change.
13041311
*/
1305-
export const FEATURED_SLUG = 'getting-started-acb-vscode-hello-world';
1312+
export interface FeaturedVideo {
1313+
video: Video;
1314+
/** Editorial caption rendered beneath the thumbnail (may include an emoji). */
1315+
caption: string;
1316+
}
13061317

1307-
/** Homepage hero video (see FEATURED_SLUG). Throws at build if the slug is stale. */
1308-
export function featuredVideo(): Video {
1309-
const v = getVideo(FEATURED_SLUG);
1310-
if (!v) throw new Error(`FEATURED_SLUG "${FEATURED_SLUG}" not found in VIDEOS`);
1311-
return v;
1318+
const FEATURED: { slug: string; caption: string }[] = [
1319+
{ slug: 'mulesoft-from-start-mulesoft-overview', caption: '👑 most viewed' },
1320+
{ slug: 'getting-started-acb-vscode-hello-world', caption: '🚀 start with ACB' },
1321+
];
1322+
1323+
/**
1324+
* Back-compat literal for scripts/youtube-analytics.mjs, whose parser greps this file for
1325+
* the FEATURED_SLUG assignment below. Keep it a single-quoted string literal (NOT
1326+
* FEATURED[0].slug — the regex needs a literal) AND equal to FEATURED[0].slug, the left /
1327+
* most-viewed pick. The assert below fails the build if the two ever drift. (Don't write the
1328+
* assignment pattern in prose above this line — the regex matches the file's FIRST occurrence.)
1329+
*/
1330+
export const FEATURED_SLUG = 'mulesoft-from-start-mulesoft-overview';
1331+
if (FEATURED_SLUG !== FEATURED[0].slug) {
1332+
throw new Error('FEATURED_SLUG must equal FEATURED[0].slug (kept as a literal for the analytics parser).');
1333+
}
1334+
1335+
/**
1336+
* Homepage hero videos, resolved LEFT→RIGHT (see FEATURED). Throws at build if any slug
1337+
* is stale OR if the count isn't exactly 2, so the two-column layout can't silently degrade.
1338+
*/
1339+
export function featuredVideos(): FeaturedVideo[] {
1340+
const resolved = FEATURED.map(({ slug, caption }) => {
1341+
const video = getVideo(slug);
1342+
if (!video) throw new Error(`FEATURED slug "${slug}" not found in VIDEOS`);
1343+
return { video, caption };
1344+
});
1345+
if (resolved.length !== 2) {
1346+
throw new Error(`FEATURED must contain exactly 2 videos (got ${resolved.length}); the homepage hero is a two-column layout.`);
1347+
}
1348+
return resolved;
13121349
}
13131350

13141351
export function getPlaylist(id: string): Playlist | undefined {

src/pages/index.astro

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import PlaylistRow from '@/components/PlaylistRow.astro';
66
import AiFriendly from '@/components/AiFriendly.astro';
77
import Icon from '@/components/Icon.astro';
88
import { SITE, SOCIAL } from '@/config';
9-
import { videosInPlaylist, latestVideos, featuredVideo, primaryPlaylists, sectionHref } from '@/data/videos';
9+
import { videosInPlaylist, latestVideos, featuredVideos, primaryPlaylists, sectionHref } from '@/data/videos';
1010
1111
const latest = latestVideos(6);
12-
const featured = featuredVideo();
12+
const featured = featuredVideos();
1313
const playlists = primaryPlaylists();
1414
---
1515
<BaseLayout
@@ -27,7 +27,7 @@ const playlists = primaryPlaylists();
2727
]}
2828
>
2929
<!-- Hero -->
30-
<section class="mx-auto w-full max-w-5xl px-4 pt-10 pb-6 text-center sm:px-6 sm:pt-14">
30+
<section class="mx-auto w-full max-w-7xl px-4 pt-10 pb-6 text-center sm:px-6 sm:pt-14">
3131
<h1 class="text-4xl font-extrabold tracking-tight sm:text-6xl">
3232
<span class="text-accent">ProstDev</span>
3333
<span class="text-muted"> | </span>
@@ -38,9 +38,13 @@ const playlists = primaryPlaylists();
3838
and deep dives to level up your integration skills. 🚀
3939
</p>
4040

41-
<div class="mx-auto mt-8 max-w-3xl">
42-
<LiteYouTube youtubeId={featured.youtubeId} title={featured.title} href={`/video/${featured.slug}`} class="shadow-xl" />
43-
<p class="mt-3 text-sm font-medium text-muted">👆 most viewed</p>
41+
<div class="mx-auto mt-8 grid max-w-7xl gap-x-6 gap-y-8 md:grid-cols-2">
42+
{featured.map(({ video, caption }) => (
43+
<div>
44+
<LiteYouTube youtubeId={video.youtubeId} title={video.title} href={`/video/${video.slug}`} class="shadow-xl" />
45+
<p class="mt-3 text-sm font-medium text-muted">{caption}</p>
46+
</div>
47+
))}
4448
</div>
4549

4650
<div class="mt-6 flex flex-wrap items-center justify-center gap-3">

0 commit comments

Comments
 (0)