Initial commit - but way too late.
Some checks failed
ci / site (push) Has been cancelled

This commit is contained in:
2026-02-10 00:22:18 -05:00
commit af112a713c
173 changed files with 27667 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import CtaLink from "../components/CtaLink.astro";
import { LINKS } from "../lib/links";
---
<BaseLayout
title="About | SanthoshJ"
description="About SanthoshJ and where to follow."
canonicalPath="/about"
>
<section class="section">
<div class="section-header">
<h2>About</h2>
<span class="muted">Tech, streaming, movies, travel</span>
</div>
<div class="empty">
<p style="margin-top: 0;">
This is a lightweight site that aggregates my content so it can be discovered via search and
shared cleanly.
</p>
<div class="cta-row">
<CtaLink platform="youtube" placement="about" url={LINKS.youtubeChannel} label="YouTube" />
<CtaLink
platform="instagram"
placement="about"
url={LINKS.instagramProfile}
label="Instagram"
/>
<CtaLink platform="podcast" placement="about" url={LINKS.podcast} label="Podcast" />
</div>
</div>
</section>
</BaseLayout>

238
site/src/pages/index.astro Normal file
View File

@@ -0,0 +1,238 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import CtaLink from "../components/CtaLink.astro";
import ContentCard from "../components/ContentCard.astro";
import InstagramEmbed from "../components/InstagramEmbed.astro";
import { readContentCache } from "../lib/content/cache";
import {
newestItems,
highPerformingYoutubeVideos,
instagramPosts,
podcastEpisodes,
} from "../lib/content/selectors";
import { readFeaturedVideoIds } from "../lib/content/curation";
import { LINKS } from "../lib/links";
const cache = await readContentCache();
const featuredIds = await readFeaturedVideoIds();
const newest = newestItems(cache, 9);
const highPerf = highPerformingYoutubeVideos(cache, 6, featuredIds);
const ig = instagramPosts(cache).slice(0, 6);
const pods = podcastEpisodes(cache)
.slice(0, 6)
.sort((a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt));
---
<BaseLayout
title="SanthoshJ | Tech, streaming, movies, travel"
description="A fast, SEO-first home for videos, movie posts, and the Irregular Mind podcast."
canonicalPath="/"
>
<section class="hero">
<div>
<h1>Fast content. Clear next actions.</h1>
<p>
I post about technology, game streaming, movies, and travel. This site collects the best of
it and points you to the platform you prefer.
</p>
<div class="cta-row">
<CtaLink
platform="youtube"
placement="hero"
url={LINKS.youtubeChannel}
label="Subscribe on YouTube"
class="primary"
/>
<CtaLink
platform="instagram"
placement="hero"
url={LINKS.instagramProfile}
label="Follow on Instagram"
/>
<CtaLink
platform="podcast"
placement="hero"
url={LINKS.podcast}
label="Listen to the podcast"
/>
</div>
<p class="muted" style="margin-top: 14px;">
Last updated: {new Date(cache.generatedAt).toLocaleString()}
</p>
</div>
<div class="empty">
<strong>Goal:</strong> 10% month-over-month growth in followers and engagement.
<br />
<span class="muted"
>This site is the SEO landing surface that turns search traffic into followers.</span
>
</div>
</section>
<section class="section">
<div class="section-header">
<h2>Newest</h2>
<a
class="muted"
href="/videos"
data-umami-event="click"
data-umami-event-target_id="section_header.newest.browse_all"
data-umami-event-placement="section_header"
data-umami-event-target_url="/videos"
>
Browse all →
</a>
</div>
{
newest.length > 0 ? (
<div class="grid">
{newest.map((item) => (
<ContentCard item={item} placement="home.newest" />
))}
</div>
) : (
<div class="empty">
No content cache yet. Run <code>npm run fetch-content</code> in <code>site/</code>.
</div>
)
}
</section>
<section class="section">
<div class="section-header">
<h2>High-performing videos</h2>
<a
class="muted"
href="/videos"
data-umami-event="click"
data-umami-event-target_id="section_header.high_performing.videos"
data-umami-event-placement="section_header"
data-umami-event-target_url="/videos"
>
Videos →
</a>
</div>
{
highPerf.length > 0 ? (
<div class="grid">
{highPerf.map((item) => (
<ContentCard item={item} placement="home.high_performing" />
))}
</div>
) : (
<div class="empty">
No video stats found yet. Add <code>YOUTUBE_API_KEY</code> or curate{" "}
<code>content/featured-videos.json</code>.
</div>
)
}
</section>
<section class="section">
<div class="section-header">
<h2>Instagram</h2>
<a
class="muted"
href={LINKS.instagramProfile}
target="_blank"
rel="noopener noreferrer"
data-umami-event="outbound_click"
data-umami-event-target_id="section_header.instagram.profile"
data-umami-event-placement="section_header"
data-umami-event-target_url={LINKS.instagramProfile}
data-umami-event-domain="www.instagram.com"
data-umami-event-source="instagram"
data-umami-event-ui_placement="section_header"
>
Profile →
</a>
</div>
{
ig.length > 0 ? (
<div class="grid">
{ig.map((item) => (
<InstagramEmbed url={item.url} placement="home.instagram" />
))}
</div>
) : (
<div class="empty">
Add post URLs to <code>content/instagram-posts.json</code> to show Instagram here.
</div>
)
}
</section>
{
ig.length > 0 ? (
<script
is:inline
define:vars={{}}
set:html={`
(function(){
var s = document.createElement('script');
s.async = true;
s.defer = true;
s.src = 'https://www.instagram.com/embed.js';
s.onload = function(){
try { window.instgrm && window.instgrm.Embeds && window.instgrm.Embeds.process(); } catch(e) {}
};
document.head.appendChild(s);
})();
`}
/>
) : null
}
<section class="section">
<div class="section-header">
<h2>Podcast: Irregular Mind</h2>
<a
class="muted"
href="/podcast"
data-umami-event="click"
data-umami-event-target_id="section_header.podcast.episodes"
data-umami-event-placement="section_header"
data-umami-event-target_url="/podcast"
>
Episodes →
</a>
</div>
{
pods.length > 0 ? (
<div class="grid">
{pods.map((item) => (
<ContentCard item={item} placement="home.podcast" />
))}
</div>
) : (
<div class="empty">
Set <code>PODCAST_RSS_URL</code> and run <code>npm run fetch-content</code> to populate
episodes.
</div>
)
}
</section>
<section class="section">
<div class="section-header">
<h2>Follow</h2>
<span class="muted">Pick your platform</span>
</div>
<div class="cta-row">
<CtaLink
platform="youtube"
placement="footer_cta"
url={LINKS.youtubeChannel}
label="YouTube"
/>
<CtaLink
platform="instagram"
placement="footer_cta"
url={LINKS.instagramProfile}
label="Instagram"
/>
<CtaLink platform="podcast" placement="footer_cta" url={LINKS.podcast} label="Podcast" />
</div>
</section>
</BaseLayout>

