feat(blog): add latest posts section and modernize blog cards

- add latest blog posts section to homepage displaying 3 most recent
- modernize BlogCard component with rounded corners and clean styling
- update featured post card on blog index with contemporary design
- remove industrial aesthetic (uppercase, mono, accent strips, overlays)
- add design.json documenting design system evolution
- change to normal case titles, hashtag tags, simplified metadata
- replace technical labels with clean badges
- add rounded-full buttons and rounded-lg/xl cards
This commit is contained in:
Nicholai Vogel 2026-01-20 08:43:50 -07:00
parent c688207865
commit eaf536ae1f
5 changed files with 395 additions and 138 deletions

261
design.json Normal file
View File

@ -0,0 +1,261 @@
{
"design_system": {
"version": "2.0",
"last_updated": "2026-01-20",
"description": "nicholai.work design language - evolved from industrial/technical to clean modern with selective technical accents"
},
"design_philosophy": {
"core_principles": [
"clean and modern with selective technical accents",
"rounded corners for contemporary feel",
"minimal decorative elements",
"readable typography over stylized text",
"intentional whitespace",
"smooth, refined interactions"
],
"when_to_use_industrial": [
"hero section only - establishes initial technical vibe",
"experience/work history - professional credibility",
"featured projects - showcasing technical work"
],
"when_to_use_modern": [
"navigation - clean, accessible",
"blog content - readable, focused",
"general UI elements - buttons, cards, badges",
"any new sections added to the site"
]
},
"typography": {
"headings": {
"modern_style": {
"size": "text-3xl to text-5xl",
"weight": "font-bold",
"case": "normal case (not uppercase)",
"tracking": "normal or tracking-tight",
"line_height": "leading-tight",
"usage": "blog posts, new sections, readable content"
},
"industrial_style": {
"size": "text-5xl to text-9xl",
"weight": "font-bold",
"case": "uppercase",
"tracking": "tracking-tighter",
"line_height": "leading-[0.85]",
"usage": "hero, experience, featured projects only"
}
},
"body": {
"size": "text-base to text-lg",
"weight": "font-normal or font-light",
"line_height": "leading-relaxed",
"color": "text-[var(--theme-text-secondary)]"
},
"labels": {
"modern": {
"size": "text-xs to text-sm",
"weight": "font-medium",
"case": "normal case",
"usage": "badges, metadata, general labels"
},
"technical": {
"size": "text-[9px] to text-[10px]",
"weight": "font-mono",
"case": "uppercase",
"tracking": "tracking-[0.2em] to tracking-[0.3em]",
"usage": "hero and work sections only"
}
}
},
"spacing": {
"sections": {
"vertical_padding": "py-20 to py-32",
"section_gap": "mb-20 to mb-24",
"content_gap": "gap-8 to gap-12"
},
"containers": {
"max_width": "container mx-auto",
"horizontal_padding": "px-6 lg:px-8 or px-6 lg:px-12",
"note": "use px-6 lg:px-8 for tighter layouts, px-6 lg:px-12 for wider"
}
},
"borders_and_corners": {
"modern_corners": {
"cards": "rounded-lg",
"buttons": "rounded-full",
"badges": "rounded-full",
"images": "rounded-lg",
"containers": "rounded-xl to rounded-2xl"
},
"industrial_corners": {
"usage": "none - use sharp corners only in hero/experience/featured",
"buttons": "no rounded corners",
"containers": "no rounded corners"
},
"borders": {
"default": "border border-[var(--theme-border-primary)]",
"subtle": "border-[var(--theme-border-secondary)]",
"accent": "border-brand-accent",
"dividers": "border-t or border-b"
}
},
"components": {
"buttons": {
"primary_modern": {
"base": "px-6 py-3 rounded-full",
"border": "border border-[var(--theme-border-primary)]",
"background": "bg-[var(--theme-hover-bg)]",
"hover": "hover:border-brand-accent hover:bg-brand-accent/5",
"text": "text-sm font-medium",
"transition": "transition-all duration-300"
},
"secondary_modern": {
"base": "px-4 py-2 rounded-full",
"background": "bg-brand-accent/10",
"text": "text-sm text-brand-accent",
"hover": "hover:bg-brand-accent/20"
},
"industrial": {
"note": "only use in hero/experience/featured sections",
"base": "px-8 py-4 border",
"corners": "no rounded corners",
"text": "font-mono text-xs uppercase tracking-widest"
}
},
"badges": {
"modern": {
"base": "px-3 py-1 rounded-full",
"background": "bg-brand-accent/10",
"text": "text-xs text-brand-accent",
"usage": "categories, tags, status indicators"
},
"minimal": {
"base": "px-2 py-1",
"text": "text-xs text-[var(--theme-text-muted)]",
"usage": "hashtags, minimal labels"
}
},
"cards": {
"modern": {
"base": "rounded-lg border border-[var(--theme-border-primary)]",
"background": "bg-[var(--theme-bg-secondary)]",
"hover": "hover:border-brand-accent hover:bg-[var(--theme-hover-bg)]",
"padding": "p-6",
"transition": "transition-all duration-300"
},
"images": {
"corners": "rounded-lg",
"aspect_ratios": "aspect-video, aspect-square, aspect-[4/3]",
"object_fit": "object-cover"
}
},
"section_headers": {
"modern_style": {
"layout": "simple, no grid",
"title": "text-3xl to text-5xl font-bold normal-case leading-tight",
"subtitle": "text-base to text-lg text-[var(--theme-text-secondary)]",
"spacing": "mb-12 to mb-16",
"decorative_elements": "none or minimal",
"usage": "new sections, blog, modern areas"
},
"industrial_style": {
"layout": "grid grid-cols-1 lg:grid-cols-12 gap-12",
"title_col": "lg:col-span-8",
"meta_col": "lg:col-span-4",
"title": "text-5xl to text-8xl uppercase tracking-tighter leading-[0.85]",
"decorative": {
"pulsing_dot": "w-2 h-2 bg-brand-accent animate-pulse",
"system_label": "font-mono text-[10px] uppercase tracking-[0.3em]",
"format": "SYS.LABEL /// DESCRIPTION"
},
"usage": "hero, experience, featured projects only"
}
},
"navigation": {
"container": "rounded-2xl backdrop-blur-md",
"items": "rounded-xl hover:bg-[var(--theme-hover-bg-strong)]",
"active_state": "bg-brand-accent/10 text-brand-accent",
"icons": "w-5 h-5 stroke-width-1.5"
},
"animations": {
"scroll_animations": {
"classes": "animate-on-scroll fade-in, slide-up, slide-left, slide-right",
"stagger": "stagger-1 through stagger-8",
"usage": "applies to all sections, triggered by IntersectionObserver"
},
"hover_effects": {
"duration": "duration-300",
"ease": "ease-out or ease-in-out",
"transitions": "transition-all, transition-colors, transition-transform"
},
"reduced_motion": {
"respect": "always check prefers-reduced-motion",
"fallback": "instant state changes"
}
}
},
"color_usage": {
"backgrounds": {
"primary": "bg-[var(--theme-bg-primary)]",
"secondary": "bg-[var(--theme-bg-secondary)]",
"overlay": "bg-[var(--theme-overlay)]",
"hover": "bg-[var(--theme-hover-bg)]"
},
"text": {
"primary": "text-[var(--theme-text-primary)]",
"secondary": "text-[var(--theme-text-secondary)]",
"muted": "text-[var(--theme-text-muted)]",
"subtle": "text-[var(--theme-text-subtle)]"
},
"accent": {
"solid": "bg-brand-accent, text-brand-accent, border-brand-accent",
"transparent": "bg-brand-accent/10, bg-brand-accent/5",
"usage": "highlights, active states, CTAs, hover effects"
}
},
"grid_layouts": {
"blog_cards": {
"mobile": "grid-cols-1",
"tablet": "md:grid-cols-2",
"desktop": "lg:grid-cols-3",
"gap": "gap-6 to gap-8"
},
"content_layout": {
"columns": "grid-cols-1 lg:grid-cols-12",
"main_content": "lg:col-span-8 to lg:col-span-10",
"sidebar": "lg:col-span-4 to lg:col-span-2"
}
},
"examples": {
"modern_section_header": {
"html": "<div class=\"mb-16\">\n <div class=\"mb-4\">\n <span class=\"px-3 py-1 rounded-full bg-brand-accent/10 text-xs text-brand-accent\">Section Label</span>\n </div>\n <h2 class=\"text-4xl lg:text-5xl font-bold text-[var(--theme-text-primary)] mb-4\">\n Section Title\n </h2>\n <p class=\"text-lg text-[var(--theme-text-secondary)] max-w-2xl\">\n Description text\n </p>\n</div>"
},
"modern_button": {
"html": "<a href=\"#\" class=\"inline-flex items-center gap-2 px-6 py-3 rounded-full border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent hover:bg-brand-accent/5 text-sm font-medium transition-all duration-300\">\n <span>Button Text</span>\n <svg>...</svg>\n</a>"
},
"modern_card": {
"html": "<div class=\"rounded-lg border border-[var(--theme-border-primary)] bg-[var(--theme-bg-secondary)] hover:border-brand-accent hover:bg-[var(--theme-hover-bg)] p-6 transition-all duration-300\">\n <div class=\"aspect-video rounded-lg overflow-hidden mb-4\">\n <img src=\"...\" class=\"w-full h-full object-cover\" />\n </div>\n <div class=\"mb-2\">\n <span class=\"px-3 py-1 rounded-full bg-brand-accent/10 text-xs text-brand-accent\">Category</span>\n </div>\n <h3 class=\"text-xl font-bold mb-2\">Card Title</h3>\n <p class=\"text-[var(--theme-text-secondary)]\">Description</p>\n</div>"
}
},
"implementation_notes": {
"new_sections": [
"use modern design language by default",
"rounded corners on all interactive elements",
"clean typography (no excessive uppercase)",
"simple, readable headers",
"badges and pills with rounded-full",
"smooth transitions and hover states"
],
"existing_sections": [
"hero: keep industrial (establishes vibe)",
"experience: keep industrial (professional credibility)",
"featured project: keep industrial (showcases technical work)",
"everything else: modernize as needed"
],
"progressive_enhancement": [
"start with accessible, readable defaults",
"add animations for users without prefers-reduced-motion",
"ensure keyboard navigation works",
"maintain high contrast ratios"
]
}
}

