Compare commits

..

5 Commits

Author SHA1 Message Date
dda37a4969 mobile view issue
Some checks failed
publish-image / publish (push) Has been cancelled
ci / site (push) Has been cancelled
2026-02-10 23:55:50 -05:00
2b4c8a79e3 mobile view issue
Some checks failed
ci / site (push) Has been cancelled
publish-image / publish (push) Has been cancelled
2026-02-10 23:50:21 -05:00
65b51d573a nginx tuning
Some checks failed
ci / site (push) Has been cancelled
publish-image / publish (push) Has been cancelled
2026-02-10 23:32:56 -05:00
fd3ebc6115 nginx tuning
Some checks failed
ci / site (push) Has been cancelled
publish-image / publish (push) Has been cancelled
2026-02-10 23:29:50 -05:00
a6e40f8b54 final documentation 2026-02-10 23:13:37 -05:00
4 changed files with 129 additions and 6 deletions

View File

@@ -2,6 +2,84 @@
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

View File

@@ -2,10 +2,17 @@ server {
listen 80;
server_name _;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css text/javascript application/javascript application/json application/xml application/rss+xml image/svg+xml;
root /usr/share/nginx/html;
index index.html;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.instagram.com https://*.instagram.com https://cloud.umami.is https://*.umami.is; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' https: data: blob:; connect-src 'self' https://cloud.umami.is https://*.umami.is; frame-src https://www.youtube.com https://open.spotify.com https://www.instagram.com https://*.instagram.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.instagram.com https://*.instagram.com https://cloud.umami.is https://*.umami.is https://wa.santhoshj.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' https: data: blob:; media-src 'self' https://anchor.fm https://*.anchor.fm https://d3ctxlq1ktw2nl.cloudfront.net; connect-src 'self' https://cloud.umami.is https://*.umami.is https://wa.santhoshj.com; frame-src https://www.youtube.com https://open.spotify.com https://www.instagram.com https://*.instagram.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
@@ -17,6 +24,12 @@ server {
try_files $uri =404;
}
location ~* \.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)$ {
expires 180d;
add_header Cache-Control "public, max-age=15552000" always;
try_files $uri =404;
}
location / {
# Serve directory index pages without requiring a trailing slash.
# This fixes /videos (and similar) resolving to /videos/index.html.

View File

@@ -68,6 +68,24 @@ import { LINKS } from "../lib/links";
let triggerElement = null;
let currentTargetId = null;
function showDialog() {
if (typeof dialog.showModal === "function") {
dialog.showModal();
return;
}
dialog.setAttribute("open", "");
}
function hideDialog() {
if (typeof dialog.close === "function") {
dialog.close();
return;
}
dialog.removeAttribute("open");
}
// Extract video ID from YouTube URL
function extractYoutubeId(url) {
try {
@@ -182,7 +200,7 @@ import { LINKS } from "../lib/links";
if (!dialog.open) return;
stopPlayback();
dialog.close();
hideDialog();
// Emit media_preview_close event if Umami is available
if (typeof window.umami !== "undefined" && currentTargetId) {
@@ -370,12 +388,15 @@ import { LINKS } from "../lib/links";
ctasEl.appendChild(viewLink);
// Open the dialog
dialog.showModal();
showDialog();
}
// Listen for clicks on modal-trigger cards
document.addEventListener("click", function(e) {
const card = e.target.closest("button.card[data-item-id]");
const target = e.target instanceof Element ? e.target : null;
if (!target) return;
const card = target.closest("button.card[data-item-id]");
if (card) {
e.preventDefault();
openModal(card);

View File

@@ -1002,6 +1002,11 @@ button.card {
/* --- Media Modal --- */
#media-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: 0;
max-width: 800px;
width: calc(100vw - 48px);
max-height: 90vh;
@@ -1024,9 +1029,11 @@ button.card {
.media-modal-content {
display: flex;
flex-direction: column;
height: 100%;
max-height: min(90vh, calc(100dvh - 32px));
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.media-modal-header {
@@ -1302,10 +1309,14 @@ button.card {
@media (max-width: 760px) {
#media-modal {
max-width: 96vw;
max-height: 94vh;
max-height: min(94vh, calc(100dvh - 24px));
width: 96vw;
}
.media-modal-content {
max-height: min(94vh, calc(100dvh - 24px));
}
.media-modal-header {
padding: 16px 20px;
}