feat: Feature 3.1 — Landing page + blog + Phase 3 specs
Next.js 14+ web app in web/ directory: - Landing page: Hero, How It Works, Features, 3-tier Pricing with founder badges, Footer with email capture - Blog system: MDX-powered, /blog index + /blog/[slug] pages, reading time, Open Graph tags, JSON-LD structured data - Auth pages: /login + /signup (Supabase Auth ready) - Design system: dark theme, grade colors (A/B/C/D), BetonBLK voice - 1 seed blog post: "How to Read Line Movement Like a Sharp" - Specs for 3.2 (Scan UI), 3.3 (Bet Tracker), 3.4 (Stripe) Build passes clean: 7 static pages generated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
const BLOG_DIR = path.join(process.cwd(), 'content', 'blog');
|
||||
|
||||
export interface BlogPost {
|
||||
slug: string;
|
||||
title: string;
|
||||
date: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
content: string;
|
||||
readingTime: number;
|
||||
}
|
||||
|
||||
export function getAllPosts(): BlogPost[] {
|
||||
if (!fs.existsSync(BLOG_DIR)) return [];
|
||||
|
||||
const files = fs.readdirSync(BLOG_DIR).filter((f) => f.endsWith('.mdx') || f.endsWith('.md'));
|
||||
|
||||
const posts = files.map((file) => {
|
||||
const raw = fs.readFileSync(path.join(BLOG_DIR, file), 'utf-8');
|
||||
const { data, content } = matter(raw);
|
||||
const slug = file.replace(/\.mdx?$/, '');
|
||||
const wordCount = content.split(/\s+/).length;
|
||||
const readingTime = Math.max(1, Math.ceil(wordCount / 200));
|
||||
|
||||
return {
|
||||
slug,
|
||||
title: data.title || slug,
|
||||
date: data.date || '',
|
||||
description: data.description || '',
|
||||
tags: data.tags || [],
|
||||
content,
|
||||
readingTime,
|
||||
};
|
||||
});
|
||||
|
||||
return posts.sort((a, b) => (a.date > b.date ? -1 : 1));
|
||||
}
|
||||
|
||||
export function getPostBySlug(slug: string): BlogPost | null {
|
||||
const posts = getAllPosts();
|
||||
return posts.find((p) => p.slug === slug) || null;
|
||||
}
|
||||
Reference in New Issue
Block a user