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:
parent
c688207865
commit
eaf536ae1f
261
design.json
Normal file
261
design.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-01-20T14:58:35.282Z",
|
||||
"generatedAt": "2026-01-20T15:37:09.139Z",
|
||||
"totalFiles": 143,
|
||||
"totalSize": 3237922,
|
||||
"days": [
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user