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,2 @@
schema: spec-driven
created: 2026-02-10

View File

@@ -0,0 +1,148 @@
## Context
This change adds a light-weight, SEO-focused website to grow social discovery and conversions for:
- YouTube channel (`youtube.com/santhoshj`)
- Instagram (`@santhoshjanan`)
- Podcast ("Irregular Mind") on major platforms
The site needs to:
- Be fast and crawlable (SEO-first).
- Surface "latest" content and "high-performing" videos prominently on the home page.
- Provide clear CTAs to convert site visitors into followers/subscribers/listeners.
- Provide analytics (target: Umami) and event tracking to measure progress against 10% month-over-month goals.
Constraints and unknowns:
- Current repo appears to contain only OpenSpec artifacts (no existing website stack captured here yet).
- Instagram "dynamic" ingestion is constrained by Meta platform rules and API availability; we should plan for a fallback.
- "High-performing" requires a definition (views/likes/comments/watch-time) and may require YouTube Data API access.
## Goals / Non-Goals
**Goals:**
- Ship a static-first website architecture that is very fast, SEO-friendly, and easy to operate.
- Ingest content from YouTube + podcast RSS, and support Instagram via API where feasible or via embed/fallback.
- Provide a normalized content model usable by homepage modules (latest + featured/high-performing).
- Provide reusable CTAs with trackable outbound clicks and conversion measurement via Umami.
- Provide clear, testable SEO outputs: indexable pages, metadata, sitemap/robots, social sharing cards.
**Non-Goals:**
- Building a full CMS/editorial workflow (no admin UI in scope initially).
- Real-time updates (seconds/minutes). Near-real-time is not required; periodic refresh is acceptable.
- Complex personalization/recommendation engines.
- Replacing platform-native analytics; the goal is directional measurement for acquisition/conversion.
## Decisions
### 1) Static-first framework and deployment
**Decision:** Use a static-first site generator (Astro is the default recommendation) and deploy on linode instance over docker container.
**Rationale:** Static output maximizes performance and SEO while minimizing operational complexity. Astro provides great HTML-by-default output and only hydrates where necessary, keeping the "light-weight" premise intact.
**Alternatives considered:**
- Next.js: good ecosystem, but easier to accidentally ship heavier runtime/hydration than needed.
- Pure static HTML/manual: too brittle and hard to keep "dynamic" content updated.
### 2) Content ingestion model (build-time with scheduled refresh)
**Decision:** Fetch and normalize social content at build time (SSG) into a local data cache (JSON) that drives rendering. Refresh content on a schedule (cron) and on-demand (manual trigger).
**Rationale:** Keeps runtime simple and fast; avoids exposing API keys to the browser; avoids rate-limit issues on every pageview.
**Mechanics:**
- A build step runs `fetch-content` to produce a normalized dataset (e.g., `content/cache/*.json`).
- CI triggers rebuild on a schedule (e.g., hourly or daily) and after new uploads (manual trigger).
**Alternatives considered:**
- Runtime serverless fetch: more moving parts, harder caching story, slower page loads, and higher risk of API failures impacting the site.
- Client-side fetch: worst for SEO and exposes third-party dependence to users.
### 3) Sources and fallbacks (YouTube, Instagram, podcast)
**YouTube**
- **Decision:** Prefer YouTube Data API (v3) when available to obtain reliable metadata and performance stats (views/likes) for "high-performing".
- **Fallback:** Use the channel RSS feed to list latest videos if API keys are not available; in this mode, "high-performing" becomes manually curated or based on a static list.
**Instagram**
- **Decision:** Start with an embed-first approach for Instagram posts (oEmbed or platform embeds) and treat API-based ingestion as optional Phase 2.
- **Rationale:** Meta API access is frequently the longest pole (app review, token refresh, account type constraints). Embeds still allow reuse of posts without deep integration.
**Podcast**
- **Decision:** Use the podcast RSS feed as the canonical source; parse episodes into the normalized model. Link out to platform destinations (Apple/Spotify/etc).
### 4) Normalized content model and ranking
**Decision:** Define a unified content schema for the site (regardless of source), e.g.:
- `id`, `source` (`youtube|instagram|podcast`), `url`
- `title`, `description`, `publishedAt`
- `thumbnail`, `tags/categories`
- `metrics` (optional: `views`, `likes`, `comments`, `duration`)
- `featured` (manual override)
**High-performing definition:**
- Default: YouTube videos ranked by `views` (optionally recency-weighted), with a manual override list for editorial selection.
- Instagram and podcast "high-performing" starts as manual until reliable metrics are available.
### 5) Information architecture and SEO surface
**Decision:** Create a small set of indexable pages with stable URLs:
- `/` home (latest + featured/high-performing + channel highlights + CTAs)
- `/videos` (YouTube list/detail, or list only initially)
- `/podcast` (episode list)
- `/about` (channel/personal bio, links)
**SEO outputs:**
- Per-page metadata (title/description), canonical URLs
- Open Graph + Twitter cards
- `sitemap.xml` and `robots.txt`
- Structured data (`JSON-LD`) for Video/Podcast where appropriate
### 6) Conversion CTAs and event tracking
**Decision:** Implement CTAs as a reusable component that:
- Renders platform-specific primary actions (YouTube subscribe, Instagram follow, podcast listen)
- Uses outbound links that include UTM parameters
- Emits Umami events for `cta_click` with dimensions like `platform`, `placement`, `target`
### 7) Analytics (Umami) and measurement plan
**Decision:** Use Umami for pageviews + custom events.
**Events (initial):**
- `cta_click` (platform, placement)
- `outbound_click` (domain, placement)
- Optional: `video_play` / `episode_play` if embeds support reliable hooks (may be limited)
**Reporting targets:**
- Traffic by source (SEO vs social referrers)
- CTA click-through rate by placement
- Growth correlation: site referrals to channel follows (approximate; platform attribution is imperfect)
## Risks / Trade-offs
- [Instagram API complexity] → Start embed-first; treat API ingestion as optional and document requirements for later.
- [YouTube API keys / quotas] → Cache results; minimize calls; RSS fallback; allow manual "featured" list.
- [“High-performing” ambiguity] → Specify a clear initial metric (views) and allow manual overrides; refine after baseline data.
- [Stale content due to build-time fetch] → Scheduled rebuilds; manual trigger; show "Last updated" timestamp.
- [SEO requires real content depth] → Add dedicated indexable pages (videos/podcast/about) and ensure metadata + structured data are correct.
- [Analytics privacy expectations] → Keep tracking minimal; avoid PII; document what is tracked.
## Migration Plan
1. Implement the static-first site skeleton and deploy a minimal version (home + about + analytics).
2. Add ingestion for podcast RSS and YouTube (RSS first, upgrade to API when keys are available).
3. Add homepage modules (latest + featured/high-performing) and CTA placements.
4. Add SEO hardening (sitemap/robots/canonicals/structured data) and validate indexing.
5. Add Instagram embed module (and/or API ingestion if chosen).
6. Iterate on ranking and content presentation based on Umami data.
Rollback:
- Revert deploy to previous static build; ingestion failures should fail closed (serve last good cached dataset).
## Open Questions
- Hosting target: Cloudflare Pages vs Netlify vs Vercel (affects cron/rebuild approach and any serverless options).
- Do you want a custom domain (and which), and should the site target a personal brand name vs channel name?
- Is a YouTube Data API key available, or should we start with RSS-only for v1?
- Should Instagram be embed-only for v1, or do you want to pursue API ingestion (which may require a business account/app setup)?
- Preferred measurement: which CTAs matter most (YouTube subs vs Instagram follows vs podcast listens) and where should they be placed (hero, sticky, end of sections)?

