Release v1.2.0: Newspaper-style layout with major UI refinements
This release transforms PING into a sophisticated newspaper-style digital publication with enhanced readability and professional presentation. Major Features: - New FeaturedPostHero component with full-width newspaper design - Completely redesigned homepage with responsive newspaper grid layout - Enhanced PostCard component with refined typography and spacing - Improved mobile-first responsive design (mobile → tablet → desktop → 2XL) - Archive section with multi-column layout for deeper content discovery Technical Improvements: - Enhanced blog post validation and error handling in lib/blog.ts - Better date handling and normalization for scheduled posts - Improved Dockerfile with correct content volume mount paths - Fixed port configuration (3025 throughout stack) - Updated Tailwind config with refined typography and newspaper aesthetics - Added getFeaturedPost() function for hero selection UI/UX Enhancements: - Professional newspaper-style borders and dividers - Improved dark mode styling throughout - Better content hierarchy and visual flow - Enhanced author bylines and metadata presentation - Refined color palette with newspaper sophistication Documentation: - Added DESIGN_BRIEF_NEWSPAPER_LAYOUT.md detailing design principles - Added TESTING_RESULTS_25_POSTS.md with test scenarios This release establishes PING as a premium publication platform for AI orchestration and contextual intelligence thought leadership. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -194,78 +194,14 @@ body {
|
||||
}
|
||||
|
||||
|
||||
/* CHORUS Blog Typography */
|
||||
.prose {
|
||||
@apply max-w-none text-carbon-950 dark:text-carbon-100;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
@apply text-h2 font-sans font-thin text-carbon-950 dark:text-mulberry-100 mb-8;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
@apply text-h3 font-logo text-carbon-950 dark:text-mulberry-200 mb-6 mt-12;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
@apply text-h4 font-sans text-carbon-950 dark:text-mulberry-300 mb-4 mt-8;
|
||||
}
|
||||
|
||||
.prose h4 {
|
||||
@apply text-h5 font-sans text-carbon-800 dark:text-mulberry-400 mb-3 mt-6;
|
||||
}
|
||||
|
||||
.prose h5 {
|
||||
@apply text-h6 font-sans text-carbon-700 dark:text-mulberry-500 mb-3 mt-6;
|
||||
}
|
||||
|
||||
.prose h6 {
|
||||
@apply text-h7 font-sans text-carbon-600 dark:text-mulberry-600 mb-2 mt-4;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
@apply text-base leading-relaxed mb-6 text-carbon-800 dark:text-carbon-200;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
@apply text-ocean-600 dark:text-ocean-400 hover:text-ocean-800 dark:hover:text-ocean-300 transition-colors underline decoration-ocean-500/30 hover:decoration-ocean-600/50 dark:hover:decoration-ocean-400/50;
|
||||
}
|
||||
|
||||
.prose strong {
|
||||
@apply text-carbon-950 dark:text-carbon-100 font-semibold;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
@apply bg-carbon-200 dark:bg-carbon-800 text-eucalyptus-700 dark:text-eucalyptus-400 px-2 py-1 rounded-sm font-mono text-sm;
|
||||
}
|
||||
|
||||
.prose pre {
|
||||
@apply bg-carbon-100 dark:bg-carbon-900 border border-carbon-300 dark:border-carbon-700 rounded-lg overflow-x-auto p-4 mb-6;
|
||||
}
|
||||
|
||||
.prose pre code {
|
||||
@apply bg-transparent text-carbon-950 dark:text-carbon-200 p-0;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
@apply border-l-4 border-carbon-400 dark:border-mulberry-600 pl-6 italic text-carbon-600 dark:text-carbon-300 my-6;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
@apply list-disc pl-6 mb-6 space-y-2;
|
||||
}
|
||||
|
||||
.prose ol {
|
||||
@apply list-decimal pl-6 mb-6 space-y-2;
|
||||
}
|
||||
|
||||
.prose li {
|
||||
@apply text-carbon-800 dark:text-carbon-200;
|
||||
}
|
||||
|
||||
/* Blog-specific utilities */
|
||||
.blog-container {
|
||||
@apply max-w-4xl mx-auto px-4 sm:px-6 lg:px-8;
|
||||
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.blog-meta {
|
||||
|
||||
281
app/page.tsx
281
app/page.tsx
@@ -1,68 +1,253 @@
|
||||
import { getSortedPostsData, getFeaturedPosts } from '@/lib/blog'
|
||||
import { getSortedPostsData, getFeaturedPost } from '@/lib/blog'
|
||||
import BlogHeader from '@/components/BlogHeader'
|
||||
import BlogFooter from '@/components/BlogFooter'
|
||||
import PostCard from '@/components/PostCard'
|
||||
import FeaturedPostHero from '@/components/FeaturedPostHero'
|
||||
|
||||
export default function HomePage() {
|
||||
// Force dynamic rendering to pick up volume-mounted content
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export default async function HomePage() {
|
||||
const allPosts = getSortedPostsData()
|
||||
const featuredPosts = getFeaturedPosts()
|
||||
const recentPosts = allPosts.slice(0, 6)
|
||||
const featuredPost = getFeaturedPost()
|
||||
const recentPosts = allPosts.slice(1, 7) // Skip the first post since it's featured
|
||||
const additionalPosts = allPosts.slice(8, 20) // Additional posts for newspaper columns
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<BlogHeader />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="blog-container py-16">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="text-h1 font-logo text-carbon-950 dark:text-mulberry-100 mb-6">
|
||||
Informed Agentic AI
|
||||
</h1>
|
||||
<p className="text-xl text-carbon-700 dark:text-carbon-300 leading-relaxed">
|
||||
Deep dives into AI orchestration, agent coordination, and the future of
|
||||
intelligent systems from the team building CHORUS.
|
||||
</p>
|
||||
{/* Featured Post - Full Width Hero */}
|
||||
{featuredPost && (
|
||||
<FeaturedPostHero post={featuredPost} />
|
||||
)}
|
||||
|
||||
{/* Recent Posts - Responsive Newspaper Layout */}
|
||||
<section className="w-full px-4 sm:px-8 py-16 border-t border-carbon-200 dark:border-carbon-800">
|
||||
{/* Section Header - Newspaper Style */}
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{recentPosts.length > 0 ? (
|
||||
<div className="flex flex-col 2xl:flex-row gap-8">
|
||||
{/* Main Content Area - Primary Articles */}
|
||||
<div className="flex-1 max-w-6xl mx-auto 2xl:mx-0">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-x-12 gap-y-8">
|
||||
{recentPosts.slice(0, 4).map((post) => (
|
||||
<PostCard key={post.slug} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Articles - Only on 2XL+ screens if more articles available */}
|
||||
{/* {recentPosts.length > 4 && (
|
||||
<aside className="hidden 2xl:block w-80 space-y-6">
|
||||
<div className="bg-sand-50/50 dark:bg-carbon-900/30 border border-carbon-200 dark:border-carbon-800 p-6 rounded-lg">
|
||||
<h3 className="font-bold text-carbon-950 dark:text-carbon-100 mb-4 text-lg border-b border-carbon-300 dark:border-carbon-700 pb-2">
|
||||
More Analysis
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{recentPosts.slice(4, 7).map((post) => (
|
||||
<article key={post.slug} className="group">
|
||||
<a href={`/posts/${post.slug}`} className="block">
|
||||
<div className="mb-1">
|
||||
<span className="text-mulberry-600 dark:text-mulberry-400 text-xs font-bold uppercase tracking-wide">
|
||||
{post.tags?.[0] || 'Analysis'}
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="text-sm font-bold text-carbon-950 dark:text-carbon-100 leading-tight mb-2 group-hover:text-carbon-700 dark:group-hover:text-mulberry-200 transition-colors">
|
||||
{post.title}
|
||||
</h4>
|
||||
<div className="flex items-center text-xxs text-carbon-600 dark:text-carbon-500">
|
||||
<span>{post.author?.name || 'CHORUS Team'}</span>
|
||||
<span className="mx-1">•</span>
|
||||
<span>{post.readingTime} min read</span>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
)} */}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-16 h-16 bg-carbon-200 dark:bg-carbon-800 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-carbon-600 dark:text-carbon-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-h5 font-semibold text-carbon-950 dark:text-carbon-200 mb-2">Coming Soon</h3>
|
||||
<p className="text-carbon-600 dark:text-carbon-400 max-w-md mx-auto">
|
||||
We're preparing some excellent content about contextual AI and agent orchestration.
|
||||
Check back soon for our first posts!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Featured Posts */}
|
||||
{featuredPosts.length > 0 && (
|
||||
<section className="blog-container py-8">
|
||||
<h2 className="text-h3 font-logo text-carbon-950 dark:text-carbon-100 mb-8">Featured Posts</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
{featuredPosts.map((post) => (
|
||||
<PostCard key={post.slug} post={post} featured={true} />
|
||||
))}
|
||||
{/* Newspaper Column Layout - Additional Articles */}
|
||||
{additionalPosts.length > 0 && (
|
||||
<section className="w-full px-4 sm:px-8 py-16 border-t border-carbon-200 dark:border-carbon-800 bg-sand-25/20 dark:bg-carbon-950/50">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="flex items-center mb-12">
|
||||
<div className="flex-1 h-px bg-carbon-300 dark:bg-carbon-700"></div>
|
||||
<div className="px-6">
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-carbon-950 dark:text-carbon-100 text-center">
|
||||
Archive
|
||||
</h2>
|
||||
<p className="text-center text-carbon-600 dark:text-carbon-500 text-sm mt-1 italic">
|
||||
Deeper insights and analysis
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-1 h-px bg-carbon-300 dark:bg-carbon-700"></div>
|
||||
</div>
|
||||
|
||||
{/* Flexbox Newspaper Columns - 4 Narrow Equal Columns */}
|
||||
<div className="hidden lg:flex gap-6">
|
||||
{/* Column 1 */}
|
||||
<div className="flex-1 space-y-4">
|
||||
{additionalPosts.slice(0, 3).map((post) => (
|
||||
<article key={post.slug} className="group border-b border-carbon-200 dark:border-carbon-700 pb-4 mb-4">
|
||||
<a href={`/posts/${post.slug}`} className="block">
|
||||
{/* <div className="mb-2">
|
||||
<span className="text-mulberry-600 dark:text-mulberry-400 text-xs font-bold uppercase tracking-wide">
|
||||
{post.tags?.[0] || 'Analysis'}
|
||||
</span>
|
||||
</div> */}
|
||||
<h3 className="text-sm font-bold text-carbon-950 dark:text-carbon-100 leading-tight mb-2 group-hover:text-carbon-700 dark:group-hover:text-mulberry-200 transition-colors">
|
||||
{post.title}
|
||||
</h3>
|
||||
<div className="mb-chorus-sm flex items-center text-xxs text-carbon-500 dark:text-carbon-600">
|
||||
<span>{post.author?.name || 'CHORUS Team'}</span>
|
||||
<span className="mx-1">•</span>
|
||||
<span>{post.readingTime} min</span>
|
||||
</div>
|
||||
<p className="text-xs text-carbon-600 dark:text-carbon-400 leading-relaxed mb-2 line-clamp-3">
|
||||
{post.description}
|
||||
</p>
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Column 2 */}
|
||||
<div className="flex-1 space-y-4">
|
||||
{additionalPosts.slice(3, 6).map((post) => (
|
||||
<article key={post.slug} className="group border-b border-carbon-200 dark:border-carbon-700 pb-4 mb-4">
|
||||
<a href={`/posts/${post.slug}`} className="block">
|
||||
{/* <div className="mb-2">
|
||||
<span className="text-mulberry-600 dark:text-mulberry-400 text-xs font-bold uppercase tracking-wide">
|
||||
{post.tags?.[0] || 'Analysis'}
|
||||
</span>
|
||||
</div> */}
|
||||
<h3 className="text-sm font-bold text-carbon-950 dark:text-carbon-100 leading-tight mb-2 group-hover:text-carbon-700 dark:group-hover:text-mulberry-200 transition-colors">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
<div className="mb-chorus-sm flex items-center text-xxs text-carbon-500 dark:text-carbon-600">
|
||||
<span>{post.author?.name || 'CHORUS Team'}</span>
|
||||
<span className="mx-1">•</span>
|
||||
<span>{post.readingTime} min</span>
|
||||
</div>
|
||||
<p className="text-xs text-carbon-600 dark:text-carbon-400 leading-relaxed mb-2 line-clamp-3">
|
||||
{post.description}
|
||||
</p>
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Column 3 */}
|
||||
<div className="flex-1 space-y-4">
|
||||
{additionalPosts.slice(6, 9).map((post) => (
|
||||
<article key={post.slug} className="group border-b border-carbon-200 dark:border-carbon-700 pb-4 mb-4">
|
||||
<a href={`/posts/${post.slug}`} className="block">
|
||||
{/* <div className="mb-2">
|
||||
<span className="text-mulberry-600 dark:text-mulberry-400 text-xs font-bold uppercase tracking-wide">
|
||||
{post.tags?.[0] || 'Analysis'}
|
||||
</span>
|
||||
</div> */}
|
||||
<h3 className="text-sm font-bold text-carbon-950 dark:text-carbon-100 leading-tight mb-2 group-hover:text-carbon-700 dark:group-hover:text-mulberry-200 transition-colors">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
<div className="mb-chorus-sm flex items-center text-xxs text-carbon-500 dark:text-carbon-600">
|
||||
<span>{post.author?.name || 'CHORUS Team'}</span>
|
||||
<span className="mx-1">•</span>
|
||||
<span>{post.readingTime} min</span>
|
||||
</div>
|
||||
<p className="text-xs text-carbon-600 dark:text-carbon-400 leading-relaxed mb-2 line-clamp-3">
|
||||
{post.description}
|
||||
</p>
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Column 4 */}
|
||||
<div className="flex-1 space-y-4">
|
||||
{additionalPosts.slice(9, 12).map((post) => (
|
||||
<article key={post.slug} className="group border-b border-carbon-200 dark:border-carbon-700 pb-4 mb-4">
|
||||
<a href={`/posts/${post.slug}`} className="block">
|
||||
{/* <div className="mb-2">
|
||||
<span className="text-mulberry-600 dark:text-mulberry-400 text-xs font-bold uppercase tracking-wide">
|
||||
{post.tags?.[0] || 'Analysis'}
|
||||
</span>
|
||||
</div> */}
|
||||
<h3 className="text-sm font-bold text-carbon-950 dark:text-carbon-100 leading-tight mb-2 group-hover:text-carbon-700 dark:group-hover:text-mulberry-200 transition-colors">
|
||||
{post.title}
|
||||
</h3>
|
||||
<div className="mb-chorus-sm flex items-center text-xxs text-carbon-500 dark:text-carbon-600">
|
||||
<span>{post.author?.name || 'CHORUS Team'}</span>
|
||||
<span className="mx-1">•</span>
|
||||
<span>{post.readingTime} min</span>
|
||||
</div>
|
||||
<p className="text-xs text-carbon-600 dark:text-carbon-400 leading-relaxed mb-2 line-clamp-3">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile/Tablet Fallback - Single Column */}
|
||||
<div className="lg:hidden space-y-6">
|
||||
{additionalPosts.slice(0, 8).map((post) => (
|
||||
<article key={post.slug} className="group border-b border-carbon-200 dark:border-carbon-700 pb-4 mb-4">
|
||||
<a href={`/posts/${post.slug}`} className="block">
|
||||
<div className="mb-2">
|
||||
<span className="text-mulberry-600 dark:text-mulberry-400 text-xs font-bold uppercase tracking-wide">
|
||||
{post.tags?.[0] || 'Analysis'}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-carbon-950 dark:text-carbon-100 leading-tight mb-3 group-hover:text-carbon-700 dark:group-hover:text-mulberry-200 transition-colors">
|
||||
{post.title}
|
||||
</h3>
|
||||
<p className="text-sm text-carbon-600 dark:text-carbon-400 leading-relaxed mb-3">
|
||||
{post.description}
|
||||
</p>
|
||||
<div className="flex items-center text-xxs text-carbon-500 dark:text-carbon-600">
|
||||
<span>{post.author?.name || 'CHORUS Team'}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<time dateTime={post.date}>
|
||||
{new Date(post.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
</time>
|
||||
<span className="mx-2">•</span>
|
||||
<span>{post.readingTime} min read</span>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Recent Posts */}
|
||||
<section className="blog-container py-12">
|
||||
<h2 className="text-h3 font-logo text-carbon-950 dark:text-carbon-100 mb-8">Recent Posts</h2>
|
||||
{recentPosts.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
{recentPosts.map((post) => (
|
||||
<PostCard key={post.slug} post={post} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<div className="w-16 h-16 bg-carbon-200 dark:bg-carbon-800 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-carbon-600 dark:text-carbon-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-h5 font-semibold text-carbon-950 dark:text-carbon-200 mb-2">Coming Soon</h3>
|
||||
<p className="text-carbon-600 dark:text-carbon-400 max-w-md mx-auto">
|
||||
We're preparing some excellent content about contextual AI and agent orchestration.
|
||||
Check back soon for our first posts!
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<BlogFooter />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc'
|
||||
import { getPostData, getAllPostSlugs } from '@/lib/blog'
|
||||
import { getPostData } from '@/lib/blog'
|
||||
import BlogHeader from '@/components/BlogHeader'
|
||||
import BlogFooter from '@/components/BlogFooter'
|
||||
import Link from 'next/link'
|
||||
@@ -15,10 +15,8 @@ interface PostPageProps {
|
||||
}>
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const slugs = getAllPostSlugs()
|
||||
return slugs.map(({ params }) => ({ slug: params.slug }))
|
||||
}
|
||||
// Force dynamic rendering - no static generation
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function generateMetadata({ params }: PostPageProps) {
|
||||
const { slug } = await params
|
||||
@@ -102,38 +100,38 @@ export default async function PostPage({ params }: PostPageProps) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h1 className="text-h1 font-logo text-carbon-950 dark:text-mulberry-100 mb-6">
|
||||
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-h1 font-bold text-carbon-950 dark:text-mulberry-100 mb-6 leading-tight">
|
||||
{post.title}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-carbon-700 dark:text-carbon-300 leading-relaxed mb-8">
|
||||
<p className="text-base sm:text-lg md:text-xl text-carbon-700 dark:text-carbon-300 leading-relaxed mb-8">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between border-b border-carbon-300 dark:border-carbon-800 pb-8">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between border-b border-carbon-300 dark:border-carbon-800 pb-8 gap-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-mulberry-400 to-ocean-500 rounded-full flex items-center justify-center">
|
||||
<span className="text-carbon-950 font-semibold">
|
||||
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-mulberry-400 to-ocean-500 rounded-full flex items-center justify-center">
|
||||
<span className="text-carbon-950 font-semibold text-sm sm:text-base">
|
||||
{post.author.name.charAt(0)}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-carbon-950 dark:text-carbon-200 font-medium">
|
||||
<p className="text-carbon-950 dark:text-carbon-200 font-medium text-sm sm:text-base">
|
||||
{post.author.name}
|
||||
</p>
|
||||
{post.author.role && (
|
||||
<p className="text-carbon-600 dark:text-carbon-500 text-sm">
|
||||
<p className="text-carbon-600 dark:text-carbon-500 text-xs sm:text-sm">
|
||||
{post.author.role}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="blog-meta text-right">
|
||||
<time dateTime={post.date} className="block">
|
||||
<div className="blog-meta sm:text-right">
|
||||
<time dateTime={post.date} className="block text-sm sm:text-base">
|
||||
{formattedDate}
|
||||
</time>
|
||||
<span className="text-carbon-600 dark:text-carbon-600">
|
||||
<span className="text-carbon-600 dark:text-carbon-600 text-xs sm:text-sm">
|
||||
{post.readingTime} min read
|
||||
</span>
|
||||
</div>
|
||||
@@ -141,7 +139,7 @@ export default async function PostPage({ params }: PostPageProps) {
|
||||
</header>
|
||||
|
||||
{/* Article content */}
|
||||
<div className="prose prose-lg max-w-none">
|
||||
<div className="prose prose-lg max-w-none dark:prose-invert">
|
||||
<MDXRemote source={post.content} options={mdxOptions} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { getSortedPostsData } from '@/lib/blog'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// Force dynamic rendering to pick up volume-mounted content
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
// RSS feed configuration
|
||||
const SITE_URL = 'https://blog.chorus.services'
|
||||
const SITE_TITLE = 'CHORUS Services Blog'
|
||||
|
||||
Reference in New Issue
Block a user