View File

@@ -0,0 +1,38 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import ContentCard from "../components/ContentCard.astro";
import { readContentCache } from "../lib/content/cache";
import { podcastEpisodes } from "../lib/content/selectors";
const cache = await readContentCache();
const episodes = podcastEpisodes(cache).sort(
(a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt),
);
---
<BaseLayout
title="Podcast | Irregular Mind"
description="Episodes from the Irregular Mind podcast."
canonicalPath="/podcast"
>
<section class="section">
<div class="section-header">
<h2>Irregular Mind</h2>
<span class="muted">{episodes.length} episodes</span>
</div>
{
episodes.length > 0 ? (
<div class="grid">
{episodes.map((item) => (
<ContentCard item={item} placement="podcast.list" />
))}
</div>
) : (
<div class="empty">
No episodes yet. Set <code>PODCAST_RSS_URL</code> and run{" "}
<code>npm run fetch-content</code>.
</div>
)
}
</section>
</BaseLayout>

View File

@@ -0,0 +1,74 @@
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { readContentCache } from "../../lib/content/cache";
import { podcastEpisodes } from "../../lib/content/selectors";
export async function getStaticPaths() {
const cache = await readContentCache();
const eps = podcastEpisodes(cache);
return eps.map((e) => ({ params: { id: e.id }, props: { episode: e } }));
}
const { episode } = Astro.props;
const jsonLd = {
"@context": "https://schema.org",
"@type": "PodcastEpisode",
name: episode.title,
datePublished: episode.publishedAt,
url: episode.url,
};
let episodeDomain = "";
try {
episodeDomain = new URL(episode.url).hostname;
} catch {
episodeDomain = "";
}
---
<BaseLayout
title={`${episode.title} | Irregular Mind`}
description={`Listen: ${episode.title}`}
canonicalPath={`/podcast/${encodeURIComponent(episode.id)}`}
ogImageUrl={episode.thumbnailUrl}
>
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
<section class="section">
<div class="section-header">
<h2>{episode.title}</h2>
<a
class="muted"
href="/podcast"
data-umami-event="click"
data-umami-event-target_id="podcast_detail.back_to_podcast"
data-umami-event-placement="section_header"
data-umami-event-target_url="/podcast"
>
Back to podcast →
</a>
</div>
<div class="empty">
<p style="margin-top: 0;">
This page exists for SEO and sharing. Listening happens on your platform.
</p>
<p>
<a
class="cta primary"
href={episode.url}
target="_blank"
rel="noopener noreferrer"
data-umami-event="outbound_click"
data-umami-event-target_id={`podcast_detail.open.${episode.id}`}
data-umami-event-placement="podcast_detail"
data-umami-event-target_url={episode.url}
data-umami-event-domain={episodeDomain || "unknown"}
data-umami-event-source="podcast"
data-umami-event-ui_placement="podcast_detail"
>
Open episode
</a>
</p>
</div>
</section>
</BaseLayout>

