blog design

This commit is contained in:
2026-01-29 21:13:54 +01:00
parent ec0a055c13
commit eafb740b1d
14 changed files with 291 additions and 385 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import { notFound } from 'next/navigation';
import { blogPosts } from '../../../src/data/blogPosts';
import { Tag } from '../../../src/components/Tag';
@@ -9,6 +9,9 @@ import { UL, LI } from '../../../src/components/ArticleList';
import { FileExamplesList } from '../../../src/components/FileExamplesList';
import { FileExampleManager } from '../../../src/data/fileExamples';
import { BlogPostClient } from '../../../src/components/BlogPostClient';
import { PageHeader } from '../../../src/components/PageHeader';
import { Section } from '../../../src/components/Section';
import { Reveal } from '../../../src/components/Reveal';
export async function generateStaticParams() {
return blogPosts.map((post) => ({
@@ -53,52 +56,33 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug:
}
return (
<div className="min-h-screen bg-white">
<div className="flex flex-col gap-24 py-12 md:py-24 overflow-hidden">
<BlogPostClient readingTime={readingTime} title={post.title} />
<main id="post-content" className="pt-24">
<section className="py-12 md:py-16">
<div className="max-w-3xl mx-auto px-6">
<div className="text-center">
<h1 className="text-3xl md:text-5xl font-serif font-bold text-slate-900 mb-6 leading-tight tracking-tight">
{post.title}
</h1>
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-slate-600 mb-6 font-sans">
<time dateTime={post.date} className="flex items-center gap-1.5 px-3 py-1 bg-slate-50 rounded-full">
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20">
<path d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zM4 8h12v8H4V8z"/>
</svg>
{formattedDate}
</time>
<span className="text-slate-400"></span>
<span className="flex items-center gap-1.5 px-3 py-1 bg-slate-50 rounded-full">
<svg className="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd"/>
</svg>
{readingTime} min
</span>
</div>
<PageHeader
title={post.title}
description={post.description}
backLink={{ href: '/blog', label: 'Zurück zum Blog' }}
backgroundSymbol="B"
/>
<p className="text-xl md:text-2xl text-slate-600 leading-relaxed font-serif italic mb-8 max-w-2xl mx-auto">
{post.description}
</p>
{post.tags && post.tags.length > 0 && (
<div className="flex flex-wrap justify-center gap-2 mb-8">
{post.tags.map((tag, index) => (
<Tag key={tag} tag={tag} index={index} className="text-xs" />
))}
</div>
)}
</div>
</div>
</section>
<section className="max-w-3xl mx-auto px-6 pb-24">
<main id="post-content">
<Section number="01" title="Inhalt">
<div className="prose prose-slate max-w-none">
<p>{post.description}</p>
<div className="flex flex-wrap items-center gap-4 text-sm text-slate-400 mb-12 font-mono uppercase tracking-widest">
<time dateTime={post.date}>{formattedDate}</time>
<span></span>
<span>{readingTime} min read</span>
</div>
{post.tags && post.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-12">
{post.tags.map((tag, index) => (
<Tag key={tag} tag={tag} index={index} className="text-xs" />
))}
</div>
)}
{slug === 'first-note' && (
<>
<LeadParagraph>
@@ -208,7 +192,7 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug:
</>
)}
</div>
</section>
</Section>
</main>
</div>
);

View File

@@ -6,6 +6,8 @@ import { MediumCard } from '../../src/components/MediumCard';
import { SearchBar } from '../../src/components/SearchBar';
import { Tag } from '../../src/components/Tag';
import { blogPosts } from '../../src/data/blogPosts';
import { PageHeader } from '../../src/components/PageHeader';
import { Reveal } from '../../src/components/Reveal';
export default function BlogPage() {
const [searchQuery, setSearchQuery] = useState('');
@@ -41,152 +43,64 @@ export default function BlogPage() {
};
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-slate-100/20 animate-gradient-shift"></div>
<div className="flex flex-col gap-24 py-12 md:py-24 overflow-hidden">
<PageHeader
title={<>Blog <br /><span className="text-slate-200">& Notes.</span></>}
description="A public notebook of things I figured out, mistakes I made, and tools I tested."
backgroundSymbol="B"
/>
{/* 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-slate-200/15 via-slate-100/10 to-slate-200/15 animate-morph"></div>
</div>
<section className="narrow-container">
<div className="grid grid-cols-1 md:grid-cols-12 gap-16">
{/* Sidebar / Filter area */}
<div className="md:col-span-4">
<div className="sticky top-32 space-y-16">
<Reveal>
<div className="space-y-4">
<h3 className="text-[10px] font-bold uppercase tracking-[0.3em] text-slate-400">Suchen</h3>
<SearchBar value={searchQuery} onChange={setSearchQuery} />
</div>
</Reveal>
{/* 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>
{allTags.length > 0 && (
<Reveal delay={0.2}>
<div className="space-y-6">
<h3 className="text-[10px] font-bold uppercase tracking-[0.3em] text-slate-400">Themen</h3>
<div className="flex flex-wrap gap-2">
{allTags.map((tag, index) => (
<button
key={tag}
onClick={() => filterByTag(tag)}
className="text-left"
>
<Tag tag={tag} index={index} />
</button>
))}
</div>
</div>
</Reveal>
)}
</div>
</div>
{/* Floating Shapes */}
<div className="absolute top-10 left-10 w-20 h-20 bg-slate-100/20 rounded-full animate-float-1"></div>
<div className="absolute top-20 right-20 w-16 h-16 bg-slate-100/20 rotate-45 animate-float-2"></div>
<div className="absolute bottom-20 left-1/4 w-12 h-12 bg-slate-100/20 rounded-full animate-float-3"></div>
<div className="absolute bottom-10 right-1/3 w-24 h-24 bg-slate-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>
{/* Posts area */}
<div className="md:col-span-8">
<div id="posts-container" className="divide-y divide-slate-50">
{filteredPosts.length === 0 ? (
<div className="empty-state">
<p>No posts found matching your criteria.</p>
</div>
) : (
filteredPosts.map((post, i) => (
<Reveal key={post.slug} delay={0.1 * i} width="100%">
<MediumCard post={post} />
</Reveal>
))
)}
</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>
);
}

View File

@@ -1,38 +1,22 @@
import * as React from 'react';
import { ArrowLeft } from 'lucide-react';
import Link from 'next/link';
import { Reveal } from '../../src/components/Reveal';
import { PageHeader } from '../../src/components/PageHeader';
import { Section } from '../../src/components/Section';
export default function ContactPage() {
return (
<div className="flex flex-col gap-24 py-24 md:py-48 overflow-hidden">
<section className="narrow-container relative">
<div className="absolute -left-24 -top-24 text-[20rem] font-bold text-slate-50 select-none -z-10">@</div>
<Link href="/" className="inline-flex items-center gap-2 text-slate-300 hover:text-slate-900 mb-16 transition-colors font-mono text-xs uppercase tracking-[0.3em]">
<ArrowLeft className="w-3 h-3" /> Zurück
</Link>
<div className="space-y-16">
<Reveal>
<h1 className="text-6xl md:text-8xl font-bold text-slate-900 tracking-tighter leading-[0.9]">
Kontakt <br />
<span className="text-slate-200">& Anfrage.</span>
</h1>
</Reveal>
<div className="flex flex-col gap-24 py-12 md:py-24 overflow-hidden">
<PageHeader
title={<>Kontakt <br /><span className="text-slate-200">& Anfrage.</span></>}
description="Haben Sie ein Projekt im Kopf? Schreiben Sie mir einfach. Ich antworte meistens innerhalb von 24 Stunden."
backLink={{ href: '/', label: 'Zurück' }}
backgroundSymbol="@"
/>
<Reveal delay={0.2}>
<p className="text-2xl md:text-3xl text-slate-500 font-serif italic leading-tight max-w-2xl">
Haben Sie ein Projekt im Kopf? Schreiben Sie mir einfach. Ich antworte meistens innerhalb von 24 Stunden.
</p>
</Reveal>
</div>
</section>
<section className="narrow-container">
<Section number="01" title="Direkt">
<div className="grid grid-cols-1 gap-24">
<Reveal delay={0.4}>
<div className="space-y-8">
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400">Direkt</h2>
<a
href="mailto:marc@mintel.me"
className="group block space-y-2"
@@ -59,7 +43,7 @@ export default function ContactPage() {
</div>
</Reveal>
</div>
</section>
</Section>
</div>
);
}

View File

@@ -53,7 +53,7 @@
}
a {
@apply text-slate-900 hover:text-slate-700 transition-colors underline underline-offset-4;
@apply text-slate-900 hover:text-slate-700 transition-colors no-underline;
}
ul, ol {

View File

@@ -1,7 +1,8 @@
import * as React from 'react';
import Link from 'next/link';
import { ArrowRight, Minus, Zap, Shield, Clock } from 'lucide-react';
import { ArrowRight, Zap, Shield } from 'lucide-react';
import { Reveal } from '../src/components/Reveal';
import { Section } from '../src/components/Section';
export default function LandingPage() {
return (
@@ -48,12 +49,10 @@ export default function LandingPage() {
</section>
{/* Was ich mache - Designed Grid */}
<section className="narrow-container relative">
<div className="absolute -right-24 -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10">01</div>
<Section number="01" title="Das Versprechen" numberPosition="right">
<div className="grid grid-cols-1 md:grid-cols-12 gap-12">
<div className="md:col-span-5 space-y-8">
<Reveal>
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400">Das Versprechen</h2>
<p className="text-4xl font-bold text-slate-900 leading-[1.1]">
Direkt. <br />
Sauber. <br />
@@ -87,13 +86,10 @@ export default function LandingPage() {
</div>
</div>
</div>
</section>
</Section>
{/* Comparison - Visual Path */}
<section className="narrow-container relative">
<div className="absolute -left-24 -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10">02</div>
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400 mb-24">Der Unterschied</h2>
<Section number="02" title="Der Unterschied">
<div className="space-y-32">
{/* Row 1 */}
<Reveal>
@@ -132,15 +128,13 @@ export default function LandingPage() {
</div>
</Reveal>
</div>
</section>
</Section>
{/* Für wen - Designed Box */}
<section className="narrow-container relative">
<div className="absolute -right-24 -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10">03</div>
<Section number="03" title="Zielgruppe" numberPosition="right">
<Reveal>
<div className="relative p-12 md:p-24 border border-slate-100">
<div className="absolute top-0 left-12 w-px h-24 bg-slate-900 -translate-y-12"></div>
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400 mb-12">Zielgruppe</h2>
<p className="text-4xl md:text-5xl font-bold text-slate-900 leading-tight mb-16 tracking-tighter">
Für Unternehmen, die <br />
<span className="text-slate-300 underline decoration-slate-100 underline-offset-8">Ergebnisse</span> wollen.
@@ -160,12 +154,10 @@ export default function LandingPage() {
</div>
</div>
</Reveal>
</section>
</Section>
{/* Leistungen - Visual Cards */}
<section className="narrow-container relative">
<div className="absolute -left-24 -top-32 text-[15rem] font-bold text-slate-50 select-none -z-10">04</div>
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400 mb-24">Leistungen</h2>
<Section number="04" title="Leistungen">
<div className="grid grid-cols-1 gap-32">
<Reveal>
<div className="group relative">
@@ -198,8 +190,7 @@ export default function LandingPage() {
</div>
</Reveal>
</div>
</section>
</Section>
</div>
);
}

View File

@@ -1,37 +1,24 @@
import * as React from 'react';
import { Info, ArrowLeft, Minus } from 'lucide-react';
import Link from 'next/link';
import { Info, Minus } from 'lucide-react';
import { Reveal } from '../../src/components/Reveal';
import { PageHeader } from '../../src/components/PageHeader';
import { Section } from '../../src/components/Section';
export default function WebsitesPage() {
return (
<div className="flex flex-col gap-48 py-24 md:py-48 overflow-hidden">
{/* Header - Designed */}
<section className="narrow-container relative">
<div className="absolute -left-24 -top-24 text-[20rem] font-bold text-slate-50 select-none -z-10"></div>
<Link href="/" className="inline-flex items-center gap-2 text-slate-300 hover:text-slate-900 mb-16 transition-colors font-mono text-xs uppercase tracking-[0.3em]">
<ArrowLeft className="w-3 h-3" /> Zurück
</Link>
<Reveal>
<h1 className="text-6xl md:text-8xl font-bold text-slate-900 tracking-tighter leading-[0.9] mb-12">
Websites <br />
<span className="text-slate-200">& Preise.</span>
</h1>
</Reveal>
<Reveal delay={0.2}>
<p className="text-2xl md:text-3xl text-slate-500 font-serif italic leading-tight max-w-2xl">
Ich baue digitale Systeme mit klaren Preisen und Ergebnissen keine Stunden, keine Überraschungen.
</p>
</Reveal>
</section>
<div className="flex flex-col gap-48 py-12 md:py-24 overflow-hidden">
<PageHeader
title={<>Websites <br /><span className="text-slate-200">& Preise.</span></>}
description="Ich baue digitale Systeme mit klaren Preisen und Ergebnissen keine Stunden, keine Überraschungen."
backLink={{ href: '/', label: 'Zurück' }}
backgroundSymbol="€"
/>
{/* 1. Website - Designed Pricing */}
<section className="narrow-container relative">
<div className="absolute -right-24 -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10">01</div>
<Section number="01" title="Die Basis">
<div className="space-y-20">
<Reveal>
<div className="border-l-4 border-slate-900 pl-12 py-4">
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400 mb-12">Die Basis</h2>
<div className="flex flex-col md:flex-row md:items-baseline gap-6 mb-8">
<div className="text-7xl md:text-9xl font-bold text-slate-900 tracking-tighter">6.000 </div>
<div className="text-slate-300 font-serif italic text-2xl">einmalig</div>
@@ -66,12 +53,10 @@ export default function WebsitesPage() {
</div>
</Reveal>
</div>
</section>
</Section>
{/* 2. Entwicklung - Designed Grid */}
<section className="narrow-container relative">
<div className="absolute -left-24 -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10">02</div>
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400 mb-24">Produktion</h2>
<Section number="02" title="Produktion" numberPosition="left">
<div className="space-y-32">
{/* Seite */}
<Reveal>
@@ -128,15 +113,13 @@ export default function WebsitesPage() {
</div>
</Reveal>
</div>
</section>
</Section>
{/* 5. Betrieb - Designed Box */}
<section className="narrow-container relative">
<div className="absolute -right-24 -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10">03</div>
<Section number="03" title="Betrieb" numberPosition="right">
<Reveal width="100%">
<div className="bg-slate-50 border-2 border-slate-900 p-16 md:p-24 space-y-16 relative overflow-hidden -mx-4 md:-mx-24">
<div className="absolute top-0 right-0 text-[20rem] font-bold text-slate-200/20 select-none translate-x-1/4 -translate-y-1/4">B</div>
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400">Betrieb</h2>
<div className="flex items-baseline gap-6">
<div className="text-7xl md:text-9xl font-bold tracking-tighter text-slate-900">120 </div>
<div className="text-slate-400 font-serif italic text-2xl">/ Monat</div>
@@ -160,8 +143,7 @@ export default function WebsitesPage() {
</div>
</div>
</Reveal>
</section>
</Section>
</div>
);
}

View File

@@ -95,19 +95,19 @@ export const BlogPostClient: React.FC<BlogPostClientProps> = ({ readingTime, tit
<svg className="w-5 h-5 group-hover:-translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
<span className="font-medium">Back</span>
<span className="font-bold uppercase tracking-widest text-xs">Zurück</span>
</button>
<div className="flex items-center gap-3">
<span className="text-sm text-slate-500 font-sans hidden sm:inline">
<span className="text-[10px] font-bold uppercase tracking-widest text-slate-400 hidden sm:inline">
{readingTime} min read
</span>
<button
onClick={handleShare}
className="share-button-top flex items-center gap-1.5 px-3 py-1.5 bg-slate-100 hover:bg-slate-200 border border-slate-200 hover:border-slate-300 rounded-full transition-all duration-200"
className="share-button-top flex items-center gap-1.5 px-3 py-1.5 bg-slate-50 hover:bg-slate-900 hover:text-white border border-slate-100 hover:border-slate-900 transition-all duration-200"
aria-label="Share this post"
>
<svg className="w-4 h-4 text-slate-600 group-hover:text-slate-900 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"/>
</svg>
</button>
@@ -115,15 +115,15 @@ export const BlogPostClient: React.FC<BlogPostClientProps> = ({ readingTime, tit
</div>
</nav>
<div className="mt-16 pt-8 border-t border-slate-200 text-center">
<div className="mt-16 pt-8 border-t border-slate-50 text-center">
<button
onClick={handleBack}
className="inline-flex items-center gap-2 px-6 py-3 bg-white border-2 border-slate-200 text-slate-700 rounded-full hover:border-slate-900 hover:text-slate-900 hover:shadow-md transition-all duration-300 font-medium group"
className="inline-flex items-center gap-2 px-8 py-4 bg-white border-2 border-slate-900 text-slate-900 hover:bg-slate-900 hover:text-white transition-all duration-300 font-bold uppercase tracking-widest text-xs group"
>
<svg className="w-5 h-5 group-hover:-translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-4 h-4 group-hover:-translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Back to all posts
Alle Beiträge
</button>
</div>
</>

View File

@@ -1,35 +1,32 @@
import React from 'react';
import * as React from 'react';
export const Footer: React.FC = () => {
const currentYear = new Date().getFullYear();
return (
<footer className="border-t border-slate-200 py-12 mt-20 bg-gradient-to-b from-white to-slate-50">
<div className="max-w-3xl mx-auto px-6">
{/* Main footer content - all centered */}
<div className="flex flex-col items-center justify-center gap-4 mb-6">
<div className="flex items-center gap-3">
<div className="w-6 h-6 bg-slate-900 rounded-full flex items-center justify-center">
<span className="text-white text-xs font-bold">M</span>
<footer className="py-24 mt-48 border-t border-slate-100">
<div className="narrow-container">
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-end">
<div className="space-y-8">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-slate-900 flex items-center justify-center">
<span className="text-white text-sm font-bold">M</span>
</div>
<span className="text-xl font-bold text-slate-900 tracking-tighter">Marc Mintel</span>
</div>
<span className="text-sm text-slate-900 font-medium">Marc Mintel</span>
<p className="text-2xl text-slate-400 font-serif italic leading-tight max-w-xs">
Digitale Systeme ohne Overhead.
</p>
</div>
<p className="text-sm text-slate-600 font-serif italic text-center max-w-md">
Write things down. Don't forget. Maybe help someone.
</p>
<div className="flex items-center gap-3 text-sm">
<span className="text-slate-500 font-sans">© {currentYear}</span>
<div className="flex flex-col md:items-end gap-4 text-sm font-mono text-slate-300 uppercase tracking-widest">
<span>© {currentYear}</span>
<div className="flex gap-8">
<a href="mailto:marc@mintel.me" className="hover:text-slate-900 transition-colors no-underline">Email</a>
<a href="https://github.com/marcmintel" className="hover:text-slate-900 transition-colors no-underline">GitHub</a>
</div>
</div>
</div>
{/* Subtle tagline */}
<div className="text-center pt-6 border-t border-slate-200/50">
<p className="text-xs text-slate-400 font-sans">
A public notebook of digital problem solving
</p>
</div>
</div>
</footer>
);

View File

@@ -11,12 +11,12 @@ export const Header: React.FC = () => {
return (
<header className="bg-white/80 backdrop-blur-md sticky top-0 z-50">
<div className="max-w-4xl mx-auto px-6 py-8 flex items-center justify-between">
<div className="narrow-container py-8 flex items-center justify-between">
<Link href="/" className="flex items-center gap-3 group">
<div className="w-10 h-10 bg-slate-900 rounded-sm flex items-center justify-center group-hover:bg-slate-700 transition-colors">
<div className="w-10 h-10 bg-slate-900 flex items-center justify-center group-hover:bg-slate-700 transition-colors">
<span className="text-white text-lg font-bold">M</span>
</div>
<span className="text-slate-900 font-bold tracking-tight text-xl">Marc Mintel</span>
<span className="text-slate-900 font-bold tracking-tighter text-2xl">Marc Mintel</span>
</Link>
<nav className="flex items-center gap-8">
@@ -38,7 +38,7 @@ export const Header: React.FC = () => {
</Link>
<Link
href="/contact"
className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-900 bg-white border border-slate-200 rounded-full px-5 py-2.5 hover:border-slate-900 hover:shadow-sm transition-all duration-300"
className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-900 border-2 border-slate-900 px-5 py-2.5 hover:bg-slate-900 hover:text-white transition-all duration-300"
>
Anfrage
</Link>

View File

@@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import Link from 'next/link';
interface Post {
@@ -14,7 +14,7 @@ interface MediumCardProps {
}
export const MediumCard: React.FC<MediumCardProps> = ({ post }) => {
const { title, description, date, slug, tags = [] } = post;
const { title, description, date, slug } = post;
const formattedDate = new Date(date).toLocaleDateString('en-US', {
month: 'short',
@@ -22,43 +22,24 @@ export const MediumCard: React.FC<MediumCardProps> = ({ post }) => {
year: 'numeric',
});
const wordCount = description.split(/\s+/).length;
const readingTime = Math.max(1, Math.ceil(wordCount / 200));
const markerSeed = Math.abs(title.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0)) % 7;
return (
<Link href={`/blog/${slug}`} className="post-link block group">
<article className="post-card bg-white border border-slate-200/80 rounded-lg px-4 py-3 transition-all duration-200 group-hover:border-slate-300 group-hover:shadow-sm">
<div className="flex items-start justify-between gap-4">
<h3 className="m-0 text-sm font-semibold leading-snug tracking-tight">
<span
className="relative inline-block text-slate-900 marker-title"
style={{ '--marker-seed': markerSeed } as React.CSSProperties}
>
{title}
</span>
</h3>
<time className="text-[11px] text-slate-500 tabular-nums whitespace-nowrap leading-none pt-0.5">
{formattedDate}
</time>
</div>
<p className="post-excerpt mt-2 mb-0 text-[13px] leading-relaxed text-slate-600 line-clamp-3">
<Link href={`/blog/${slug}`} className="group block">
<article className="space-y-3 py-8 border-b border-slate-50 group-hover:border-slate-900 transition-colors">
<time className="text-[10px] font-mono text-slate-300 uppercase tracking-widest group-hover:text-slate-900 transition-colors">
{formattedDate}
</time>
<h3 className="text-3xl font-bold text-slate-900 tracking-tighter group-hover:text-slate-900 transition-colors">
{title}
</h3>
<p className="text-xl text-slate-500 font-serif italic leading-tight line-clamp-2">
{description}
</p>
<div className="mt-3 flex items-center justify-between gap-3">
<span className="text-[11px] text-slate-500 tabular-nums leading-none">{readingTime} min</span>
{tags.length > 0 && (
<div className="flex flex-wrap items-center justify-end gap-1">
{tags.slice(0, 3).map((tag: string) => (
<span key={tag} className="inline-flex items-center rounded-full bg-slate-100/80 border border-slate-200/60 px-2 py-0.5 text-[11px] text-slate-700 leading-none">
{tag}
</span>
))}
</div>
)}
<div className="pt-4 flex items-center gap-4 text-slate-900 font-bold text-sm group/link">
Lesen
<div className="w-8 h-px bg-slate-900 group-hover:w-12 transition-all"></div>
</div>
</article>
</Link>

View File

@@ -0,0 +1,53 @@
import * as React from 'react';
import { ArrowLeft } from 'lucide-react';
import Link from 'next/link';
import { Reveal } from './Reveal';
interface PageHeaderProps {
title: React.ReactNode;
description?: string;
backLink?: {
href: string;
label: string;
};
backgroundSymbol?: string;
}
export const PageHeader: React.FC<PageHeaderProps> = ({
title,
description,
backLink,
backgroundSymbol
}) => {
return (
<section className="narrow-container relative">
{backgroundSymbol && (
<div className="absolute -left-24 -top-24 text-[20rem] font-bold text-slate-50 select-none -z-10">
{backgroundSymbol}
</div>
)}
{backLink && (
<Link href={backLink.href} className="inline-flex items-center gap-2 text-slate-300 hover:text-slate-900 mb-16 transition-colors font-mono text-xs uppercase tracking-[0.3em]">
<ArrowLeft className="w-3 h-3" /> {backLink.label}
</Link>
)}
<div className="space-y-16">
<Reveal>
<h1 className="text-6xl md:text-8xl font-bold text-slate-900 tracking-tighter leading-[0.9]">
{title}
</h1>
</Reveal>
{description && (
<Reveal delay={0.2}>
<p className="text-2xl md:text-3xl text-slate-500 font-serif italic leading-tight max-w-2xl">
{description}
</p>
</Reveal>
)}
</div>
</section>
);
};

View File

@@ -1,13 +1,15 @@
'use client';
import React, { useState, useRef } from 'react';
import * as React from 'react';
import { useState, useRef } from 'react';
interface SearchBarProps {
value?: string;
onChange?: (value: string) => void;
size?: 'small' | 'large';
}
export const SearchBar: React.FC<SearchBarProps> = ({ value: propValue, onChange }) => {
export const SearchBar: React.FC<SearchBarProps> = ({ value: propValue, onChange, size = 'small' }) => {
const [internalValue, setInternalValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const [isFocused, setIsFocused] = useState(false);
@@ -41,39 +43,35 @@ export const SearchBar: React.FC<SearchBarProps> = ({ value: propValue, onChange
};
return (
<div className="relative w-full max-w-2xl mx-auto">
<div className="relative w-full">
<div className="relative flex items-center">
<div className="relative flex-1">
<input
ref={inputRef}
type="text"
placeholder="Search"
value={value}
className={`w-full px-3 py-2 text-[14px] border border-slate-200 rounded-md bg-transparent transition-colors font-sans focus:outline-none ${
isFocused ? 'bg-white border-slate-300' : 'hover:border-slate-300'
}`}
onChange={handleInput}
onKeyDown={handleKeyDown}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
aria-label="Search blog posts"
/>
<input
ref={inputRef}
type="text"
placeholder="Suchen..."
value={value}
className={`w-full px-0 py-2 font-bold text-slate-900 bg-transparent border-b-2 transition-all focus:outline-none placeholder:text-slate-100 ${
size === 'large' ? 'text-2xl md:text-4xl py-4 border-b-4' : 'text-lg'
} ${
isFocused ? 'border-slate-900' : 'border-slate-100'
}`}
onChange={handleInput}
onKeyDown={handleKeyDown}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
aria-label="Search blog posts"
/>
{value && (
<button
onClick={clearSearch}
className="absolute right-1.5 top-1/2 -translate-y-1/2 h-7 px-2 inline-flex items-center justify-center rounded text-[12px] text-slate-500 hover:text-slate-700 hover:bg-slate-100 transition-colors"
aria-label="Clear search"
type="button"
>
Clear
</button>
)}
</div>
<div className="hidden md:flex items-center gap-2 ml-3 text-xs text-slate-400 font-sans">
<kbd className="bg-slate-100 px-2 py-1 rounded border border-slate-200">ESC</kbd>
</div>
{value && (
<button
onClick={clearSearch}
className="absolute right-0 top-1/2 -translate-y-1/2 text-[10px] font-bold uppercase tracking-widest text-slate-400 hover:text-slate-900 transition-colors"
aria-label="Clear search"
type="button"
>
Löschen
</button>
)}
</div>
</div>
);

View File

@@ -0,0 +1,36 @@
import * as React from 'react';
import { Reveal } from './Reveal';
interface SectionProps {
number?: string;
title?: string;
children: React.ReactNode;
className?: string;
numberPosition?: 'left' | 'right';
delay?: number;
}
export const Section: React.FC<SectionProps> = ({
number,
title,
children,
className = "",
numberPosition = 'left',
delay = 0
}) => {
return (
<section className={`narrow-container relative ${className}`}>
{number && (
<div className={`absolute ${numberPosition === 'left' ? '-left-24' : '-right-24'} -top-12 text-[15rem] font-bold text-slate-50 select-none -z-10`}>
{number}
</div>
)}
{title && (
<Reveal delay={delay}>
<h2 className="text-sm font-bold uppercase tracking-[0.3em] text-slate-400 mb-24">{title}</h2>
</Reveal>
)}
{children}
</section>
);
};

View File

@@ -1,6 +1,4 @@
'use client';
import React from 'react';
import * as React from 'react';
import Link from 'next/link';
interface TagProps {
@@ -9,25 +7,13 @@ interface TagProps {
className?: string;
}
const getColorClass = (tag: string) => {
const tagLower = tag.toLowerCase();
if (tagLower.includes('meta') || tagLower.includes('learning')) return 'highlighter-yellow';
if (tagLower.includes('debug') || tagLower.includes('tools')) return 'highlighter-pink';
if (tagLower.includes('ai') || tagLower.includes('automation')) return 'highlighter-blue';
if (tagLower.includes('script') || tagLower.includes('code')) return 'highlighter-green';
return 'highlighter-yellow'; // default
};
export const Tag: React.FC<TagProps> = ({ tag, index, className = '' }) => {
const colorClass = getColorClass(tag);
export const Tag: React.FC<TagProps> = ({ tag, className = '' }) => {
return (
<Link
href={`/tags/${tag}`}
className={`highlighter-tag ${colorClass} ${className} inline-block text-xs font-bold px-2.5 py-1 rounded cursor-pointer transition-all duration-200 relative overflow-hidden group`}
style={{ '--tag-index': index } as React.CSSProperties}
className={`inline-block text-[10px] font-bold uppercase tracking-[0.2em] text-slate-400 border border-slate-100 px-3 py-1.5 hover:border-slate-900 hover:text-slate-900 transition-all duration-300 ${className}`}
>
<span className="relative z-10">{tag}</span>
{tag}
</Link>
);
};