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,
|
"totalFiles": 143,
|
||||||
"totalSize": 3237922,
|
"totalSize": 3237922,
|
||||||
"days": [
|
"days": [
|
||||||
|
|||||||
@ -34,17 +34,14 @@ const isFeatured = variant === 'featured';
|
|||||||
---
|
---
|
||||||
|
|
||||||
<article class:list={[
|
<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' : '',
|
isFeatured ? 'lg:grid lg:grid-cols-2' : '',
|
||||||
className
|
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 -->
|
<!-- Image section -->
|
||||||
<a href={href} class:list={[
|
<a href={href} class:list={[
|
||||||
'block relative overflow-hidden',
|
'block relative overflow-hidden rounded-t-lg',
|
||||||
isFeatured ? 'aspect-[16/10] lg:aspect-auto lg:h-full' : isCompact ? 'aspect-[16/9]' : 'aspect-[16/9]'
|
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 && (
|
{heroImage && (
|
||||||
<Image
|
<Image
|
||||||
@ -52,16 +49,14 @@ const isFeatured = variant === 'featured';
|
|||||||
alt=""
|
alt=""
|
||||||
width={isFeatured ? 800 : 720}
|
width={isFeatured ? 800 : 720}
|
||||||
height={isFeatured ? 500 : 360}
|
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 badge overlay -->
|
||||||
{category && (
|
{category && (
|
||||||
<div class="absolute top-4 left-4">
|
<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}
|
{category}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -71,24 +66,20 @@ const isFeatured = variant === 'featured';
|
|||||||
<!-- Content section -->
|
<!-- Content section -->
|
||||||
<div class:list={[
|
<div class:list={[
|
||||||
'flex flex-col',
|
'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 -->
|
<!-- Metadata -->
|
||||||
<div class="flex items-center gap-3 mb-4">
|
<div class="flex items-center gap-2 mb-3 text-sm text-[var(--theme-text-muted)]">
|
||||||
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest">
|
|
||||||
<FormattedDate date={pubDate} />
|
<FormattedDate date={pubDate} />
|
||||||
</span>
|
<span>·</span>
|
||||||
<span class="h-px flex-grow max-w-8 bg-[var(--theme-border-strong)]"></span>
|
<span>{readTime}</span>
|
||||||
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
|
|
||||||
{readTime}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<a href={href}>
|
<a href={href}>
|
||||||
<h3 class:list={[
|
<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',
|
'font-bold text-[var(--theme-text-primary)] mb-2 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'
|
isFeatured ? 'text-2xl lg:text-3xl' : isCompact ? 'text-lg' : 'text-xl'
|
||||||
]}>
|
]}>
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
@ -96,50 +87,21 @@ const isFeatured = variant === 'featured';
|
|||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<p class:list={[
|
<p class:list={[
|
||||||
'text-[var(--theme-text-secondary)] font-light leading-relaxed',
|
'text-[var(--theme-text-secondary)] leading-relaxed mb-4',
|
||||||
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'
|
isFeatured ? 'text-base line-clamp-3' : isCompact ? 'text-sm line-clamp-2' : 'text-sm line-clamp-3'
|
||||||
]}>
|
]}>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Tags (only for featured and default variants) -->
|
<!-- Tags (only for featured and default variants) -->
|
||||||
{tags && tags.length > 0 && !isCompact && (
|
{tags && tags.length > 0 && !isCompact && (
|
||||||
<div class="flex flex-wrap gap-2 mb-6">
|
<div class="flex flex-wrap gap-2 mt-auto">
|
||||||
{tags.slice(0, 4).map((tag) => (
|
{tags.slice(0, 3).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">
|
<span class="px-2 py-1 text-xs text-[var(--theme-text-muted)]">
|
||||||
{tag}
|
#{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -77,109 +77,67 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
|
|||||||
<!-- Featured Hero Section -->
|
<!-- Featured Hero Section -->
|
||||||
{featuredPost && (
|
{featuredPost && (
|
||||||
<div class="mb-16 lg:mb-24 animate-on-scroll slide-up stagger-2">
|
<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="mb-6">
|
||||||
<div class="w-2 h-2 bg-brand-accent rounded-full animate-pulse"></div>
|
<span class="inline-block px-3 py-1 rounded-full bg-brand-accent/10 text-xs text-brand-accent">
|
||||||
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest font-bold">
|
Featured
|
||||||
SYS.BLOG /// FEATURED
|
|
||||||
</span>
|
</span>
|
||||||
<span class="h-px flex-grow bg-[var(--theme-border-secondary)]"></span>
|
|
||||||
</div>
|
</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">
|
<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">
|
||||||
<!-- 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>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2">
|
<div class="grid grid-cols-1 lg:grid-cols-2">
|
||||||
<!-- Image section -->
|
<!-- 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 && (
|
{featuredPost.data.heroImage && (
|
||||||
<Image
|
<Image
|
||||||
src={featuredPost.data.heroImage}
|
src={featuredPost.data.heroImage}
|
||||||
alt=""
|
alt=""
|
||||||
width={900}
|
width={900}
|
||||||
height={600}
|
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 -->
|
<!-- Category badge -->
|
||||||
{featuredPost.data.category && (
|
{featuredPost.data.category && (
|
||||||
<div class="absolute top-6 left-6">
|
<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}
|
{featuredPost.data.category}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<!-- Grid overlay effect -->
|
|
||||||
<div class="absolute inset-0 grid-overlay opacity-30 pointer-events-none"></div>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Content section -->
|
<!-- Content section -->
|
||||||
<div class="p-8 lg:p-12 flex flex-col justify-center">
|
<div class="p-8 lg:p-12 flex flex-col justify-center">
|
||||||
<!-- Technical header -->
|
<!-- Metadata -->
|
||||||
<div class="flex items-center gap-3 mb-6">
|
<div class="flex items-center gap-2 mb-4 text-sm text-[var(--theme-text-muted)]">
|
||||||
<span class="text-[10px] font-mono text-brand-accent uppercase tracking-widest">
|
|
||||||
<FormattedDate date={featuredPost.data.pubDate} />
|
<FormattedDate date={featuredPost.data.pubDate} />
|
||||||
</span>
|
<span>·</span>
|
||||||
<span class="h-px w-8 bg-[var(--theme-border-strong)]"></span>
|
<span>{calculateReadingTime(featuredPost.body)}</span>
|
||||||
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest">
|
|
||||||
{calculateReadingTime(featuredPost.body)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<a href={`/blog/${featuredPost.id}/`}>
|
<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}
|
{featuredPost.data.title}
|
||||||
</h2>
|
</h2>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- 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}
|
{featuredPost.data.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
{featuredPost.data.tags && featuredPost.data.tags.length > 0 && (
|
{featuredPost.data.tags && featuredPost.data.tags.length > 0 && (
|
||||||
<div class="flex flex-wrap gap-2 mb-8">
|
<div class="flex flex-wrap gap-2 mt-auto">
|
||||||
{featuredPost.data.tags.slice(0, 5).map((tag: string) => (
|
{featuredPost.data.tags.slice(0, 4).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">
|
<span class="px-2 py-1 text-xs text-[var(--theme-text-muted)]">
|
||||||
{tag}
|
#{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@ -188,11 +146,10 @@ const categories = [...new Set(allPosts.map((post) => post.data.category).filter
|
|||||||
|
|
||||||
<!-- Latest Section with Filters -->
|
<!-- Latest Section with Filters -->
|
||||||
<div class="mb-16 lg:mb-24">
|
<div class="mb-16 lg:mb-24">
|
||||||
<div class="flex items-center gap-4 mb-8">
|
<div class="mb-8">
|
||||||
<span class="text-[10px] font-mono text-[var(--theme-text-muted)] uppercase tracking-widest font-bold">
|
<h2 class="text-2xl lg:text-3xl font-bold text-[var(--theme-text-primary)]">
|
||||||
/// LATEST TRANSMISSIONS
|
All Posts
|
||||||
</span>
|
</h2>
|
||||||
<span class="h-px flex-grow bg-[var(--theme-border-secondary)]"></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters Component -->
|
<!-- 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) -->
|
<!-- Empty state (hidden by default, shown via JS when no results) -->
|
||||||
<div id="no-results" class="hidden text-center py-20">
|
<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">
|
<div class="text-[var(--theme-text-primary)] text-lg font-semibold mb-2">
|
||||||
/// NO MATCHING ARTICLES FOUND
|
no matching articles found
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[var(--theme-text-secondary)] text-sm">
|
<p class="text-[var(--theme-text-secondary)] text-sm">
|
||||||
Try adjusting your search or filter criteria.
|
try adjusting your search or filter criteria
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import Hero from '../components/sections/Hero.astro';
|
|||||||
import Experience from '../components/sections/Experience.astro';
|
import Experience from '../components/sections/Experience.astro';
|
||||||
import FeaturedProject from '../components/sections/FeaturedProject.astro';
|
import FeaturedProject from '../components/sections/FeaturedProject.astro';
|
||||||
import Skills from '../components/sections/Skills.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
|
// Fetch all section content
|
||||||
const heroEntry = await getEntry('sections', 'hero');
|
const heroEntry = await getEntry('sections', 'hero');
|
||||||
@ -12,6 +14,11 @@ const experienceEntry = await getEntry('sections', 'experience');
|
|||||||
const skillsEntry = await getEntry('sections', 'skills');
|
const skillsEntry = await getEntry('sections', 'skills');
|
||||||
const featuredProjectEntry = await getEntry('sections', 'featured-project');
|
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
|
// Extract content from entries
|
||||||
const heroContent = {
|
const heroContent = {
|
||||||
headlineLine1: heroEntry.data.headlineLine1 || '',
|
headlineLine1: heroEntry.data.headlineLine1 || '',
|
||||||
@ -68,4 +75,74 @@ const featuredProjectContent = {
|
|||||||
|
|
||||||
<FeaturedProject {...featuredProjectContent} />
|
<FeaturedProject {...featuredProjectContent} />
|
||||||
<Skills {...skillsContent} />
|
<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>
|
</BaseLayout>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user