View File

@@ -0,0 +1,30 @@
## Why
Grow channel discovery and conversions by giving search engines a fast, crawlable home for SanthoshJ content (YouTube, Instagram, podcast) with clear next actions. This is needed now to drive at least 10% month-over-month growth in both followers and engagement across platforms.
## What Changes
- Add a light-weight, SEO-focused website that aggregates and showcases content across YouTube, Instagram, and the Irregular Mind podcast.
- Dynamically display newest content and highlight high-performing videos prominently on the home page.
- Add prominent conversion CTAs (subscribe/follow/listen) throughout the site to drive measurable conversions from site visits.
- Add analytics instrumentation (target: Umami) to measure traffic sources, content performance, and conversion events.
## Capabilities
### New Capabilities
- `social-content-aggregation`: Pull and normalize content metadata from YouTube, Instagram, and the podcast feed so the website can render "latest" and "featured" content.
- `homepage-content-modules`: Define and render homepage sections for newest content, high-performing videos, and channel/podcast highlights (with graceful empty/loading states).
- `seo-content-surface`: Provide SEO fundamentals (metadata, indexable pages, sitemaps) so content can be discovered via search and shared cleanly on social.
- `conversion-ctas`: Provide reusable, configurable CTAs (YouTube subscribe, Instagram follow, podcast listen) and trackable outbound links.
- `analytics-umami`: Instrument pageviews and key events (CTA clicks, outbound conversions) and support configuration for Umami.
### Modified Capabilities
<!-- None (no existing specs yet) -->
## Impact
- Website codebase: new pages/components for homepage, content listings, and SEO metadata; additional build/runtime configuration to support dynamic content ingestion.
- External integrations: YouTube (Data API or feeds), Instagram (embed/Graph/API constraints), podcast RSS feed parsing; may require API keys and rate-limit handling.
- Analytics/deployment: add Umami script/config and define event taxonomy; ensure privacy expectations and consistent environments (dev/staging/prod).

