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:
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user