View File

@ -1,5 +1,5 @@
{
"generatedAt": "2026-01-20T14:58:35.282Z",
"generatedAt": "2026-01-20T15:37:09.139Z",
"totalFiles": 143,
"totalSize": 3237922,
"days": [

View File

@ -34,17 +34,14 @@ const isFeatured = variant === 'featured';
---
<article class:list={[
'group relative border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent/40 transition-all duration-500 overflow-hidden',
'group relative rounded-lg border border-[var(--theme-border-primary)] bg-[var(--theme-bg-secondary)] hover:border-brand-accent hover:bg-[var(--theme-hover-bg)] transition-all duration-300 overflow-hidden',
isFeatured ? 'lg:grid lg:grid-cols-2' : '',
className
]}>
<!-- Accent indicator strip -->
<div class="absolute top-0 left-0 w-1 h-full bg-[var(--theme-text-subtle)] opacity-50 group-hover:bg-brand-accent group-hover:opacity-100 transition-all duration-500"></div>
<!-- Image section -->
<a href={href} class:list={[
'block relative overflow-hidden',
isFeatured ? 'aspect-[16/10] lg:aspect-auto lg:h-full' : isCompact ? 'aspect-[16/9]' : 'aspect-[16/9]'
'block relative overflow-hidden rounded-t-lg',
isFeatured ? 'aspect-[16/10] lg:aspect-auto lg:h-full lg:rounded-t-none lg:rounded-l-lg' : isCompact ? 'aspect-[16/9]' : 'aspect-video'
]}>
{heroImage && (
<Image
@ -52,16 +49,14 @@ const isFeatured = variant === 'featured';
alt=""
width={isFeatured ? 800 : 720}
height={isFeatured ? 500 : 360}
class="w-full h-full object-cover transition-transform duration-[1.2s] ease-out group-hover:scale-105"
class="w-full h-full object-cover transition-transform duration-500 ease-out group-hover:scale-105"
/>
)}
<div class="absolute inset-0 bg-[var(--theme-card-overlay)] group-hover:opacity-50 transition-opacity duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-t from-[var(--theme-card-gradient)] to-transparent"></div>
<!-- Category badge overlay -->
{category && (
<div class="absolute top-4 left-4">
<span class="px-3 py-1.5 text-[10px] font-mono font-bold uppercase tracking-widest bg-[var(--theme-bg-primary)]/80 border border-[var(--theme-border-strong)] text-[var(--theme-text-primary)] backdrop-blur-sm">
<span class="px-3 py-1 rounded-full bg-brand-accent/10 backdrop-blur-sm text-xs text-brand-accent">
{category}
</span>
</div>
@ -71,24 +66,20 @@ const isFeatured = variant === 'featured';
<!-- Content section -->
<div class:list={[
'flex flex-col',
isFeatured ? 'p-8 lg:p-12 justify-center' : isCompact ? 'p-5' : 'p-6 lg:p-8'
isFeatured ? 'p-8 lg:p-12 justify-center' : isCompact ? 'p-5' : 'p-6'
]}>
<!-- Technical header with metadata -->
<div class="flex items-center gap-3 mb-4">
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest">
<FormattedDate date={pubDate} />
</span>
<span class="h-px flex-grow max-w-8 bg-[var(--theme-border-strong)]"></span>
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
{readTime}
</span>
<!-- Metadata -->
<div class="flex items-center gap-2 mb-3 text-sm text-[var(--theme-text-muted)]">
<FormattedDate date={pubDate} />
<span>·</span>
<span>{readTime}</span>
</div>
<!-- Title -->
<a href={href}>
<h3 class:list={[
'font-bold text-[var(--theme-text-primary)] uppercase tracking-tight mb-3 group-hover:text-brand-accent transition-colors duration-300 leading-tight',
isFeatured ? 'text-3xl lg:text-4xl' : isCompact ? 'text-lg' : 'text-xl lg:text-2xl'
'font-bold text-[var(--theme-text-primary)] mb-2 group-hover:text-brand-accent transition-colors duration-300 leading-tight',
isFeatured ? 'text-2xl lg:text-3xl' : isCompact ? 'text-lg' : 'text-xl'
]}>
{title}
</h3>
@ -96,50 +87,21 @@ const isFeatured = variant === 'featured';
<!-- Description -->
<p class:list={[
'text-[var(--theme-text-secondary)] font-light leading-relaxed',
isFeatured ? 'text-base lg:text-lg line-clamp-3 mb-8' : isCompact ? 'text-sm line-clamp-2 mb-4' : 'text-sm line-clamp-2 mb-6'
'text-[var(--theme-text-secondary)] leading-relaxed mb-4',
isFeatured ? 'text-base line-clamp-3' : isCompact ? 'text-sm line-clamp-2' : 'text-sm line-clamp-3'
]}>
{description}
</p>
<!-- Tags (only for featured and default variants) -->
{tags && tags.length > 0 && !isCompact && (
<div class="flex flex-wrap gap-2 mb-6">
{tags.slice(0, 4).map((tag) => (
<span class="px-2 py-1 text-[10px] font-mono uppercase border border-[var(--theme-border-primary)] text-[var(--theme-text-muted)] group-hover:border-[var(--theme-border-strong)] transition-colors">
{tag}
<div class="flex flex-wrap gap-2 mt-auto">
{tags.slice(0, 3).map((tag) => (
<span class="px-2 py-1 text-xs text-[var(--theme-text-muted)]">
#{tag}
</span>
))}
</div>
)}
<!-- Read link -->
<div class:list={[
'flex items-center',
isFeatured ? 'mt-auto pt-6 border-t border-[var(--theme-border-primary)]' : 'mt-auto'
]}>
<a
href={href}
class="inline-flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-[var(--theme-text-muted)] group-hover:text-[var(--theme-text-primary)] transition-all duration-300"
>
Read Article
<span class="block w-6 h-[1px] bg-[var(--theme-text-subtle)] group-hover:bg-brand-accent group-hover:w-10 transition-all duration-300"></span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</a>
</div>
</div>
</article>

View File

@ -77,109 +77,67 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<!-- Featured Hero Section -->
{featuredPost && (
<div class="mb-16 lg:mb-24 animate-on-scroll slide-up stagger-2">
<div class="flex items-center gap-4 mb-8">
<div class="w-2 h-2 bg-brand-accent rounded-full animate-pulse"></div>
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest font-bold">
SYS.BLOG /// FEATURED
<div class="mb-6">
<span class="inline-block px-3 py-1 rounded-full bg-brand-accent/10 text-xs text-brand-accent">
Featured
</span>
<span class="h-px flex-grow bg-[var(--theme-border-secondary)]"></span>
</div>
<article class="group relative border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent/40 transition-all duration-500 overflow-hidden">
<!-- Accent indicator strip -->
<div class="absolute top-0 left-0 w-1 h-full bg-brand-accent"></div>
<div class="absolute top-0 left-0 w-full h-1 bg-brand-accent opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<article class="group relative rounded-xl border border-[var(--theme-border-primary)] bg-[var(--theme-bg-secondary)] hover:border-brand-accent hover:bg-[var(--theme-hover-bg)] transition-all duration-300 overflow-hidden">
<div class="grid grid-cols-1 lg:grid-cols-2">
<!-- Image section -->
<a href={`/blog/${featuredPost.id}/`} class="block relative aspect-[16/10] lg:aspect-auto overflow-hidden">
<a href={`/blog/${featuredPost.id}/`} class="block relative aspect-[16/10] lg:aspect-auto overflow-hidden rounded-t-xl lg:rounded-t-none lg:rounded-l-xl">
{featuredPost.data.heroImage && (
<Image
src={featuredPost.data.heroImage}
alt=""
width={900}
height={600}
class="w-full h-full object-cover transition-transform duration-[1.2s] ease-out group-hover:scale-105"
class="w-full h-full object-cover transition-transform duration-500 ease-out group-hover:scale-105"
/>
)}
<div class="absolute inset-0 bg-[var(--theme-card-overlay)] group-hover:opacity-50 transition-opacity duration-500"></div>
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-transparent to-[var(--theme-card-gradient)] hidden lg:block"></div>
<div class="absolute inset-0 bg-gradient-to-t from-[var(--theme-card-gradient)] to-transparent lg:hidden"></div>
<!-- Category badge -->
{featuredPost.data.category && (
<div class="absolute top-6 left-6">
<span class="px-4 py-2 text-[10px] font-mono font-bold uppercase tracking-widest bg-[var(--theme-overlay)] border border-brand-accent/50 text-brand-accent backdrop-blur-sm">
<span class="px-3 py-1 rounded-full bg-brand-accent/10 backdrop-blur-sm text-xs text-brand-accent">
{featuredPost.data.category}
</span>
</div>
)}
<!-- Grid overlay effect -->
<div class="absolute inset-0 grid-overlay opacity-30 pointer-events-none"></div>
</a>
<!-- Content section -->
<div class="p-8 lg:p-12 flex flex-col justify-center">
<!-- Technical header -->
<div class="flex items-center gap-3 mb-6">
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest">
<FormattedDate date={featuredPost.data.pubDate} />
</span>
<span class="h-px w-8 bg-[var(--theme-border-strong)]"></span>
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
{calculateReadingTime(featuredPost.body)}
</span>
<!-- Metadata -->
<div class="flex items-center gap-2 mb-4 text-sm text-[var(--theme-text-muted)]">
<FormattedDate date={featuredPost.data.pubDate} />
<span>·</span>
<span>{calculateReadingTime(featuredPost.body)}</span>
</div>
<!-- Title -->
<a href={`/blog/${featuredPost.id}/`}>
<h2 class="text-3xl lg:text-4xl xl:text-5xl font-bold text-[var(--theme-text-primary)] uppercase tracking-tight mb-6 group-hover:text-brand-accent transition-colors duration-300 leading-tight">
<h2 class="text-3xl lg:text-4xl font-bold text-[var(--theme-text-primary)] mb-4 group-hover:text-brand-accent transition-colors duration-300 leading-tight">
{featuredPost.data.title}
</h2>
</a>
<!-- Description -->
<p class="text-[var(--theme-text-secondary)] text-base lg:text-lg font-light leading-relaxed mb-8 line-clamp-3">
<p class="text-[var(--theme-text-secondary)] text-base lg:text-lg leading-relaxed mb-6 line-clamp-3">
{featuredPost.data.description}
</p>
<!-- Tags -->
{featuredPost.data.tags && featuredPost.data.tags.length > 0 && (
<div class="flex flex-wrap gap-2 mb-8">
{featuredPost.data.tags.slice(0, 5).map((tag: string) => (
<span class="px-3 py-1.5 text-[10px] font-mono uppercase border border-[var(--theme-border-primary)] text-[var(--theme-text-muted)] group-hover:border-[var(--theme-border-strong)] transition-colors">
{tag}
<div class="flex flex-wrap gap-2 mt-auto">
{featuredPost.data.tags.slice(0, 4).map((tag: string) => (
<span class="px-2 py-1 text-xs text-[var(--theme-text-muted)]">
#{tag}
</span>
))}
</div>
)}
<!-- Read link -->
<div class="pt-6 border-t border-[var(--theme-border-primary)]">
<a
href={`/blog/${featuredPost.id}/`}
class="inline-flex items-center gap-4 text-xs font-bold uppercase tracking-widest text-[var(--theme-text-primary)] hover:text-brand-accent transition-all duration-300 group/link"
>
Read Full Article
<span class="block w-8 h-[1px] bg-[var(--theme-border-strong)] group-hover/link:bg-brand-accent group-hover/link:w-12 transition-all duration-300"></span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="group-hover/link:translate-x-1 transition-transform duration-300"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</a>
</div>
</div>
</div>
</article>
@ -188,11 +146,10 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<!-- Latest Section with Filters -->
<div class="mb-16 lg:mb-24">
<div class="flex items-center gap-4 mb-8">
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest font-bold">
/// LATEST TRANSMISSIONS
</span>
<span class="h-px flex-grow bg-[var(--theme-border-secondary)]"></span>
<div class="mb-8">
<h2 class="text-2xl lg:text-3xl font-bold text-[var(--theme-text-primary)]">
All Posts
</h2>
</div>
<!-- Filters Component -->
@ -224,11 +181,11 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
<!-- Empty state (hidden by default, shown via JS when no results) -->
<div id="no-results" class="hidden text-center py-20">
<div class="text-[var(--theme-text-muted)] font-mono text-sm uppercase tracking-widest mb-4">
/// NO MATCHING ARTICLES FOUND
<div class="text-[var(--theme-text-primary)] text-lg font-semibold mb-2">
no matching articles found
</div>
<p class="text-[var(--theme-text-secondary)] text-sm">
Try adjusting your search or filter criteria.
try adjusting your search or filter criteria
</p>
</div>
</div>

View File

@ -4,7 +4,9 @@ import Hero from '../components/sections/Hero.astro';
import Experience from '../components/sections/Experience.astro';
import FeaturedProject from '../components/sections/FeaturedProject.astro';
import Skills from '../components/sections/Skills.astro';
import { getEntry } from 'astro:content';
import { getEntry, getCollection } from 'astro:content';
import BlogCard from '../components/BlogCard.astro';
import { calculateReadingTime } from '../utils/reading-time';
// Fetch all section content
const heroEntry = await getEntry('sections', 'hero');
@ -12,6 +14,11 @@ const experienceEntry = await getEntry('sections', 'experience');
const skillsEntry = await getEntry('sections', 'skills');
const featuredProjectEntry = await getEntry('sections', 'featured-project');
// Fetch 3 most recent blog posts
const latestBlogs = (await getCollection('blog'))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
.slice(0, 3);
// Extract content from entries
const heroContent = {
headlineLine1: heroEntry.data.headlineLine1 || '',
@ -68,4 +75,74 @@ const featuredProjectContent = {
<FeaturedProject {...featuredProjectContent} />
<Skills {...skillsContent} />
<!-- Container Divider -->
<div class="container mx-auto px-6 lg:px-12 my-8">
<div class="h-[1px] divider-gradient"></div>
</div>
<!-- Latest Blogs Section -->
<section id="latest-blogs" class="bg-[var(--theme-bg-primary)] py-20 lg:py-32 border-t border-[var(--theme-border-primary)]">
<div class="container mx-auto px-6 lg:px-8">
<!-- Header Section -->
<div class="mb-16 max-w-3xl">
<div class="mb-4">
<span class="inline-block px-3 py-1 rounded-full bg-brand-accent/10 text-xs text-brand-accent">
Latest Posts
</span>
</div>
<h2 class="text-4xl lg:text-5xl font-bold text-[var(--theme-text-primary)] mb-4 leading-tight">
Recent Writings
</h2>
<p class="text-lg text-[var(--theme-text-secondary)] leading-relaxed">
exploring vfx pipelines, ai workflows, and technical insights from the production trenches
</p>
</div>
<!-- Blog Cards Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
{latestBlogs.map((post, index) => (
<div class={`animate-on-scroll slide-up stagger-${index + 1}`}>
<BlogCard
title={post.data.title}
description={post.data.description}
pubDate={post.data.pubDate}
heroImage={post.data.heroImage}
category={post.data.category}
tags={post.data.tags}
href={`/blog/${post.id}/`}
readTime={calculateReadingTime(post.body)}
/>
</div>
))}
</div>
<!-- View All Button -->
<div class="flex justify-center animate-on-scroll fade-in stagger-4">
<a
href="/blog"
class="inline-flex items-center gap-2 px-6 py-3 rounded-full border border-[var(--theme-border-primary)] bg-[var(--theme-hover-bg)] hover:border-brand-accent hover:bg-brand-accent/5 text-sm font-medium transition-all duration-300"
>
<span>View All Posts</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="transition-transform group-hover:translate-x-1"
>
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</a>
</div>
</div>
</section>
</BaseLayout>