feat: ultra-aggressive mobile spacing refinement & native fidelity navigation redesign
This commit is contained in:
@@ -7,7 +7,7 @@ export const Footer: React.FC = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="relative py-16 mt-24 border-t border-slate-100 bg-white z-10">
|
||||
<footer className="relative py-10 md:py-16 mt-8 md:mt-24 border-t border-slate-100 bg-white z-10">
|
||||
{/* Tech border at top */}
|
||||
<div className="absolute top-0 left-0 right-0 h-px overflow-hidden">
|
||||
<div
|
||||
@@ -20,14 +20,19 @@ export const Footer: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<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="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 items-end">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Image src={LogoBlack} alt="Marc Mintel" height={72} />
|
||||
<Image
|
||||
src={LogoBlack}
|
||||
alt="Marc Mintel"
|
||||
height={64}
|
||||
className="h-12 md:h-16 w-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:items-end gap-4 text-sm font-mono text-slate-300 uppercase tracking-widest">
|
||||
<div className="flex flex-col md:items-end gap-3 md:gap-4 text-xs md:text-sm font-mono text-slate-300 uppercase tracking-widest">
|
||||
<span>© {currentYear}</span>
|
||||
<div className="flex gap-8">
|
||||
<a
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
@@ -10,6 +10,7 @@ import IconWhite from "../assets/logo/Icon White Transparent.svg";
|
||||
export const Header: React.FC = () => {
|
||||
const pathname = usePathname();
|
||||
const [isScrolled, setIsScrolled] = React.useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
@@ -19,18 +20,44 @@ export const Header: React.FC = () => {
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
// Close mobile menu on pathname change and handle body scroll lock
|
||||
React.useEffect(() => {
|
||||
setIsMobileMenuOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobileMenuOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "unset";
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = "unset";
|
||||
};
|
||||
}, [isMobileMenuOpen]);
|
||||
|
||||
const isActive = (path: string) => pathname === path;
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/about", label: "Über mich" },
|
||||
{ href: "/websites", label: "Websites" },
|
||||
{ href: "/case-studies", label: "Case Studies", prefix: true },
|
||||
{ href: "/blog", label: "Blog", prefix: true },
|
||||
];
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`sticky top-0 z-50 transition-all duration-500 ${
|
||||
isScrolled
|
||||
? "bg-white/70 backdrop-blur-xl border-b border-slate-100 shadow-sm shadow-slate-100/50"
|
||||
: "bg-white/80 backdrop-blur-md border-b border-slate-50"
|
||||
}`}
|
||||
>
|
||||
<header className="sticky top-0 z-[100] w-full">
|
||||
{/* Decoupled Background Layer - Prevents backdrop-filter parent context bugs */}
|
||||
<div
|
||||
className={`absolute inset-0 transition-all duration-500 -z-10 ${
|
||||
isScrolled
|
||||
? "bg-white/70 backdrop-blur-xl border-b border-slate-100 shadow-sm shadow-slate-100/50"
|
||||
: "bg-white/80 backdrop-blur-md border-b border-slate-50"
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Animated tech border at bottom */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px overflow-hidden">
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px overflow-hidden pointer-events-none">
|
||||
<div
|
||||
className="h-full w-full"
|
||||
style={{
|
||||
@@ -42,28 +69,24 @@ export const Header: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="narrow-container py-4 flex items-center justify-between">
|
||||
<div className="narrow-container py-4 flex items-center justify-between relative z-10">
|
||||
<Link href="/" className="flex items-center gap-4 group">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 bg-black rounded-xl flex items-center justify-center group-hover:scale-105 transition-all duration-500 shadow-sm shrink-0 relative overflow-hidden">
|
||||
<div className="w-10 h-10 md:w-12 md:h-12 bg-black rounded-xl flex items-center justify-center group-hover:scale-105 transition-all duration-500 shadow-sm shrink-0 relative overflow-hidden">
|
||||
<Image
|
||||
src={IconWhite}
|
||||
alt="Marc Mintel Icon"
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-8 h-8 relative z-10"
|
||||
className="w-6 h-6 md:w-8 md:h-8 relative z-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-8">
|
||||
{[
|
||||
{ href: "/about", label: "Über mich" },
|
||||
{ href: "/websites", label: "Websites" },
|
||||
{ href: "/case-studies", label: "Case Studies", prefix: true },
|
||||
{ href: "/blog", label: "Blog", prefix: true },
|
||||
].map((link) => {
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
{navLinks.map((link) => {
|
||||
const active = link.prefix
|
||||
? isActive(link.href) || pathname?.startsWith(`${link.href}/`)
|
||||
: isActive(link.href);
|
||||
@@ -97,7 +120,196 @@ export const Header: React.FC = () => {
|
||||
Anfrage
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Toggle */}
|
||||
<button
|
||||
className="md:hidden relative z-[110] p-2 w-10 h-10 flex items-center justify-center rounded-xl bg-slate-900 text-white active:scale-90 transition-all duration-300 shadow-lg shadow-slate-200"
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
aria-label="Toggle Menu"
|
||||
>
|
||||
<div className="w-5 h-3.5 relative flex flex-col justify-between">
|
||||
<motion.span
|
||||
animate={
|
||||
isMobileMenuOpen ? { rotate: 45, y: 7 } : { rotate: 0, y: 0 }
|
||||
}
|
||||
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
|
||||
className="w-full h-0.5 bg-current rounded-full origin-center"
|
||||
/>
|
||||
<motion.span
|
||||
animate={
|
||||
isMobileMenuOpen ? { opacity: 0, x: -10 } : { opacity: 1, x: 0 }
|
||||
}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="w-full h-0.5 bg-current rounded-full"
|
||||
/>
|
||||
<motion.span
|
||||
animate={
|
||||
isMobileMenuOpen ? { rotate: -45, y: -7 } : { rotate: 0, y: 0 }
|
||||
}
|
||||
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
|
||||
className="w-full h-0.5 bg-current rounded-full origin-center"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation - Bottom-Anchored Control Center */}
|
||||
<AnimatePresence>
|
||||
{isMobileMenuOpen && (
|
||||
<React.Fragment key="mobile-control-center">
|
||||
{/* Dimmed Backdrop */}
|
||||
<motion.div
|
||||
key="cc-backdrop"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="fixed inset-0 z-[101] bg-black/30 backdrop-blur-sm md:hidden"
|
||||
/>
|
||||
|
||||
{/* Bottom Sheet */}
|
||||
<motion.div
|
||||
key="cc-sheet"
|
||||
initial={{ y: "100%" }}
|
||||
animate={{ y: 0 }}
|
||||
exit={{ y: "100%" }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 350,
|
||||
damping: 30,
|
||||
mass: 0.8,
|
||||
}}
|
||||
drag="y"
|
||||
dragConstraints={{ top: 0, bottom: 0 }}
|
||||
dragElastic={0.15}
|
||||
onDragEnd={(_, info) => {
|
||||
if (info.offset.y > 80 || info.velocity.y > 300) {
|
||||
setIsMobileMenuOpen(false);
|
||||
}
|
||||
}}
|
||||
className="fixed inset-x-0 bottom-0 z-[102] md:hidden bg-white rounded-t-[2rem] shadow-[0_-8px_40px_rgba(0,0,0,0.12)] flex flex-col max-h-[85vh] overflow-hidden"
|
||||
>
|
||||
{/* Grab Handle */}
|
||||
<div className="flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing">
|
||||
<div className="w-10 h-1 bg-slate-200 rounded-full" />
|
||||
</div>
|
||||
|
||||
{/* Status Bar */}
|
||||
<div className="px-6 py-3 flex justify-between items-center border-b border-slate-100/80">
|
||||
<div className="flex items-center gap-2 text-[9px] font-mono font-bold tracking-[0.15em] text-slate-400 uppercase">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse" />
|
||||
Online
|
||||
</div>
|
||||
<div className="text-[9px] font-mono font-bold tracking-widest text-slate-400 uppercase">
|
||||
{pathname === "/"
|
||||
? "HOME"
|
||||
: pathname.toUpperCase().replace(/^\//, "")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tiled Navigation Grid */}
|
||||
<div className="px-5 pt-5 pb-3 flex-1 overflow-y-auto">
|
||||
<div className="grid grid-cols-2 gap-2.5">
|
||||
{[
|
||||
{ href: "/about", label: "Über mich", sub: "Architect" },
|
||||
{ href: "/websites", label: "Websites", sub: "Systems" },
|
||||
{
|
||||
href: "/case-studies",
|
||||
label: "Cases",
|
||||
sub: "Solutions",
|
||||
prefix: true,
|
||||
},
|
||||
{
|
||||
href: "/blog",
|
||||
label: "Blog",
|
||||
sub: "Insights",
|
||||
prefix: true,
|
||||
},
|
||||
].map((item, i) => {
|
||||
const active = item.prefix
|
||||
? pathname?.startsWith(item.href)
|
||||
: pathname === item.href;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={item.href}
|
||||
initial={{ opacity: 0, scale: 0.85, y: 15 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
transition={{
|
||||
delay: 0.05 + i * 0.04,
|
||||
type: "spring",
|
||||
stiffness: 400,
|
||||
damping: 25,
|
||||
}}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Link
|
||||
href={item.href}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className={`relative flex flex-col justify-center p-6 h-[110px] rounded-2xl border transition-all duration-200 ${
|
||||
active
|
||||
? "bg-slate-50 border-slate-200 ring-1 ring-slate-200"
|
||||
: "bg-white border-slate-100 active:bg-slate-50"
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<span className="text-[15px] font-black tracking-tight text-slate-900 block leading-tight mb-1">
|
||||
{item.label}
|
||||
</span>
|
||||
<span className="text-[9px] font-mono font-bold text-slate-400 uppercase tracking-[0.2em]">
|
||||
{item.sub}
|
||||
</span>
|
||||
</div>
|
||||
{active && (
|
||||
<div className="absolute top-4 right-4 w-1.5 h-1.5 rounded-full bg-slate-900" />
|
||||
)}
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Primary CTA */}
|
||||
<div className="px-5 pb-5 pt-2">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.25, type: "spring", stiffness: 300 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
>
|
||||
<Link
|
||||
href="/contact"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="flex items-center justify-between w-full p-4 bg-slate-900 text-white rounded-2xl active:bg-slate-800 transition-colors"
|
||||
>
|
||||
<span className="text-[13px] font-bold uppercase tracking-[0.15em]">
|
||||
Projekt anfragen
|
||||
</span>
|
||||
<svg
|
||||
className="w-4 h-4 text-slate-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2.5"
|
||||
d="M14 5l7 7m0 0l-7 7m7-7H3"
|
||||
/>
|
||||
</svg>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Safe-Area Spacer (iOS home indicator) */}
|
||||
<div className="h-[env(safe-area-inset-bottom,0px)]" />
|
||||
</motion.div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,54 +1,69 @@
|
||||
import { BookOpen, Code2, Terminal, Wrench } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
import { BookOpen, Code2, Terminal, Wrench } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
export const Hero: React.FC = () => {
|
||||
return (
|
||||
<section className="relative bg-slate-900 text-white py-20 md:py-24 overflow-hidden">
|
||||
<section className="relative bg-slate-900 text-white py-16 md:py-24 overflow-hidden text-center">
|
||||
{/* Background pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute inset-0" style={{
|
||||
backgroundImage: 'radial-gradient(circle at 1px 1px, white 1px, transparent 0)',
|
||||
backgroundSize: '40px 40px'
|
||||
}} />
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle at 1px 1px, white 1px, transparent 0)",
|
||||
backgroundSize: "32px 32px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="container relative z-10">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Main heading */}
|
||||
<h1 className="text-4xl md:text-6xl font-bold mb-6 animate-slide-up">
|
||||
<h1 className="text-4xl md:text-6xl font-bold mb-4 md:mb-6 animate-slide-up leading-tight">
|
||||
Digital Problem Solver
|
||||
</h1>
|
||||
|
||||
<p className="text-xl md:text-2xl text-slate-300 mb-8 leading-relaxed animate-fade-in">
|
||||
I work on Digital problems and build tools, scripts, and systems to solve them.
|
||||
|
||||
<p className="text-lg md:text-2xl text-slate-300 mb-8 leading-relaxed animate-fade-in px-4 md:px-0">
|
||||
I work on Digital problems and build tools, scripts, and systems to
|
||||
solve them.
|
||||
</p>
|
||||
|
||||
|
||||
{/* Quick stats or focus areas */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 mb-12 animate-fade-in">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6 mb-12 animate-fade-in">
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-6 border border-white/10 hover:border-white/30 transition-all duration-300">
|
||||
<Code2 className="mx-auto mb-3 text-slate-400" size={28} />
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">Code</div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">
|
||||
Code
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-6 border border-white/10 hover:border-white/30 transition-all duration-300">
|
||||
<Wrench className="mx-auto mb-3 text-slate-400" size={28} />
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">Tools</div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">
|
||||
Tools
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-6 border border-white/10 hover:border-white/30 transition-all duration-300">
|
||||
<Terminal className="mx-auto mb-3 text-slate-400" size={28} />
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">Automation</div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">
|
||||
Automation
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-6 border border-white/10 hover:border-white/30 transition-all duration-300">
|
||||
<BookOpen className="mx-auto mb-3 text-slate-400" size={28} />
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">Learning</div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-slate-300">
|
||||
Learning
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Topics */}
|
||||
<div className="mt-8 text-sm text-slate-400 animate-fade-in">
|
||||
<span className="font-semibold text-slate-300">Topics:</span> Vibe coding with AI • Debugging • Mac tools • Automation • Small scripts • Learning notes • FOSS
|
||||
<span className="font-semibold text-slate-300">Topics:</span> Vibe
|
||||
coding with AI • Debugging • Mac tools • Automation • Small scripts
|
||||
• Learning notes • FOSS
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,17 +20,17 @@ export const HeroSection: React.FC = () => {
|
||||
const opacity = useTransform(scrollY, [0, 600], [1, 0]);
|
||||
|
||||
return (
|
||||
<section className="relative min-h-[100vh] flex items-center justify-center overflow-hidden bg-white">
|
||||
<section className="relative min-h-[85dvh] md:min-h-[100dvh] flex items-center justify-center overflow-hidden bg-white px-5 md:px-0">
|
||||
{/* 1. The Binary Architecture (Background) */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
<AbstractCircuit />
|
||||
</div>
|
||||
|
||||
{/* 2. Content Layer */}
|
||||
<div className="relative z-10 container mx-auto px-6 h-full flex flex-col justify-center items-center">
|
||||
<div className="relative z-10 container mx-auto h-full flex flex-col justify-center items-center">
|
||||
<motion.div
|
||||
style={{ y, opacity }}
|
||||
className="text-center relative max-w-[90vw]"
|
||||
className="text-center relative max-w-full md:max-w-[90vw]"
|
||||
>
|
||||
{/* Architectural Coordinate Labels */}
|
||||
<div className="absolute -left-20 top-0 hidden xl:flex flex-col gap-8 opacity-20 pointer-events-none">
|
||||
@@ -47,19 +47,19 @@ export const HeroSection: React.FC = () => {
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1, delay: 0.2 }}
|
||||
className="mb-10 inline-flex items-center gap-4 px-6 py-2 border border-slate-100 bg-white/40 backdrop-blur-sm rounded-full"
|
||||
className="mb-4 md:mb-10 inline-flex items-center gap-3 md:gap-4 px-4 md:px-6 py-2 border border-slate-100 bg-white/40 backdrop-blur-sm rounded-full"
|
||||
>
|
||||
<div className="flex gap-1">
|
||||
<div className="w-1 h-1 rounded-full bg-blue-500 animate-pulse" />
|
||||
<div className="w-1 h-1 rounded-full bg-blue-300 animate-pulse delay-100" />
|
||||
</div>
|
||||
<span className="text-[10px] font-mono font-bold tracking-[0.4em] text-slate-500 uppercase">
|
||||
<span className="text-[9px] md:text-[10px] font-mono font-bold tracking-[0.3em] md:tracking-[0.4em] text-slate-500 uppercase">
|
||||
Digital_Architect // v.2026
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Headline */}
|
||||
<h1 className="text-7xl md:text-[11rem] font-black tracking-tighter leading-[0.8] text-slate-900 mb-12 uppercase">
|
||||
<h1 className="text-3xl md:text-[11rem] font-black tracking-tighter leading-[0.9] md:leading-[0.8] text-slate-900 mb-6 md:mb-12 uppercase">
|
||||
<div className="block">
|
||||
<GlitchText delay={0.5} duration={1.2}>
|
||||
Websites
|
||||
@@ -80,20 +80,26 @@ export const HeroSection: React.FC = () => {
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.8 }}
|
||||
className="flex flex-col items-center gap-12"
|
||||
className="flex flex-col items-center gap-6 md:gap-12"
|
||||
>
|
||||
<p className="text-xl md:text-3xl text-slate-400 font-medium max-w-2xl leading-relaxed">
|
||||
Ein Entwickler. Ein Ansprechpartner. <br />
|
||||
<p className="text-base md:text-3xl text-slate-400 font-medium max-w-2xl leading-relaxed px-4">
|
||||
Ein Entwickler. Ein Ansprechpartner.{" "}
|
||||
<br className="hidden md:block" />
|
||||
<span className="text-slate-900 font-bold tracking-tight">
|
||||
Systematische Architekturen für das Web.
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-center gap-6">
|
||||
<Button href="/contact" size="large">
|
||||
<div className="flex flex-col md:flex-row items-center justify-center gap-4 md:gap-6 w-full px-5 md:px-0">
|
||||
<Button href="/contact" size="large" className="w-full md:w-auto">
|
||||
Projekt anfragen
|
||||
</Button>
|
||||
<Button href="/websites" variant="outline" size="large">
|
||||
<Button
|
||||
href="/websites"
|
||||
variant="outline"
|
||||
size="large"
|
||||
className="w-full md:w-auto"
|
||||
>
|
||||
Prozess ansehen
|
||||
</Button>
|
||||
</div>
|
||||
@@ -102,13 +108,13 @@ export const HeroSection: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* 3. Blueprint Frame (Decorative Borders) */}
|
||||
<div className="absolute inset-0 pointer-events-none border-[1px] border-slate-100 m-8 opacity-40" />
|
||||
<div className="absolute inset-0 pointer-events-none border-[1px] border-slate-100 m-4 md:m-8 opacity-40 md:opacity-40" />
|
||||
<div className="absolute top-8 left-8 p-4 hidden md:block opacity-20 transform -rotate-90 origin-top-left transition-opacity hover:opacity-100 group">
|
||||
<span className="text-[10px] font-mono tracking-widest text-slate-400">
|
||||
POS_TRANSMISSION_001
|
||||
</span>
|
||||
</div>
|
||||
<div className="absolute bottom-8 right-8 p-4 hidden md:block opacity-20 transition-opacity hover:opacity-100">
|
||||
<div className="absolute bottom-4 right-4 md:bottom-8 md:right-8 p-4 opacity-20 transition-opacity hover:opacity-100 scale-75 md:scale-100 origin-bottom-right">
|
||||
<span className="text-[10px] font-mono tracking-widest text-slate-400">
|
||||
EST_2026 // M-ARCH
|
||||
</span>
|
||||
@@ -116,10 +122,10 @@ export const HeroSection: React.FC = () => {
|
||||
|
||||
{/* 4. Scroll Indicator */}
|
||||
<motion.div
|
||||
className="absolute bottom-10 left-1/2 -translate-x-1/2 flex flex-col items-center gap-3 opacity-40"
|
||||
className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 flex flex-col items-center gap-3 opacity-40"
|
||||
style={{ opacity }}
|
||||
>
|
||||
<div className="w-[1px] h-12 bg-slate-200" />
|
||||
<div className="w-[1px] h-8 md:h-12 bg-slate-200" />
|
||||
<span className="text-[9px] font-mono uppercase tracking-[0.3em] text-slate-400">
|
||||
Scroll
|
||||
</span>
|
||||
|
||||
@@ -27,6 +27,8 @@ interface IframeSectionProps {
|
||||
dynamicGlow?: boolean;
|
||||
minHeight?: number;
|
||||
mobileWidth?: number;
|
||||
mobileHeight?: string;
|
||||
desktopHeight?: string;
|
||||
}
|
||||
|
||||
import PropTypes from "prop-types";
|
||||
@@ -129,6 +131,8 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
|
||||
dynamicGlow = true,
|
||||
minHeight = 400,
|
||||
mobileWidth = 390,
|
||||
mobileHeight,
|
||||
desktopHeight,
|
||||
}) => {
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const iframeRef = React.useRef<HTMLIFrameElement>(null);
|
||||
@@ -264,7 +268,20 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
|
||||
return match ? parseFloat(match[1]) : null;
|
||||
};
|
||||
|
||||
const baseNumericHeight = parseNumericHeight(height);
|
||||
const [isMobile, setIsMobile] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
||||
checkMobile();
|
||||
window.addEventListener("resize", checkMobile);
|
||||
return () => window.removeEventListener("resize", checkMobile);
|
||||
}, []);
|
||||
|
||||
const activeHeight = isMobile
|
||||
? mobileHeight || height
|
||||
: desktopHeight || height;
|
||||
|
||||
const baseNumericHeight = parseNumericHeight(activeHeight);
|
||||
const finalScaledHeight = clipHeight
|
||||
? clipHeight * scale
|
||||
: baseNumericHeight
|
||||
@@ -273,11 +290,11 @@ export const IframeSection: React.FC<IframeSectionProps> = ({
|
||||
|
||||
const chassisStyle = {
|
||||
height:
|
||||
height === "100%"
|
||||
activeHeight === "100%"
|
||||
? "100%"
|
||||
: finalScaledHeight
|
||||
? `${finalScaledHeight + headerHeightPx}px`
|
||||
: `calc(${height} + ${headerHeightPx}px)`,
|
||||
: `calc(${activeHeight} + ${headerHeightPx}px)`,
|
||||
minHeight: minHeight ? `${minHeight}px` : undefined,
|
||||
};
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ export const Card: React.FC<CardProps> = ({
|
||||
|
||||
const paddings = {
|
||||
none: "p-0",
|
||||
small: "p-6 md:p-8",
|
||||
normal: "p-8 md:p-10",
|
||||
large: "p-10 md:p-12",
|
||||
small: "p-4 md:p-8",
|
||||
normal: "p-5 md:p-10",
|
||||
large: "p-6 md:p-12",
|
||||
};
|
||||
|
||||
const [hexId, setHexId] = React.useState("0x0000");
|
||||
@@ -103,7 +103,13 @@ export const Container: React.FC<{
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("mx-auto px-6 w-full", variants[variant], className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"mx-auto px-5 md:px-6 w-full",
|
||||
variants[variant],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -29,18 +29,18 @@ export const MediumCard: React.FC<MediumCardProps> = ({ post }) => {
|
||||
variant="glass"
|
||||
padding="normal"
|
||||
techBorder={false}
|
||||
className="relative overflow-hidden transition-all duration-300 border-slate-100 hover:border-slate-300 bg-white/30 backdrop-blur-sm"
|
||||
className="relative overflow-hidden transition-all duration-300 border-slate-100 hover:border-slate-300 bg-white/30 backdrop-blur-sm p-5 md:p-6"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3 md:space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<time className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-400">
|
||||
<time className="text-[9px] md:text-[10px] font-bold uppercase tracking-[0.2em] text-slate-400">
|
||||
{formattedDate}
|
||||
</time>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-1.5 md:gap-2">
|
||||
{tags?.slice(0, 2).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-[9px] font-mono text-slate-300 uppercase"
|
||||
className="text-[8px] md:text-[9px] font-mono text-slate-300 uppercase"
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
@@ -48,16 +48,16 @@ export const MediumCard: React.FC<MediumCardProps> = ({ post }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-2xl font-bold text-slate-900 tracking-tight leading-tight group-hover:text-black transition-colors">
|
||||
<div className="space-y-1.5 md:space-y-2">
|
||||
<h3 className="text-xl md:text-2xl font-bold text-slate-900 tracking-tight leading-tight group-hover:text-black transition-colors">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-base text-slate-500 font-serif italic leading-relaxed line-clamp-2">
|
||||
<p className="text-sm md:text-base text-slate-500 font-serif italic leading-relaxed line-clamp-2">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="pt-2 flex items-center gap-2 text-[10px] font-bold uppercase tracking-widest text-slate-400 group-hover:text-slate-900 transition-all">
|
||||
<div className="pt-1 md:pt-2 flex items-center gap-2 text-[9px] md:text-[10px] font-bold uppercase tracking-widest text-slate-400 group-hover:text-slate-900 transition-all">
|
||||
<span>Beitrag öffnen</span>
|
||||
<ArrowRight className="w-3 h-3 translate-x-0 group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"narrow-container relative",
|
||||
"narrow-container relative overflow-hidden md:overflow-visible",
|
||||
isBlog
|
||||
? "pt-32 pb-12 md:pt-48 md:pb-16"
|
||||
: "pt-24 pb-16 md:pt-40 md:pb-24",
|
||||
@@ -35,8 +35,8 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
className={cn(
|
||||
"absolute font-bold text-slate-50 select-none -z-10 opacity-40 tracking-tighter leading-none",
|
||||
isBlog
|
||||
? "-left-8 top-12 text-[12rem] md:text-[16rem]"
|
||||
: "-left-24 -top-12 text-[20rem] md:text-[24rem]",
|
||||
? "-left-4 md:-left-8 top-12 text-[10rem] md:text-[16rem]"
|
||||
: "-left-12 md:-left-24 -top-8 md:-top-12 text-[15rem] md:text-[24rem]",
|
||||
)}
|
||||
>
|
||||
{backgroundSymbol}
|
||||
@@ -51,13 +51,13 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn("space-y-6 md:space-y-8 relative", isBlog && "max-w-7xl")}
|
||||
className={cn("space-y-4 md:space-y-8 relative", isBlog && "max-w-7xl")}
|
||||
>
|
||||
<Reveal>
|
||||
<h1
|
||||
className={cn(
|
||||
"font-bold text-slate-900 tracking-tighter leading-[1.1]",
|
||||
isBlog ? "text-4xl md:text-6xl" : "text-5xl md:text-7xl",
|
||||
isBlog ? "text-xl md:text-6xl" : "text-2xl md:text-7xl",
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
@@ -70,8 +70,8 @@ export const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
className={cn(
|
||||
"font-serif italic text-slate-400 leading-relaxed",
|
||||
isBlog
|
||||
? "text-lg md:text-xl max-w-xl"
|
||||
: "text-xl md:text-2xl max-w-2xl",
|
||||
? "text-sm md:text-xl max-w-xl"
|
||||
: "text-base md:text-2xl max-w-2xl",
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
|
||||
@@ -49,7 +49,7 @@ export const Section: React.FC<SectionProps> = ({
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"relative py-24 md:py-40 group overflow-hidden",
|
||||
"relative py-6 md:py-40 group overflow-hidden",
|
||||
bgClass,
|
||||
borderTopClass,
|
||||
borderBottomClass,
|
||||
@@ -81,41 +81,43 @@ export const Section: React.FC<SectionProps> = ({
|
||||
{effects}
|
||||
|
||||
<div className={cn("relative z-10", containerClass)}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 md:gap-24">
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 md:gap-24">
|
||||
{/* Sidebar: Number & Title (Only if provided) */}
|
||||
{(number || title || illustration) && (
|
||||
<div className="md:col-span-3">
|
||||
<div className="md:sticky md:top-40 space-y-8">
|
||||
{number && (
|
||||
<Reveal delay={delay}>
|
||||
<span className="block text-7xl md:text-8xl font-bold text-slate-100 leading-none select-none tracking-tighter relative">
|
||||
{number}
|
||||
{/* Subtle binary overlay on number */}
|
||||
<span
|
||||
className="absolute top-1 left-0 text-[8px] font-mono text-slate-200/30 tracking-wider leading-none select-none pointer-events-none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{parseInt(number || "0")
|
||||
.toString(2)
|
||||
.padStart(8, "0")}
|
||||
<div className="md:sticky md:top-40 flex flex-col gap-4 md:gap-8">
|
||||
<div className="flex items-end md:flex-col md:items-start gap-6 md:gap-8">
|
||||
{number && (
|
||||
<Reveal delay={delay}>
|
||||
<span className="block text-4xl md:text-8xl font-bold text-slate-100 leading-none select-none tracking-tighter relative">
|
||||
{number}
|
||||
{/* Subtle binary overlay on number */}
|
||||
<span
|
||||
className="absolute -top-1 md:top-1 left-0 text-[7px] md:text-[8px] font-mono text-slate-200/30 tracking-wider leading-none select-none pointer-events-none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{parseInt(number || "0")
|
||||
.toString(2)
|
||||
.padStart(8, "0")}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</Reveal>
|
||||
)}
|
||||
{title && (
|
||||
<Reveal delay={delay + 0.1}>
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Animated dot indicator */}
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-slate-300 animate-circuit-pulse shrink-0" />
|
||||
<Label className="text-slate-900 text-[10px] tracking-[0.4em]">
|
||||
{title}
|
||||
</Label>
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
</Reveal>
|
||||
)}
|
||||
{title && (
|
||||
<Reveal delay={delay + 0.1}>
|
||||
<div className="flex items-center gap-3 md:gap-4 mb-2 md:mb-0">
|
||||
{/* Animated dot indicator */}
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-slate-300 animate-circuit-pulse shrink-0" />
|
||||
<Label className="text-slate-900 text-[9px] md:text-[10px] tracking-[0.3em] md:tracking-[0.4em]">
|
||||
{title}
|
||||
</Label>
|
||||
</div>
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
{illustration && (
|
||||
<Reveal delay={delay + 0.2}>
|
||||
<div className="pt-8 opacity-100 group-hover:scale-105 transition-transform duration-1000 ease-out origin-left grayscale hover:grayscale-0">
|
||||
<div className="pt-2 md:pt-8 opacity-100 group-hover:scale-105 transition-transform duration-1000 ease-out origin-left grayscale hover:grayscale-0">
|
||||
{illustration}
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import * as React from "react";
|
||||
|
||||
interface TypographyProps {
|
||||
children: React.ReactNode;
|
||||
@@ -6,49 +6,77 @@ interface TypographyProps {
|
||||
}
|
||||
|
||||
export const H1: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h1 className={`text-5xl md:text-7xl font-bold text-slate-900 tracking-tighter leading-[1.1] ${className}`}>
|
||||
<h1
|
||||
className={`text-4xl md:text-7xl font-bold text-slate-900 tracking-tighter leading-[1.1] ${className}`}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
|
||||
export const H2: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h2 className={`text-3xl md:text-5xl font-bold text-slate-900 tracking-tighter leading-tight ${className}`}>
|
||||
<h2
|
||||
className={`text-xl md:text-5xl font-bold text-slate-900 tracking-tighter leading-tight ${className}`}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
|
||||
export const H3: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h3 className={`text-2xl md:text-4xl font-bold text-slate-900 tracking-tight leading-tight ${className}`}>
|
||||
<h3
|
||||
className={`text-lg md:text-4xl font-bold text-slate-900 tracking-tight leading-tight ${className}`}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
|
||||
export const H4: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<h4 className={`text-xl md:text-2xl font-bold text-slate-900 tracking-tight ${className}`}>
|
||||
<h4
|
||||
className={`text-base md:text-2xl font-bold text-slate-900 tracking-tight ${className}`}
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
|
||||
export const LeadText: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<p className={`text-lg md:text-xl font-serif italic text-slate-500 leading-relaxed ${className}`}>
|
||||
export const LeadText: React.FC<TypographyProps> = ({
|
||||
children,
|
||||
className = "",
|
||||
}) => (
|
||||
<p
|
||||
className={`text-sm md:text-xl font-serif italic text-slate-500 leading-relaxed ${className}`}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
|
||||
export const BodyText: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<p className={`text-base text-slate-500 leading-relaxed ${className}`}>
|
||||
export const BodyText: React.FC<TypographyProps> = ({
|
||||
children,
|
||||
className = "",
|
||||
}) => (
|
||||
<p
|
||||
className={`text-sm md:text-base text-slate-500 leading-relaxed ${className}`}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
|
||||
export const Label: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<span className={`text-[10px] font-bold uppercase tracking-[0.3em] text-slate-400 block ${className}`}>
|
||||
export const Label: React.FC<TypographyProps> = ({
|
||||
children,
|
||||
className = "",
|
||||
}) => (
|
||||
<span
|
||||
className={`text-[10px] font-bold uppercase tracking-[0.3em] text-slate-400 block ${className}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
export const MonoLabel: React.FC<TypographyProps> = ({ children, className = "" }) => (
|
||||
<span className={`text-[10px] font-mono uppercase tracking-[0.2em] text-slate-400 block ${className}`}>
|
||||
export const MonoLabel: React.FC<TypographyProps> = ({
|
||||
children,
|
||||
className = "",
|
||||
}) => (
|
||||
<span
|
||||
className={`text-[10px] font-mono uppercase tracking-[0.2em] text-slate-400 block ${className}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -24,30 +24,30 @@ export const BlogCommandBar: React.FC<BlogCommandBarProps> = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-full max-w-2xl mx-auto flex flex-col items-center space-y-6 px-4 md:px-0",
|
||||
"w-full max-w-2xl mx-auto flex flex-col items-center space-y-4 md:space-y-6 px-0",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{/* Command Input Area */}
|
||||
<div className="relative group w-full">
|
||||
<div className="relative group w-full px-0 sm:px-4 md:px-0">
|
||||
{/* Glow Effect */}
|
||||
<div className="absolute -inset-0.5 bg-gradient-to-r from-slate-200 to-slate-100 rounded-2xl blur opacity-30 group-hover:opacity-100 transition duration-500" />
|
||||
<div className="absolute -inset-0.5 bg-gradient-to-r from-slate-200 to-slate-100 rounded-2xl blur opacity-20 group-hover:opacity-100 transition duration-500" />
|
||||
|
||||
<div className="relative flex items-center bg-white rounded-2xl border border-slate-200 p-2 shadow-sm transition-all focus-within:ring-2 focus-within:ring-slate-100 focus-within:border-slate-300">
|
||||
<div className="pl-4 text-slate-400">
|
||||
<Search className="w-5 h-5" />
|
||||
<div className="relative flex items-center bg-white rounded-2xl border border-slate-200 p-1.5 md:p-2 shadow-sm transition-all focus-within:ring-2 focus-within:ring-slate-100 focus-within:border-slate-300">
|
||||
<div className="pl-3 md:pl-4 text-slate-400">
|
||||
<Search className="w-4 h-4 md:w-5 md:h-5" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
placeholder="Search posts..."
|
||||
className="w-full bg-transparent px-4 py-3 text-lg text-slate-900 placeholder:text-slate-300 outline-none font-bold"
|
||||
className="w-full bg-transparent px-3 md:px-4 py-2 md:py-3 text-base md:text-lg text-slate-900 placeholder:text-slate-300 outline-none font-bold"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => onSearchChange("")}
|
||||
className="mr-2 px-3 py-1.5 bg-slate-100 hover:bg-slate-200 rounded-lg text-[10px] font-bold uppercase tracking-wider text-slate-500 hover:text-slate-900 transition-colors"
|
||||
className="mr-2 px-2.5 py-1 md:px-3 md:py-1.5 bg-slate-100 hover:bg-slate-200 rounded-lg text-[9px] md:text-[10px] font-bold uppercase tracking-wider text-slate-500 hover:text-slate-900 transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -56,7 +56,7 @@ export const BlogCommandBar: React.FC<BlogCommandBarProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Tag Command Row */}
|
||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||
<div className="flex flex-wrap items-center justify-center gap-1.5 md:gap-2 px-4 md:px-0">
|
||||
{tags.map((tag) => {
|
||||
const isActive = activeTags.includes(tag);
|
||||
return (
|
||||
@@ -64,7 +64,7 @@ export const BlogCommandBar: React.FC<BlogCommandBarProps> = ({
|
||||
key={tag}
|
||||
onClick={() => onTagToggle(tag)}
|
||||
className={cn(
|
||||
"px-3 py-1.5 rounded-lg text-[10px] font-mono uppercase tracking-wider border transition-all duration-200 select-none",
|
||||
"px-2.5 py-1 md:px-3 md:py-1.5 rounded-lg text-[9px] md:text-[10px] font-mono uppercase tracking-wider border transition-all duration-200 select-none",
|
||||
isActive
|
||||
? "bg-slate-900 text-white border-slate-900 shadow-md transform scale-105"
|
||||
: "bg-white text-slate-500 border-slate-200 hover:border-slate-400 hover:text-slate-900 hover:bg-slate-50",
|
||||
|
||||
Reference in New Issue
Block a user