View File

@@ -0,0 +1,33 @@
## ADDED Requirements
### Requirement: Umami pageview tracking
When Umami is enabled by configuration, the site MUST load Umami tracking on all indexable pages and MUST record pageviews.
When Umami is disabled or not configured, the site MUST still function and MUST NOT error in the browser due to missing analytics.
#### Scenario: Umami enabled
- **WHEN** Umami is enabled by configuration
- **THEN** the site includes the Umami script on `/`, `/videos`, `/podcast`, and `/about`
#### Scenario: Umami disabled
- **WHEN** Umami is not configured
- **THEN** the site renders normally and no analytics script is required
### Requirement: Custom event tracking
When Umami is enabled, the site MUST support custom event emission for:
- `cta_click`
- `outbound_click`
Each emitted event MUST include enough properties to segment reports by platform and placement when applicable.
#### Scenario: Emit outbound click event
- **WHEN** a user clicks a non-CTA outbound link from the homepage
- **THEN** the system emits an `outbound_click` event with a property identifying the destination domain
### Requirement: Environment configuration
The site MUST support configuration of Umami parameters (at minimum: website ID and script URL) without requiring code changes.
#### Scenario: Configure Umami via environment
- **WHEN** Umami configuration values are provided via environment or config file
- **THEN** the site uses those values to initialize analytics without modifying source code

View File

@@ -0,0 +1,35 @@
## ADDED Requirements
### Requirement: Reusable CTA component
The site MUST implement CTAs as a reusable component that can render at least the following actions:
- YouTube subscribe action (linking to the channel)
- Instagram follow action (linking to the profile)
- Podcast listen action (linking to a designated destination)
Each CTA MUST be configurable with:
- `platform` (`youtube`, `instagram`, `podcast`)
- `placement` (e.g., `hero`, `module_header`, `footer`)
- destination `url`
#### Scenario: Rendering a YouTube subscribe CTA
- **WHEN** the homepage includes a CTA with `platform: youtube`
- **THEN** the site renders a visible action that links to the configured YouTube channel destination URL
### Requirement: Trackable outbound links
CTA outbound links MUST support appending UTM parameters so traffic can be attributed in downstream analytics.
#### Scenario: UTM parameters applied
- **WHEN** a CTA is configured with UTM parameters
- **THEN** the rendered outbound link includes the UTM query parameters in its URL
### Requirement: CTA click event emission
CTA clicks MUST emit an analytics event with at least:
- event name `cta_click`
- `platform`
- `placement`
- `target` (destination URL or a stable identifier)
#### Scenario: User clicks CTA
- **WHEN** a user clicks an Instagram follow CTA in the hero placement
- **THEN** the system emits a `cta_click` event with `platform=instagram` and `placement=hero`

