feat: add featured section, logo gallery, Firecrawl import, and API export

Featured Section
- Frontmatter-controlled featured items with featured: true and featuredOrder
- Card view with excerpts and list/card toggle button
- View preference saved to localStorage
- New Convex queries for featured posts and pages with by_featured index

Logo Gallery
- Continuous marquee scroll with clickable logos
- CSS animation, grayscale with color on hover
- Configurable speed, position, and title
- 5 sample logos included

Firecrawl Content Importer
- npm run import <url> scrapes external URLs to markdown drafts
- Creates local files in content/blog/ with frontmatter
- Then sync to dev or prod (no separate import:prod command)

API Enhancements
- New /api/export endpoint for batch content fetching
- AI plugin discovery at /.well-known/ai-plugin.json
- OpenAPI 3.0 spec at /openapi.yaml
- Enhanced llms.txt documentation

Documentation
- AGENTS.md with codebase instructions for AI agents
- Updated all sync vs deploy tables to include import workflow
- Renamed content/pages/changelog.md to changelog-page.md

Technical
- New components: FeaturedCards.tsx, LogoMarquee.tsx
- New script: scripts/import-url.ts
- New dependency: @mendable/firecrawl-js
- Schema updates with featured, featuredOrder, excerpt fields
This commit is contained in:
Wayne Sutton
2025-12-18 12:28:25 -08:00
parent e5b22487ca
commit 87e02d00dc
34 changed files with 3161 additions and 154 deletions

View File

@@ -30,6 +30,9 @@ interface PostFrontmatter {
tags: string[];
readTime?: string;
image?: string; // Header/OG image URL
excerpt?: string; // Short excerpt for card view
featured?: boolean; // Show in featured section
featuredOrder?: number; // Order in featured section (lower = first)
}
interface ParsedPost {
@@ -42,6 +45,9 @@ interface ParsedPost {
tags: string[];
readTime?: string;
image?: string; // Header/OG image URL
excerpt?: string; // Short excerpt for card view
featured?: boolean; // Show in featured section
featuredOrder?: number; // Order in featured section (lower = first)
}
// Page frontmatter (for static pages like About, Projects, Contact)
@@ -50,6 +56,9 @@ interface PageFrontmatter {
slug: string;
published: boolean;
order?: number; // Display order in navigation
excerpt?: string; // Short excerpt for card view
featured?: boolean; // Show in featured section
featuredOrder?: number; // Order in featured section (lower = first)
}
interface ParsedPage {
@@ -58,6 +67,9 @@ interface ParsedPage {
content: string;
published: boolean;
order?: number;
excerpt?: string; // Short excerpt for card view
featured?: boolean; // Show in featured section
featuredOrder?: number; // Order in featured section (lower = first)
}
// Calculate reading time based on word count
@@ -92,6 +104,9 @@ function parseMarkdownFile(filePath: string): ParsedPost | null {
tags: frontmatter.tags || [],
readTime: frontmatter.readTime || calculateReadTime(content),
image: frontmatter.image, // Header/OG image URL
excerpt: frontmatter.excerpt, // Short excerpt for card view
featured: frontmatter.featured, // Show in featured section
featuredOrder: frontmatter.featuredOrder, // Order in featured section
};
} catch (error) {
console.error(`Error parsing ${filePath}:`, error);
@@ -135,6 +150,9 @@ function parsePageFile(filePath: string): ParsedPage | null {
content: content.trim(),
published: frontmatter.published ?? true,
order: frontmatter.order,
excerpt: frontmatter.excerpt, // Short excerpt for card view
featured: frontmatter.featured, // Show in featured section
featuredOrder: frontmatter.featuredOrder, // Order in featured section
};
} catch (error) {
console.error(`Error parsing page ${filePath}:`, error);