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:
anthonyrawlins
2025-10-19 00:23:51 +11:00
parent 796924499d
commit 5e0be60c30
40 changed files with 1865 additions and 324 deletions

View File

@@ -8,17 +8,17 @@ const scheduledDirectory = path.join(process.cwd(), 'content/scheduled')
// Helper function to check if a post should be published
function shouldPublishPost(post: BlogMeta): boolean {
// If no publishDate is set, use the date field
const publishDate = post.publishDate || post.date
if (!publishDate) return true
// Don't publish draft posts
if (post.draft === true) return false
// Check if publish date has passed
// Require at least one valid date field
const publishDate = post.publishDate || post.date
if (!publishDate) return false
const publishDateTime = new Date(publishDate)
if (isNaN(publishDateTime.getTime())) return false
const now = new Date()
return publishDateTime <= now
}
@@ -46,7 +46,7 @@ function readMarkdownFiles(directory: string, basePath: string = ''): BlogPost[]
// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents)
const meta = matterResult.data as BlogMeta
const meta = matterResult.data as Partial<BlogMeta>
// Create slug from filename (remove .md extension)
const slug = item.replace(/\.md$/, '')
@@ -54,14 +54,42 @@ function readMarkdownFiles(directory: string, basePath: string = ''): BlogPost[]
// Calculate reading time (average 200 words per minute)
const wordCount = matterResult.content.split(/\s+/).length
const readingTime = Math.ceil(wordCount / 200)
// Basic validation and normalization
const rawDate = meta.date || meta.publishDate
if (!rawDate) {
console.warn(`Skipping '${fullPath}' - missing date/publishDate in frontmatter`)
return
}
const dateObj = new Date(rawDate as string)
if (isNaN(dateObj.getTime())) {
console.warn(`Skipping '${fullPath}' - invalid date value: ${rawDate}`)
return
}
if (!meta.title || !meta.description) {
console.warn(`Skipping '${fullPath}' - missing required title/description in frontmatter`)
return
}
const normalizedDate = meta.date ? (meta.date as string) : dateObj.toISOString().split('T')[0]
const normalizedPublishDate = meta.publishDate ? new Date(meta.publishDate as string).toISOString() : undefined
const post: BlogPost = {
slug,
title: meta.title,
description: meta.description,
content: matterResult.content,
date: normalizedDate,
publishDate: normalizedPublishDate,
draft: meta.draft === true,
author: meta.author || { name: 'CHORUS Team' },
tags: Array.isArray(meta.tags) ? (meta.tags as string[]) : [],
featured: meta.featured === true,
coverImage: meta.coverImage,
readingTime,
...meta,
}
posts.push(post)
} catch (error) {
console.error(`Error reading markdown file ${fullPath}:`, error)
@@ -116,9 +144,10 @@ export function getPostData(slug: string): BlogPost | null {
return post
}
export function getFeaturedPosts(): BlogPost[] {
export function getFeaturedPost(): BlogPost | null {
const allPosts = getSortedPostsData()
return allPosts.filter(post => post.featured)
// Return the most recent post as the featured post
return allPosts.length > 0 ? allPosts[0] : null
}
export function getPostsByTag(tag: string): BlogPost[] {
@@ -137,4 +166,4 @@ export function getAllTags(): string[] {
})
return Array.from(tags).sort()
}
}