6 Blog post

This commit is contained in:
2025-09-02 11:59:51 +02:00
parent 2a1412a52d
commit 148cb6d283
66 changed files with 10494 additions and 0 deletions

517
src/components/BlogPost.tsx Normal file
View File

@@ -0,0 +1,517 @@
import React from 'react'
import { BlogPost } from '@/data/blogPosts'
interface BlogPostCardProps {
post: BlogPost
onReadMore: (post: BlogPost) => void
}
export function BlogPostCard({ post, onReadMore }: BlogPostCardProps) {
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date)
}
return (
<article style={{
backgroundColor: 'white',
border: '2px solid #8B7D6B',
padding: '24px',
marginBottom: '32px',
position: 'relative',
boxShadow: '4px 4px 0 rgba(139, 125, 107, 0.2)'
}}>
{/* Featured Badge */}
{post.featured && (
<div style={{
position: 'absolute',
top: '-12px',
right: '24px',
backgroundColor: '#C89C2B',
color: '#F7F1E1',
padding: '4px 12px',
fontFamily: 'Pacifico, cursive',
fontSize: '14px',
transform: 'rotate(3deg)',
boxShadow: '2px 2px 4px rgba(0,0,0,0.2)'
}}>
Featured
</div>
)}
{/* Header Image */}
{(post.previewImage || post.images[0]) && (
<div style={{
marginBottom: '24px',
marginLeft: '-24px',
marginRight: '-24px',
marginTop: '-24px',
height: '300px',
backgroundImage: `url('${post.previewImage || post.images[0]}')`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundColor: 'white',
borderBottom: '2px solid #8B7D6B'
}} />
)}
{/* Category & Date */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px'
}}>
<span style={{
fontFamily: 'Space Mono, monospace',
fontSize: '12px',
textTransform: 'uppercase',
letterSpacing: '0.15em',
color: '#C89C2B',
fontWeight: 'bold'
}}>
{post.category}
</span>
<span style={{
fontFamily: 'Space Mono, monospace',
fontSize: '12px',
color: '#8B7D6B'
}}>
{formatDate(post.datePublished)}
</span>
</div>
{/* Title */}
<h2 style={{
fontFamily: 'Abril Fatface, serif',
fontSize: '28px',
fontWeight: '900',
color: '#1E1A17',
marginBottom: '16px',
lineHeight: '1.2'
}}>
{post.title}
</h2>
{/* Excerpt */}
<p style={{
fontFamily: 'Spectral, serif',
fontSize: '16px',
color: '#4A4A4A',
lineHeight: '1.6',
marginBottom: '20px'
}}>
{post.excerpt}
</p>
{/* Tags */}
<div style={{
display: 'flex',
flexWrap: 'wrap',
gap: '8px',
marginBottom: '20px'
}}>
{post.tags.slice(0, 3).map(tag => (
<span key={tag} style={{
backgroundColor: '#F7F1E1',
border: '1px solid #8B7D6B',
padding: '4px 8px',
fontFamily: 'Space Mono, monospace',
fontSize: '11px',
textTransform: 'uppercase',
letterSpacing: '0.05em',
color: '#8B7D6B'
}}>
{tag}
</span>
))}
</div>
{/* Read More Button */}
<button
onClick={() => onReadMore(post)}
style={{
backgroundColor: 'transparent',
border: '2px solid #C89C2B',
color: '#C89C2B',
padding: '12px 24px',
fontFamily: 'Staatliches, sans-serif',
fontSize: '16px',
letterSpacing: '0.1em',
textTransform: 'uppercase',
cursor: 'pointer',
transition: 'all 0.3s ease',
position: 'relative',
overflow: 'hidden'
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#C89C2B'
e.currentTarget.style.color = '#F7F1E1'
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'transparent'
e.currentTarget.style.color = '#C89C2B'
}}
>
Read Full Article
</button>
</article>
)
}
interface BlogPostModalProps {
post: BlogPost
onClose: () => void
}
export function BlogPostModal({ post, onClose }: BlogPostModalProps) {
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date)
}
// Convert markdown-style formatting to HTML
const formatContent = (content: string) => {
return content
.split('\n\n')
.map((paragraph, index) => {
// Handle headers
if (paragraph.startsWith('## ')) {
return (
<h3 key={index} style={{
fontFamily: 'Abril Fatface, serif',
fontSize: '24px',
fontWeight: '900',
color: '#1E1A17',
marginTop: '32px',
marginBottom: '16px'
}}>
{paragraph.replace('## ', '')}
</h3>
)
}
if (paragraph.startsWith('### ')) {
return (
<h4 key={index} style={{
fontFamily: 'Staatliches, sans-serif',
fontSize: '20px',
color: '#C89C2B',
marginTop: '24px',
marginBottom: '12px'
}}>
{paragraph.replace('### ', '')}
</h4>
)
}
// Handle lists
if (paragraph.startsWith('- ')) {
const items = paragraph.split('\n').filter(line => line.startsWith('- '))
return (
<ul key={index} style={{
marginBottom: '16px',
paddingLeft: '24px'
}}>
{items.map((item, i) => (
<li key={i} style={{
fontFamily: 'Spectral, serif',
fontSize: '16px',
color: '#4A4A4A',
lineHeight: '1.8',
marginBottom: '8px'
}}>
{item.replace('- ', '').replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')}
</li>
))}
</ul>
)
}
// Regular paragraphs with bold text support
const formattedText = paragraph.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
return (
<p key={index} style={{
fontFamily: 'Spectral, serif',
fontSize: '16px',
color: '#4A4A4A',
lineHeight: '1.8',
marginBottom: '16px'
}} dangerouslySetInnerHTML={{ __html: formattedText }} />
)
})
}
return (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(30, 26, 23, 0.8)',
zIndex: 1000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '20px',
overflowY: 'auto'
}} onClick={onClose}>
<article style={{
backgroundColor: '#F7F1E1',
maxWidth: '900px',
width: '100%',
maxHeight: '90vh',
overflowY: 'auto',
padding: '48px',
position: 'relative',
boxShadow: '0 10px 40px rgba(0,0,0,0.3)'
}} onClick={(e) => e.stopPropagation()}>
{/* Close Button */}
<button
onClick={onClose}
style={{
position: 'absolute',
top: '20px',
right: '20px',
background: 'transparent',
border: 'none',
fontSize: '32px',
cursor: 'pointer',
color: '#8B7D6B',
zIndex: 10
}}
>
×
</button>
{/* Header */}
<div style={{ marginBottom: '32px' }}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px'
}}>
<span style={{
fontFamily: 'Space Mono, monospace',
fontSize: '12px',
textTransform: 'uppercase',
letterSpacing: '0.15em',
color: '#C89C2B',
fontWeight: 'bold'
}}>
{post.category}
</span>
<span style={{
fontFamily: 'Space Mono, monospace',
fontSize: '12px',
color: '#8B7D6B'
}}>
{formatDate(post.datePublished)}
</span>
</div>
<h1 style={{
fontFamily: 'Abril Fatface, serif',
fontSize: '42px',
fontWeight: '900',
color: '#1E1A17',
lineHeight: '1.2',
marginBottom: '24px'
}}>
{post.title}
</h1>
<div className="newspaper-rule" style={{
width: '100px',
height: '2px',
backgroundColor: '#C89C2B',
margin: '24px 0'
}} />
</div>
{/* Content with interleaved images */}
<div style={{ marginBottom: '32px' }}>
{(() => {
const blocks = post.content.split('\n\n')
const nodes: React.ReactNode[] = []
const totalBlocks = blocks.length
const totalImages = post.images.length
const step = totalImages > 0 ? Math.ceil(totalBlocks / (totalImages + 1)) : Infinity
let imageIndex = 0
blocks.forEach((paragraph, index) => {
// Headers
if (paragraph.startsWith('## ')) {
nodes.push(
<h3 key={`h3-${index}`} style={{
fontFamily: 'Abril Fatface, serif',
fontSize: '24px',
fontWeight: '900',
color: '#1E1A17',
marginTop: '32px',
marginBottom: '16px'
}}>
{paragraph.replace('## ', '')}
</h3>
)
} else if (paragraph.startsWith('### ')) {
nodes.push(
<h4 key={`h4-${index}`} style={{
fontFamily: 'Staatliches, sans-serif',
fontSize: '20px',
color: '#C89C2B',
marginTop: '24px',
marginBottom: '12px'
}}>
{paragraph.replace('### ', '')}
</h4>
)
} else if (paragraph.startsWith('- ')) {
const items = paragraph.split('\n').filter(line => line.startsWith('- '))
nodes.push(
<ul key={`ul-${index}`} style={{
marginBottom: '16px',
paddingLeft: '24px'
}}>
{items.map((item, i) => (
<li
key={`li-${index}-${i}`}
style={{
fontFamily: 'Spectral, serif',
fontSize: '16px',
color: '#4A4A4A',
lineHeight: '1.8',
marginBottom: '8px'
}}
dangerouslySetInnerHTML={{ __html: item.replace('- ', '').replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') }}
/>
))}
</ul>
)
} else {
// Paragraphs
const formattedText = paragraph.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
nodes.push(
<p key={`p-${index}`} style={{
fontFamily: 'Spectral, serif',
fontSize: '16px',
color: '#4A4A4A',
lineHeight: '1.8',
marginBottom: '16px'
}} dangerouslySetInnerHTML={{ __html: formattedText }} />
)
}
// Interleave images evenly through the content
if (totalImages > 0 && imageIndex < totalImages && (index + 1) % step === 0) {
nodes.push(
<div
key={`img-${imageIndex}`}
style={{
margin: '16px 0 32px',
display: 'flex',
justifyContent: 'center'
}}
>
<div
style={{
border: '2px solid #8B7D6B',
backgroundColor: 'white',
padding: '6px',
maxWidth: '360px',
width: '100%'
}}
>
<img
src={post.images[imageIndex]}
alt={`Image ${imageIndex + 1}`}
style={{
display: 'block',
width: '100%',
height: 'auto',
backgroundColor: '#F7F1E1'
}}
/>
</div>
</div>
)
imageIndex++
}
})
// If any images remain, append them at the end
while (imageIndex < totalImages) {
nodes.push(
<div
key={`img-tail-${imageIndex}`}
style={{
margin: '16px 0 32px',
display: 'flex',
justifyContent: 'center'
}}
>
<div
style={{
border: '2px solid #8B7D6B',
backgroundColor: 'white',
padding: '6px',
maxWidth: '360px',
width: '100%'
}}
>
<img
src={post.images[imageIndex]}
alt={`Image ${imageIndex + 1}`}
style={{
display: 'block',
width: '100%',
height: 'auto',
backgroundColor: '#F7F1E1'
}}
/>
</div>
</div>
)
imageIndex++
}
return nodes
})()}
</div>
{/* Tags */}
<div style={{
borderTop: '2px solid #8B7D6B',
paddingTop: '24px',
display: 'flex',
flexWrap: 'wrap',
gap: '8px'
}}>
{post.tags.map(tag => (
<span key={tag} style={{
backgroundColor: 'white',
border: '1px solid #8B7D6B',
padding: '6px 12px',
fontFamily: 'Space Mono, monospace',
fontSize: '12px',
textTransform: 'uppercase',
letterSpacing: '0.05em',
color: '#8B7D6B'
}}>
{tag}
</span>
))}
</div>
</article>
</div>
)
}