View File

@@ -0,0 +1,38 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import ContentCard from "../components/ContentCard.astro";
import { readContentCache } from "../lib/content/cache";
import { youtubeVideos } from "../lib/content/selectors";
const cache = await readContentCache();
const videos = youtubeVideos(cache).sort(
(a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt),
);
---
<BaseLayout
title="Videos | SanthoshJ"
description="Latest and featured YouTube videos."
canonicalPath="/videos"
>
<section class="section">
<div class="section-header">
<h2>Videos</h2>
<span class="muted">{videos.length} items</span>
</div>
{
videos.length > 0 ? (
<div class="grid">
{videos.map((item) => (
<ContentCard item={item} placement="videos.list" />
))}
</div>
) : (
<div class="empty">
No videos yet. Configure <code>YOUTUBE_CHANNEL_ID</code> and run{" "}
<code>npm run fetch-content</code>.
</div>
)
}
</section>
</BaseLayout>

View File

@@ -0,0 +1,75 @@
---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { readContentCache } from "../../lib/content/cache";
import { youtubeVideos } from "../../lib/content/selectors";
export async function getStaticPaths() {
const cache = await readContentCache();
const videos = youtubeVideos(cache);
return videos.map((v) => ({ params: { id: v.id }, props: { video: v } }));
}
const { video } = Astro.props;
const jsonLd = {
"@context": "https://schema.org",
"@type": "VideoObject",
name: video.title,
uploadDate: video.publishedAt,
thumbnailUrl: video.thumbnailUrl ? [video.thumbnailUrl] : undefined,
url: video.url,
};
let videoDomain = "";
try {
videoDomain = new URL(video.url).hostname;
} catch {
videoDomain = "";
}
---
<BaseLayout
title={`${video.title} | SanthoshJ`}
description={`Watch: ${video.title}`}
canonicalPath={`/videos/${encodeURIComponent(video.id)}`}
ogImageUrl={video.thumbnailUrl}
>
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
<section class="section">
<div class="section-header">
<h2>{video.title}</h2>
<a
class="muted"
href="/videos"
data-umami-event="click"
data-umami-event-target_id="video_detail.back_to_videos"
data-umami-event-placement="section_header"
data-umami-event-target_url="/videos"
>
Back to videos →
</a>
</div>
<div class="empty">
<p style="margin-top: 0;">
This page exists for SEO and sharing. The canonical watch page is YouTube.
</p>
<p>
<a
class="cta primary"
href={video.url}
target="_blank"
rel="noopener noreferrer"
data-umami-event="outbound_click"
data-umami-event-target_id={`video_detail.watch.${video.id}`}
data-umami-event-placement="video_detail"
data-umami-event-target_url={video.url}
data-umami-event-domain={videoDomain || "unknown"}
data-umami-event-source="youtube"
data-umami-event-ui_placement="video_detail"
>
Watch on YouTube
</a>
</p>
</div>
</section>
</BaseLayout>