# fast-website

Lightweight, SEO-first website for SanthoshJ that aggregates YouTube + Instagram + podcast content and tracks conversion events via Umami.

## Specs (OpenSpec)

This repo uses OpenSpec (schema: `spec-driven`) to document and ship features.

- Active specs live in `openspec/specs/<spec-name>/spec.md`
- Completed initiatives (proposal/design/tasks + delta specs) live in `openspec/changes/archive/<date>-<change-name>/`

Key public flags (documented in `site/.env.example`):

- `PUBLIC_SITE_URL`: canonical URL base
- `PUBLIC_ENABLE_SW`: set to `"false"` to disable service worker registration
- `PUBLIC_ENABLE_NAV_HOVER_LINE`: set to `"false"` to disable decorative hover-line styling

### Spec Index (Active)

Content + ingestion:

- `social-content-aggregation` (`openspec/specs/social-content-aggregation/spec.md`): normalize YouTube/Instagram/podcast items and refresh via cached ingestion
- `homepage-content-modules` (`openspec/specs/homepage-content-modules/spec.md`): homepage module ordering, newest feed, high-performing videos, and Instagram omission when empty
- `wordpress-content-source` (`openspec/specs/wordpress-content-source/spec.md`): fetch WordPress posts/pages/categories via `wp-json` and write to build cache
- `blog-section-surface` (`openspec/specs/blog-section-surface/spec.md`): `/blog` surface (index/category/detail) + Umami-instrumented navigation
- `seo-content-surface` (`openspec/specs/seo-content-surface/spec.md`): indexable pages, canonical URLs, sitemap + robots, and JSON-LD expectations

Analytics + tracking:

- `interaction-tracking-taxonomy` (`openspec/specs/interaction-tracking-taxonomy/spec.md`): required Umami event attributes, `target_id` namespaces, and modal interaction events
- `analytics-umami` (`openspec/specs/analytics-umami/spec.md`): Umami enable/disable behavior + supported custom events
- `conversion-ctas` (`openspec/specs/conversion-ctas/spec.md`): reusable CTA surface + UTM support + `cta_click` tracking

UI + UX shell:

- `wcag-responsive-ui` (`openspec/specs/wcag-responsive-ui/spec.md`): responsive nav shell, focus-visible baseline, reduced motion, and semantic element rules
- `card-layout-system` (`openspec/specs/card-layout-system/spec.md`): standardized card information architecture + modal-trigger behavior for video/podcast cards
- `media-modal` (`openspec/specs/media-modal/spec.md`): `<dialog>`-based media preview modal requirements (focus, playback stop, crawlable CTAs)
- `image-lazy-loading` (`openspec/specs/image-lazy-loading/spec.md`): shimmer placeholders + fade-in + failure handling + reduced-motion behavior
- `site-theming` (`openspec/specs/site-theming/spec.md`): `data-theme` application, defaults, and persistence rules (localStorage + cookie fallback)
- `theme-switcher-notch` (`openspec/specs/theme-switcher-notch/spec.md`): floating theme notch placement, interaction, and accessibility requirements
- `navbar-branding` (`openspec/specs/navbar-branding/spec.md`): header logo + centered brand layout
- `nav-hover-line` (`openspec/specs/nav-hover-line/spec.md`): decorative hover-line treatment for header titles + key surface titles (flagged)

Performance + deployment:

- `service-worker-performance` (`openspec/specs/service-worker-performance/spec.md`): production SW registration + runtime caching + safe updates
- `responsive-image-delivery` (`openspec/specs/responsive-image-delivery/spec.md`): explicit dimensions + deterministic image behavior for quality gates
- `lighthouse-quality-gate` (`openspec/specs/lighthouse-quality-gate/spec.md`): deterministic Lighthouse runner + 100-score assertion
- `docker-content-refresh` (`openspec/specs/docker-content-refresh/spec.md`): Docker-only host refresh/update workflow (no Node.js on server)
- `cache-layer` (`openspec/specs/cache-layer/spec.md`): Redis-backed shared cache + TTL + manual cache clear

### Quality Gates

- Site build: `npm -C site run build`
- Lighthouse gate docs: `site/docs/lighthouse.md` (scripts: `npm -C site run lighthouse:run`, `npm -C site run verify:lighthouse`)

### Completed Initiatives (Archived)

Each archived initiative includes `proposal.md`, `design.md`, `tasks.md`, and any delta specs used for that change.