View File

@@ -0,0 +1,44 @@
## ADDED Requirements
### Requirement: Homepage modules and ordering
The homepage MUST render distinct content modules that include:
- newest content
- high-performing videos
- channel/podcast highlights
- prominent conversion CTAs
The homepage MUST render modules in a deterministic order configured by the site (not dependent on network timing).
#### Scenario: Homepage render with available data
- **WHEN** content data is available in the cache
- **THEN** the homepage renders the newest module and the high-performing videos module in the configured order
### Requirement: Newest content module
The system MUST compute a "newest" feed across sources by sorting normalized items by `publishedAt` descending.
The system MUST support filtering and limiting the number of items displayed for the newest module.
#### Scenario: Mixed-source newest list
- **WHEN** the cached dataset contains YouTube, Instagram, and podcast items with different publish dates
- **THEN** the newest module lists items ordered by `publishedAt` descending regardless of source
### Requirement: High-performing YouTube videos module
When `metrics.views` is available for YouTube items, the system MUST compute "high-performing" videos by ranking videos by `metrics.views` (descending) with an optional manual override list.
When `metrics.views` is not available, the system MUST render the high-performing module using a manual curated list and MUST NOT fail the page render.
#### Scenario: Views-based ranking available
- **WHEN** YouTube items include `metrics.views`
- **THEN** the high-performing module shows videos ranked by views, unless a manual override list is configured
#### Scenario: Views-based ranking unavailable
- **WHEN** YouTube items do not include `metrics.views`
- **THEN** the high-performing module renders using a manual curated list and the page still loads successfully
### Requirement: Graceful empty and error states
If a module has no content to display, the homepage MUST render a non-broken empty state for that module and MUST still render the rest of the page.
#### Scenario: No Instagram items available
- **WHEN** the cached dataset contains no Instagram items
- **THEN** the Instagram-related module renders an empty state and the homepage still renders other modules

View File

@@ -0,0 +1,48 @@
## ADDED Requirements
### Requirement: Indexable pages
The site MUST provide indexable HTML pages for:
- home (`/`)
- videos (`/videos`)
- podcast (`/podcast`)
- about (`/about`)
These pages MUST be server-rendered or statically generated HTML suitable for search engine crawling (not client-rendered only).
#### Scenario: Crawling the home page
- **WHEN** a crawler requests `/`
- **THEN** the server returns an HTML document containing the homepage content modules and metadata
### Requirement: Metadata and canonical URLs
Each indexable page MUST define:
- a document title
- a meta description
- a canonical URL
#### Scenario: Page metadata is present
- **WHEN** a crawler requests `/videos`
- **THEN** the HTML contains a `<title>`, a meta description, and a canonical URL for `/videos`
### Requirement: Social sharing cards
The site MUST provide Open Graph and Twitter card metadata for indexable pages so shared links render a preview.
#### Scenario: Sharing the home page
- **WHEN** a social crawler requests `/`
- **THEN** the response includes Open Graph and Twitter card tags with a title, description, and image when available
### Requirement: Sitemap and robots
The site MUST provide:
- `sitemap.xml` enumerating indexable pages
- `robots.txt` that allows indexing of indexable pages
#### Scenario: Sitemap is available
- **WHEN** a crawler requests `/sitemap.xml`
- **THEN** the server returns an XML sitemap listing `/`, `/videos`, `/podcast`, and `/about`
### Requirement: Structured data
The site MUST support structured data (JSON-LD) for Video and Podcast content when detail pages exist, and MUST ensure the JSON-LD is valid JSON.
#### Scenario: Video structured data present
- **WHEN** a video detail page exists and is requested
- **THEN** the HTML includes JSON-LD describing the video using a recognized schema type

View File

