Files
mintel.me/app/blog/page.tsx
2026-01-29 19:06:45 +01:00

192 lines
7.8 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { MediumCard } from '../../src/components/MediumCard';
import { SearchBar } from '../../src/components/SearchBar';
import { Tag } from '../../src/components/Tag';
import { blogPosts } from '../../src/data/blogPosts';
export default function BlogPage() {
const [searchQuery, setSearchQuery] = useState('');
const [filteredPosts, setFilteredPosts] = useState(blogPosts);
// Sort posts by date
const allPosts = [...blogPosts].sort((a, b) =>
new Date(b.date).getTime() - new Date(a.date).getTime()
);
// Get unique tags
const allTags = Array.from(new Set(allPosts.flatMap(post => post.tags || [])));
useEffect(() => {
const query = searchQuery.toLowerCase().trim();
if (query.startsWith('#')) {
const tag = query.slice(1);
setFilteredPosts(allPosts.filter(post =>
post.tags?.some(t => t.toLowerCase() === tag.toLowerCase())
));
} else {
setFilteredPosts(allPosts.filter(post => {
const title = post.title.toLowerCase();
const description = post.description.toLowerCase();
const tags = (post.tags || []).join(' ').toLowerCase();
return title.includes(query) || description.includes(query) || tags.includes(query);
}));
}
}, [searchQuery]);
const filterByTag = (tag: string) => {
setSearchQuery(`#${tag}`);
};
return (
<div className="container">
{/* Clean Hero Section */}
<section className="pt-10 pb-8 md:pt-12 md:pb-10 relative overflow-hidden">
{/* Animated Background */}
<div className="absolute inset-0 bg-gradient-to-br from-white via-slate-50/30 to-blue-50/20 animate-gradient-shift"></div>
{/* Morphing Blob */}
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="w-48 h-48 bg-gradient-to-br from-blue-200/15 via-purple-200/10 to-indigo-200/15 animate-morph"></div>
</div>
{/* Animated Drawing Paths */}
<svg className="absolute inset-0 w-full h-full pointer-events-none" viewBox="0 0 100 100">
<path d="M10,50 Q50,10 90,50 T90,90" stroke="rgba(59,130,246,0.1)" strokeWidth="0.5" fill="none" className="animate-draw"></path>
<path d="M10,70 Q50,30 90,70" stroke="rgba(147,51,234,0.1)" strokeWidth="0.5" fill="none" className="animate-draw-delay"></path>
<path d="M20,20 Q50,80 80,20" stroke="rgba(16,185,129,0.1)" strokeWidth="0.5" fill="none" className="animate-draw-reverse"></path>
</svg>
{/* Floating Shapes */}
<div className="absolute top-10 left-10 w-20 h-20 bg-blue-100/20 rounded-full animate-float-1"></div>
<div className="absolute top-20 right-20 w-16 h-16 bg-indigo-100/20 rotate-45 animate-float-2"></div>
<div className="absolute bottom-20 left-1/4 w-12 h-12 bg-purple-100/20 rounded-full animate-float-3"></div>
<div className="absolute bottom-10 right-1/3 w-24 h-24 bg-cyan-100/20 animate-float-4"></div>
<div className="max-w-3xl mx-auto px-6 relative z-10">
<div className="text-center animate-fade-in">
<h1 className="text-3xl md:text-4xl font-serif font-light text-slate-900 tracking-tight mb-3">
Marc Mintel
</h1>
<p className="text-base md:text-lg text-slate-600 leading-relaxed font-serif italic">
"A public notebook of things I figured out, mistakes I made, and tools I tested."
</p>
<div className="flex items-center justify-center gap-3 text-[13px] text-slate-500 font-sans mt-3">
<span className="inline-flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zm0 12a4 4 0 110-8 4 4 0 010 8z"/></svg>
Vulkaneifel, Germany
</span>
<span aria-hidden="true"></span>
<span>Digital problem solver</span>
</div>
</div>
</div>
</section>
{/* Search */}
<section className="mb-8 mt-8">
<div id="search-container">
<SearchBar value={searchQuery} onChange={setSearchQuery} />
</div>
</section>
{/* Topics */}
{allTags.length > 0 && (
<section className="mb-8">
<h2 className="text-lg font-semibold text-slate-800 mb-4">Topics</h2>
<div className="tag-cloud flex flex-wrap gap-2">
{allTags.map((tag, index) => (
<button
key={tag}
onClick={() => filterByTag(tag)}
className="inline-block"
>
<Tag tag={tag} index={index} />
</button>
))}
</div>
</section>
)}
{/* All Posts */}
<section>
<div id="posts-container" className="not-prose grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-4">
{filteredPosts.length === 0 ? (
<div className="empty-state col-span-full">
<p>No posts found matching your criteria.</p>
</div>
) : (
filteredPosts.map(post => (
<MediumCard key={post.slug} post={post} />
))
)}
</div>
</section>
<style jsx global>{`
@keyframes gradient-shift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.animate-gradient-shift {
background-size: 200% 200%;
animation: gradient-shift 20s ease infinite;
}
@keyframes morph {
0%, 100% { border-radius: 50%; transform: scale(1) rotate(0deg); }
25% { border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; transform: scale(1.1) rotate(90deg); }
50% { border-radius: 20% 80% 20% 80% / 80% 20% 80% 20%; transform: scale(0.9) rotate(180deg); }
75% { border-radius: 70% 30% 50% 50% / 50% 70% 30% 50%; transform: scale(1.05) rotate(270deg); }
}
.animate-morph {
animation: morph 25s ease-in-out infinite;
}
@keyframes draw {
to { stroke-dashoffset: 0; }
}
.animate-draw {
stroke-dasharray: 200;
stroke-dashoffset: 200;
animation: draw 8s ease-in-out infinite alternate;
}
.animate-draw-delay {
stroke-dasharray: 200;
stroke-dashoffset: 200;
animation: draw 8s ease-in-out infinite alternate 4s;
}
.animate-draw-reverse {
stroke-dasharray: 200;
stroke-dashoffset: 200;
animation: draw 8s ease-in-out infinite alternate-reverse 2s;
}
@keyframes float-1 {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-20px) rotate(180deg); }
}
.animate-float-1 { animation: float-1 15s ease-in-out infinite; }
@keyframes float-2 {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-15px) rotate(90deg); }
}
.animate-float-2 { animation: float-2 18s ease-in-out infinite; }
@keyframes float-3 {
0%, 100% { transform: translateY(0px) scale(1); }
50% { transform: translateY(-10px) scale(1.1); }
}
.animate-float-3 { animation: float-3 12s ease-in-out infinite; }
@keyframes float-4 {
0%, 100% { transform: translateY(0px) rotate(0deg); }
50% { transform: translateY(-25px) rotate(-90deg); }
}
.animate-float-4 { animation: float-4 20s ease-in-out infinite; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in { animation: fadeIn 0.6s ease-out both; }
`}</style>
</div>
);
}