| Change | Focus |
|---|---|
| `2026-02-10-dynamic-homepage-social-acquisition` | Initial SEO-first site: content aggregation, homepage modules, CTAs, and analytics |
| `2026-02-10-better-tracking` | Site-wide click tracking taxonomy aligned to Umami |
| `2026-02-10-custom-events-umami` | Expand Umami custom event coverage + standardize event properties |
| `2026-02-10-blog-umami-fix` | Fix and verify blog surface Umami instrumentation |
| `2026-02-10-better-cache` | Add Redis cache layer + TTL + manual clear; wire into ingestion |
| `2026-02-10-card-layout` | Standardize card layout across surfaces |
| `2026-02-10-lazy-loading` | Add shimmer placeholders and reduced-motion-safe image loading UX |
| `2026-02-10-blogs-section` | Add WordPress-backed blog section + routes + secondary nav |
| `2026-02-10-hide-ig-if-no-data` | Omit Instagram module when dataset is empty |
| `2026-02-10-reduce-bounce-rate` | Add in-page media modal previews for video/podcast cards |
| `2026-02-10-service-workers` | Add service worker caching for repeat-visit performance |
| `2026-02-10-deploy-without-node` | Docker-only host refresh/update workflow |
| `2026-02-10-fix-sub-pages` | Fix static serving so `/videos`, `/podcast`, `/about` do not 404 |
| `2026-02-10-wcag-responsive` | WCAG baseline + responsive nav shell + typography/background fixes |
| `2026-02-10-lighthouse-fixes` | Lighthouse cleanup + deterministic 100/100/100/100 gate |
| `2026-02-11-dch-theming` | Dark/light/high-contrast themes + theme notch UI |
| `2026-02-11-remember-theme` | Persist theme across visits + emit theme switch tracking |
| `2026-02-11-final-touches` | Header branding polish + hover-line treatment behind env flag |

## Local Setup

```bash
cd site
cp .env.example .env
npm install
npm run fetch-content
npm run dev
```

Open `http://localhost:4321`.

## Configuration

All configuration is via environment variables (see `site/.env.example`).

### YouTube

- `YOUTUBE_CHANNEL_ID` is required for ingestion.
- Optional: `YOUTUBE_API_KEY` enables view-count ingestion for high-performing ranking.

Notes:
- The channel ID looks like `UC...` and can be found by viewing the channel page source or via YouTube Studio settings.
- If `YOUTUBE_API_KEY` is not set, ingestion falls back to the public channel RSS feed and metrics will be missing.

### Podcast

- `PODCAST_RSS_URL` is required to ingest episodes.

### Instagram (Embed-First)

Edit `site/content/instagram-posts.json`:

```json
{
  "posts": [
    { "url": "https://www.instagram.com/p/<id>/", "publishedAt": "2026-02-01T00:00:00Z", "title": "Movie post" }
  ]
}
```

Phase 2 (API ingestion) path:
- Requires Meta developer app setup, correct account type, token issuance + refresh, and platform policy compliance.
- Recommended approach: keep embed-first for v1, then add an optional API ingest module when constraints are clear.

### Curation / Featured Videos

If YouTube metrics are not available, curate high-performing videos in `site/content/featured-videos.json`:

```json
{ "videoIds": ["abc123", "def456"] }
```

## Analytics (Umami)

Set:
- `PUBLIC_UMAMI_SCRIPT_URL`
- `PUBLIC_UMAMI_WEBSITE_ID`

If either is missing, analytics is disabled and the site still functions.

Event tracking is implemented using Umami's `data-umami-event` and `data-umami-event-*` attributes.

## Click Tracking Taxonomy (Umami-Compatible)

All tracked clickables MUST set:
- `data-umami-event`: the event name (max 50 chars), e.g. `click`, `cta_click`, `outbound_click`
- `data-umami-event-target_id`: stable unique identifier for the clickable element (namespaced like `nav.*`, `cta.*`, `card.*`)
- `data-umami-event-placement`: where the clickable appears (e.g. `nav`, `hero`, `section_header`, `home.newest`)

For links, also set:
- `data-umami-event-target_url`: destination URL (can be relative for internal navigation)

Common additional properties:
- `data-umami-event-domain`: destination domain (for outbound links)
- `data-umami-event-source`: `youtube` / `instagram` / `podcast` where applicable
- `data-umami-event-ui_placement`: a sub-placement for UI grouping (e.g. `content_card`)

Instrumentation checklist:
- Every clickable should have a unique `target_id` so it can be segmented in Umami.
- Use categorical ids and placements only (no PII).

## Deployment (Linode + Docker)

The production host is intentionally minimal and only needs Docker (no Node.js on the server).

### Local Docker

```bash
docker compose build
docker compose up -d
```

The container serves the static output on port `8080` (map or proxy as needed).

### Production (Docker-Only Host)

In production, CI builds and publishes a Docker image (nginx serving the static output). The server updates by pulling that image and restarting the service.

Runbook: `deploy/runbook.md`.

### Refreshing Content (Manual, Docker-Only)

Content is fetched at build time into `site/content/cache/content.json` (typically in CI), then packaged into the image.

On the server host:

```bash
./scripts/refresh.sh
```

This:
1. Pulls the latest published image
2. Restarts the service (no build on the host)

### Refreshing Content (Scheduled)

Install a daily cron using `deploy/cron.example` as a starting point.

Rollback:
- Re-deploy a known-good image tag/digest (see `deploy/runbook.md`).