@@ -0,0 +1,67 @@
## ADDED Requirements
### Requirement: Normalized content items
The system MUST normalize all ingested items (YouTube videos, Instagram posts, podcast episodes) into a single internal schema so the website can render them consistently.
The normalized item MUST include at minimum:
- `id` (stable within its source)
- `source` (`youtube`, `instagram`, or `podcast`)
- `url`
- `title`
- `publishedAt` (ISO-8601)
- `thumbnailUrl` (optional)
#### Scenario: Normalizing a YouTube video
- **WHEN** the system ingests a YouTube video item
- **THEN** it produces a normalized item containing `id`, `source: youtube`, `url`, `title`, and `publishedAt`
#### Scenario: Normalizing a podcast episode
- **WHEN** the system ingests a podcast RSS episode
- **THEN** it produces a normalized item containing `id`, `source: podcast`, `url`, `title`, and `publishedAt`
### Requirement: YouTube ingestion with stats when available
The system MUST support ingesting YouTube videos for channel `youtube.com/santhoshj`.
When a YouTube API key is configured, the system MUST ingest video metadata and MUST ingest view count (and MAY ingest likes/comments if available) so "high-performing" can be computed.
When no YouTube API key is configured, the system MUST still ingest latest videos using a non-authenticated mechanism (for example, channel RSS) but MUST omit performance stats.
#### Scenario: API key configured
- **WHEN** a YouTube API key is configured
- **THEN** the system ingests video metadata and includes `metrics.views` for each ingested video when available from the API
#### Scenario: No API key configured
- **WHEN** no YouTube API key is configured
- **THEN** the system ingests latest videos and does not require `metrics.views` to be present
### Requirement: Podcast RSS ingestion
The system MUST ingest the Irregular Mind podcast RSS feed and produce normalized items representing podcast episodes.
#### Scenario: RSS feed fetch succeeds
- **WHEN** the system fetches the podcast RSS feed successfully
- **THEN** it produces one normalized item per episode with `source: podcast`
### Requirement: Instagram content support via embed-first approach
The system MUST support representing Instagram posts for `@santhoshjanan` in the site content surface.
If API-based ingestion is not configured/available, the system MUST support an embed-first representation where the normalized item contains a `url` to the Instagram post and any additional embed metadata needed by the renderer.
#### Scenario: Embed-first mode
- **WHEN** Instagram API ingestion is not configured
- **THEN** the system provides normalized Instagram items that contain a public post `url` suitable for embedding
### Requirement: Refresh and caching
The system MUST cache the latest successful ingestion output and MUST serve the cached data to the site renderer.
The system MUST support periodic refresh on a schedule (at minimum daily) and MUST support a manual refresh trigger.
On ingestion failure, the system MUST continue serving the most recent cached data.
#### Scenario: Scheduled refresh fails
- **WHEN** a scheduled refresh run fails to fetch one or more sources
- **THEN** the site continues to use the most recent successfully cached dataset
#### Scenario: Manual refresh requested
- **WHEN** a manual refresh is triggered
- **THEN** the system attempts ingestion immediately and updates the cache if ingestion succeeds

View File

