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

@@ -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>
);
};