@@ -0,0 +1,78 @@
## 1. Repo And Tooling Setup
- [x] 1.1 Choose the site framework and initialize the project skeleton (Astro preferred) with TypeScript support
- [x] 1.2 Add a consistent config approach for secrets and runtime settings (env vars for API keys, Umami settings, base URL)
- [x] 1.3 Add basic quality gates (lint/format, typecheck) and a minimal CI workflow
- [x] 1.4 Add Docker build/run for deployment to the Linode instance (multi-stage build, static output served by nginx or equivalent)
## 2. Content Model And Cache
- [x] 2.1 Define the normalized content item schema (id, source, url, title, publishedAt, thumbnailUrl, optional metrics/views)
- [x] 2.2 Implement a content cache file format and location (e.g., `content/cache/*.json`) plus a "last updated" timestamp
- [x] 2.3 Implement cache read utilities used by page rendering, including source filtering and limit controls
- [x] 2.4 Implement "serve last good cache on failure" behavior in the ingestion workflow (write temp then swap)
## 3. YouTube Ingestion
- [x] 3.1 Implement YouTube ingestion via RSS as the baseline (latest videos, no metrics)
- [x] 3.2 Implement optional YouTube Data API ingestion when `YOUTUBE_API_KEY` is configured (include `metrics.views`)
- [x] 3.3 Implement configurable channel identifier for `youtube.com/santhoshj` (document required values)
- [x] 3.4 Add a small set of local fixtures/tests for parsing and normalization (RSS and API response samples)
## 4. Podcast Ingestion
- [x] 4.1 Add configuration for the Irregular Mind podcast RSS URL
- [x] 4.2 Implement podcast RSS fetch + parse to normalized items (one item per episode)
- [x] 4.3 Ensure episode URLs and publish dates normalize correctly across common RSS date formats
- [x] 4.4 Add fixtures/tests for RSS parsing and normalization
## 5. Instagram Support (Embed-First)
- [x] 5.1 Define the minimal normalized representation for Instagram items in embed-first mode (post URL + optional thumbnail)
- [x] 5.2 Implement a configuration-driven list of Instagram post URLs (or a simple feed source) for v1
- [x] 5.3 Implement the renderer for Instagram embeds with a graceful empty state when no items exist
- [x] 5.4 Document a Phase 2 path for API-based ingestion (constraints, account/app requirements, token refresh)
## 6. Build-Time Refresh And Scheduling
- [x] 6.1 Implement a `fetch-content` script/command that ingests all sources and writes the cache
- [x] 6.2 Add a manual refresh mechanism for ops (document how to run on the Linode host)
- [x] 6.3 Add a scheduled refresh plan (cron on Linode or CI scheduler) and document frequency (daily minimum)
- [x] 6.4 Add logging for ingestion runs (success/failure per source, cache write result)
## 7. Pages And SEO Surface
- [x] 7.1 Implement `/` home page scaffold with deterministic module order
- [x] 7.2 Implement `/videos`, `/podcast`, and `/about` indexable pages (static/SSR HTML, not client-only)
- [x] 7.3 Add per-page metadata: title, description, canonical URL
- [x] 7.4 Add Open Graph + Twitter card tags for indexable pages
- [x] 7.5 Implement `sitemap.xml` and `robots.txt` (include `/`, `/videos`, `/podcast`, `/about`)
- [x] 7.6 Add structured data (JSON-LD) scaffolding for Video/Podcast where detail pages exist (ensure valid JSON)
## 8. Homepage Modules
- [x] 8.1 Implement "newest content" feed (sort by `publishedAt` desc across sources, configurable limits)
- [x] 8.2 Implement "high-performing videos" module using `metrics.views` when present
- [x] 8.3 Implement a manual override/curation list for featured/high-performing videos
- [x] 8.4 Implement graceful empty/error states for each module (page never breaks if a module has no data)
## 9. CTAs And Outbound Tracking
- [x] 9.1 Implement a reusable CTA component with `platform`, `placement`, and destination `url`
- [x] 9.2 Add default destinations for YouTube channel, Instagram profile, and podcast listen target(s)
- [x] 9.3 Add UTM parameter support for CTA outbound links
- [x] 9.4 Define CTA placements on homepage (hero + at least one additional placement) and ensure they are visually prominent
## 10. Umami Analytics
- [x] 10.1 Add Umami configuration (website ID + script URL) via environment/config without code changes per environment
- [x] 10.2 Load Umami on all indexable pages when enabled; ensure site works when disabled
- [x] 10.3 Implement `cta_click` event emission with `platform`, `placement`, and `target`
- [x] 10.4 Implement `outbound_click` event emission with destination domain/identifier
## 11. Verification And Launch
- [x] 11.1 Validate SEO basics locally (view-source contains metadata; sitemap/robots served correctly)
- [x] 11.2 Validate performance basics (no blocking API calls at runtime; pages render from cache)
- [x] 11.3 Validate analytics in a staging environment (events show up in Umami; disabled mode has no errors)
- [x] 11.4 Document deployment steps for the Linode Docker setup (build, run, refresh schedule, rollback)