697 lines
34 KiB
TypeScript
697 lines
34 KiB
TypeScript
import { Card } from "@/components/ui/card";
|
||
import { Montserrat, Playfair_Display } from "next/font/google";
|
||
import { motion, AnimatePresence } from "framer-motion";
|
||
import Link from "next/link";
|
||
import Image from "next/image";
|
||
import React from "react";
|
||
import Head from 'next/head';
|
||
|
||
const playfair = Playfair_Display({
|
||
subsets: ["latin"],
|
||
display: "swap",
|
||
variable: "--font-playfair",
|
||
});
|
||
|
||
const montserrat = Montserrat({
|
||
subsets: ["latin"],
|
||
variable: "--font-montserrat",
|
||
});
|
||
|
||
const MotionCard = motion(Card);
|
||
|
||
const focusStyles = `
|
||
focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black
|
||
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-black
|
||
`;
|
||
|
||
export default function Home() {
|
||
const [isScrolled, setIsScrolled] = React.useState(false);
|
||
const [isMenuVisible, setIsMenuVisible] = React.useState(false);
|
||
const lastScrollY = React.useRef(0);
|
||
|
||
React.useEffect(() => {
|
||
const updateScroll = () => {
|
||
const currentScrollY = window.scrollY;
|
||
setIsScrolled(currentScrollY > 100);
|
||
|
||
if (currentScrollY === 0) {
|
||
setIsMenuVisible(false);
|
||
return;
|
||
}
|
||
|
||
if (currentScrollY < lastScrollY.current && currentScrollY > 100) {
|
||
setIsMenuVisible(true);
|
||
}
|
||
else if (currentScrollY > lastScrollY.current) {
|
||
setIsMenuVisible(false);
|
||
}
|
||
|
||
lastScrollY.current = currentScrollY;
|
||
};
|
||
|
||
window.addEventListener('scroll', updateScroll);
|
||
updateScroll();
|
||
|
||
return () => window.removeEventListener('scroll', updateScroll);
|
||
}, []);
|
||
|
||
const scrollToTop = () => {
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
};
|
||
|
||
const scrollToSection = (sectionId: string) => {
|
||
const element = document.getElementById(sectionId);
|
||
if (element) {
|
||
element.scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
};
|
||
|
||
return (
|
||
<AnimatePresence>
|
||
<>
|
||
<Head>
|
||
<title>Johannes Behrens - International Model</title>
|
||
<meta
|
||
name="description"
|
||
content="Johannes Behrens is an international model based in Hamburg, Germany. Specializing in commercial, fitness, and editorial modeling with a dynamic and charismatic presence."
|
||
/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
</Head>
|
||
<motion.main
|
||
className={`${playfair.variable} ${montserrat.variable} min-h-screen font-sans bg-black`}
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<a
|
||
href="#main-content"
|
||
className={`
|
||
sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4
|
||
bg-white text-black px-4 py-2 z-50 ${focusStyles}
|
||
`}
|
||
>
|
||
Skip to main content
|
||
</a>
|
||
|
||
<header>
|
||
<motion.nav
|
||
className="fixed top-0 left-0 right-0 z-50 bg-black/90 backdrop-blur-md border-b border-white/10"
|
||
initial={{ y: -100 }}
|
||
animate={{ y: isMenuVisible ? 0 : -100 }}
|
||
transition={{ duration: 0.3, ease: [0.32, 0.72, 0, 1] }}
|
||
role="navigation"
|
||
aria-label="Main navigation"
|
||
>
|
||
<div className="container mx-auto px-4 sm:px-6 md:px-8 h-16 flex items-center justify-between">
|
||
<motion.button
|
||
className="flex items-center h-full"
|
||
onClick={scrollToTop}
|
||
whileHover={{ scale: 1.02 }}
|
||
transition={{ duration: 0.2 }}
|
||
aria-label="Back to top"
|
||
>
|
||
<div className="flex flex-col leading-[0.8]">
|
||
<span className="font-playfair text-xl text-white font-black tracking-tight">Johannes</span>
|
||
<span className="font-playfair text-xl text-white font-black tracking-tight pl-4">Behrens</span>
|
||
</div>
|
||
</motion.button>
|
||
<div className="flex items-center gap-8" role="menubar">
|
||
<motion.button
|
||
className={`font-montserrat text-sm text-white/60 hover:text-white transition-colors ${focusStyles}`}
|
||
onClick={() => scrollToSection('about')}
|
||
whileHover={{ scale: 1.02 }}
|
||
transition={{ duration: 0.2 }}
|
||
aria-label="Go to about section"
|
||
role="menuitem"
|
||
tabIndex={0}
|
||
>
|
||
About
|
||
</motion.button>
|
||
<motion.button
|
||
className="font-montserrat text-sm text-white/60 hover:text-white transition-colors"
|
||
onClick={() => scrollToSection('portfolio')}
|
||
whileHover={{ scale: 1.02 }}
|
||
transition={{ duration: 0.2 }}
|
||
aria-label="Go to portfolio section"
|
||
role="menuitem"
|
||
>
|
||
Portfolio
|
||
</motion.button>
|
||
<motion.button
|
||
className="font-montserrat text-sm text-white/60 hover:text-white transition-colors"
|
||
onClick={() => scrollToSection('contact')}
|
||
whileHover={{ scale: 1.02 }}
|
||
transition={{ duration: 0.2 }}
|
||
aria-label="Go to contact section"
|
||
role="menuitem"
|
||
>
|
||
Contact
|
||
</motion.button>
|
||
</div>
|
||
</div>
|
||
</motion.nav>
|
||
</header>
|
||
|
||
<div id="main-content" tabIndex={-1}>
|
||
<section
|
||
className="relative h-[100svh] bg-black overflow-hidden"
|
||
aria-label="Hero section"
|
||
>
|
||
<motion.div
|
||
className="absolute inset-0 w-full md:w-[80%] left-0 md:left-[-15%]"
|
||
initial={{ opacity: 0, scale: 1.1 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
transition={{ duration: 1.4, ease: [0.32, 0.72, 0, 1], delay: 0.3 }}
|
||
aria-hidden="true"
|
||
>
|
||
<div className="relative w-full h-full md:hidden">
|
||
<Image
|
||
src="/images/hero-background.jpg"
|
||
alt="Johannes Behrens, professional model, standing confidently in a fashion pose"
|
||
fill
|
||
className="object-cover object-[center_15%]"
|
||
priority
|
||
quality={85}
|
||
sizes="100vw"
|
||
loading="eager"
|
||
fetchPriority="high"
|
||
/>
|
||
<motion.div
|
||
className="absolute inset-0 bg-gradient-to-b from-black/80 via-black/30 to-black/80"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 1, ease: [0.32, 0.72, 0, 1], delay: 0.6 }}
|
||
/>
|
||
</div>
|
||
|
||
<div className="relative w-full h-full hidden md:block">
|
||
<Image
|
||
src="/images/hero-background.jpg"
|
||
alt="Johannes Behrens, professional model, standing confidently in a fashion pose"
|
||
fill
|
||
className="object-cover"
|
||
priority
|
||
quality={85}
|
||
sizes="(max-width: 768px) 100vw, 80vw"
|
||
loading="eager"
|
||
fetchPriority="high"
|
||
/>
|
||
<motion.div
|
||
className="absolute inset-0 bg-gradient-to-r from-black via-black/20 to-transparent"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 1, ease: [0.32, 0.72, 0, 1], delay: 0.6 }}
|
||
/>
|
||
</div>
|
||
</motion.div>
|
||
|
||
<div className="absolute inset-0 flex items-center md:justify-start">
|
||
<div className="w-full md:w-[80%] px-6 sm:px-8 md:pl-[25%] md:pr-0 lg:pl-[50%]">
|
||
<div className="relative">
|
||
<motion.div
|
||
className="relative z-20 text-center md:text-left"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1], delay: 0.7 }}
|
||
>
|
||
<div className="mix-blend-difference">
|
||
<motion.div
|
||
className="overflow-hidden mb-4"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1], delay: 0.9 }}
|
||
>
|
||
<div className="font-montserrat tracking-[0.3em] md:tracking-[0.5em] text-white/90 text-xs sm:text-sm uppercase font-medium">
|
||
International Model
|
||
</div>
|
||
</motion.div>
|
||
|
||
<motion.h1
|
||
className="font-playfair text-[4.5rem] sm:text-[5rem] md:text-[7rem] lg:text-[8rem] font-black text-white leading-[0.8] tracking-tight"
|
||
initial={{ opacity: 0, y: 30 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1], delay: 1 }}
|
||
>
|
||
Johannes
|
||
</motion.h1>
|
||
<span className="font-playfair text-[3.5rem] sm:text-[4rem] md:text-[5rem] lg:text-[6rem] font-black text-white md:leading-[0.7] tracking-tight md:pl-14">
|
||
Behrens
|
||
</span>
|
||
</div>
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
className="mt-8 md:mt-12 mix-blend-difference"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1], delay: 1.2 }}
|
||
>
|
||
<div className="grid grid-cols-3 gap-3 md:gap-4 text-white/80 text-center md:text-left">
|
||
<div className="space-y-2">
|
||
<h2 className="font-montserrat text-[10px] md:text-xs tracking-[0.2em] uppercase font-semibold">Based in</h2>
|
||
<p className="font-montserrat text-lg md:text-xl font-medium">Hamburg</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<h2 className="font-montserrat text-[10px] md:text-xs tracking-[0.2em] uppercase font-semibold">Height</h2>
|
||
<p className="font-montserrat text-lg md:text-xl font-medium">183 cm</p>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<h2 className="font-montserrat text-[10px] md:text-xs tracking-[0.2em] uppercase font-semibold">Bookings</h2>
|
||
<p className="font-montserrat text-lg md:text-xl font-medium">Worldwide</p>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
className="mt-8 text-center md:hidden"
|
||
initial={{ opacity: 0, y: 15 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1], delay: 1.4 }}
|
||
>
|
||
<p className="font-montserrat text-[10px] tracking-[0.3em] text-white/60 uppercase font-medium">
|
||
Commercial • Fitness
|
||
</p>
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<motion.div
|
||
className="fixed bottom-6 md:bottom-8 right-6 md:right-8 z-50"
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1], delay: 1.6 }}
|
||
>
|
||
<motion.button
|
||
onClick={scrollToTop}
|
||
className={`group relative flex items-center gap-2 h-10 ${focusStyles}`}
|
||
whileHover={{ scale: 1.05 }}
|
||
transition={{ duration: 0.3, ease: [0.32, 0.72, 0, 1] }}
|
||
aria-label="Scroll to top"
|
||
tabIndex={0}
|
||
>
|
||
<motion.div
|
||
className="absolute right-0 bg-black/50 backdrop-blur-md rounded-full"
|
||
initial={{ opacity: 0, scale: 0.8 }}
|
||
animate={{
|
||
opacity: isScrolled ? 1 : 0,
|
||
scale: isScrolled ? 1 : 0.8,
|
||
width: isScrolled ? 40 : 120,
|
||
}}
|
||
transition={{ duration: 0.3, ease: [0.32, 0.72, 0, 1] }}
|
||
style={{
|
||
height: 40,
|
||
}}
|
||
/>
|
||
|
||
<motion.div
|
||
className="flex items-center gap-2 px-3"
|
||
animate={{
|
||
opacity: isScrolled ? 0 : 1,
|
||
pointerEvents: isScrolled ? "none" : "auto",
|
||
}}
|
||
transition={{ duration: 0.2, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<div className="font-montserrat tracking-[0.3em] text-white/60 text-[10px] uppercase font-medium mix-blend-difference">
|
||
Scroll
|
||
</div>
|
||
<div className="w-[40px] md:w-[50px] h-px bg-white/60 origin-right mix-blend-difference" />
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
className="absolute right-0 flex items-center justify-center w-10 h-10"
|
||
initial={{ opacity: 0 }}
|
||
animate={{
|
||
opacity: isScrolled ? 1 : 0,
|
||
pointerEvents: isScrolled ? "auto" : "none",
|
||
}}
|
||
transition={{ duration: 0.2, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<svg
|
||
width="14"
|
||
height="14"
|
||
viewBox="0 0 14 14"
|
||
fill="none"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
className="text-white/60"
|
||
>
|
||
<path
|
||
d="M7 1L7 13M7 1L13 7M7 1L1 7"
|
||
stroke="currentColor"
|
||
strokeWidth="1.5"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
/>
|
||
</svg>
|
||
</motion.div>
|
||
</motion.button>
|
||
</motion.div>
|
||
</section>
|
||
|
||
<section id="about" className="py-16 sm:py-20 md:py-24 lg:py-32 bg-black border-t border-white/10">
|
||
<div className="container mx-auto px-4 sm:px-6 md:px-8">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-16 lg:gap-24 max-w-7xl mx-auto">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 30 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true, margin: "-10%" }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1] }}
|
||
className="space-y-8"
|
||
>
|
||
<div>
|
||
<motion.div
|
||
className="font-montserrat tracking-[0.3em] text-white/60 text-xs uppercase mb-4 sm:mb-6 font-semibold"
|
||
initial={{ opacity: 0 }}
|
||
whileInView={{ opacity: 1 }}
|
||
viewport={{ once: true }}
|
||
>
|
||
About
|
||
</motion.div>
|
||
<motion.p
|
||
className="font-montserrat text-white/80 text-base sm:text-lg leading-relaxed font-medium"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1], delay: 0.2 }}
|
||
>
|
||
With a versatile athletic background and a strong stage presence as a drummer, I bring dynamic and charismatic energy to every project. Reliable, adaptable, and passionate about delivering outstanding results.
|
||
</motion.p>
|
||
</div>
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 30 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true, margin: "-10%" }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1] }}
|
||
className="space-y-10 sm:space-y-12"
|
||
>
|
||
<div>
|
||
<motion.div
|
||
className="font-montserrat tracking-[0.3em] text-white/60 text-xs uppercase mb-6 font-semibold"
|
||
initial={{ opacity: 0 }}
|
||
whileInView={{ opacity: 1 }}
|
||
viewport={{ once: true }}
|
||
>
|
||
Measurements
|
||
</motion.div>
|
||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-x-6 sm:gap-x-8 gap-y-6">
|
||
<div>
|
||
<h2 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Height</h2>
|
||
<p className="font-montserrat text-lg sm:text-xl text-white font-medium">183 cm</p>
|
||
</div>
|
||
<div>
|
||
<h2 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Chest</h2>
|
||
<p className="font-montserrat text-lg sm:text-xl text-white font-medium">95 cm</p>
|
||
</div>
|
||
<div>
|
||
<h2 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Waist</h2>
|
||
<p className="font-montserrat text-lg sm:text-xl text-white font-medium">73 cm</p>
|
||
</div>
|
||
<div>
|
||
<h2 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Hip</h2>
|
||
<p className="font-montserrat text-lg sm:text-xl text-white font-medium">88 cm</p>
|
||
</div>
|
||
<div>
|
||
<h2 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Suit Size</h2>
|
||
<p className="font-montserrat text-lg sm:text-xl text-white font-medium">48</p>
|
||
</div>
|
||
<div>
|
||
<h2 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Shoe Size</h2>
|
||
<p className="font-montserrat text-lg sm:text-xl text-white font-medium">45</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-3 font-semibold">Features</h3>
|
||
<div className="grid grid-cols-2 gap-x-8 gap-y-2">
|
||
<p className="font-montserrat text-white/80 text-sm sm:text-base font-medium">Brown Eyes</p>
|
||
<p className="font-montserrat text-white/80 text-sm sm:text-base font-medium">Brown Hair</p>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="relative py-16 sm:py-20 md:py-24 lg:py-32 bg-white">
|
||
<div className="absolute inset-0" style={{
|
||
backgroundImage: `linear-gradient(to right, rgba(0, 0, 0, 0.03) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 0, 0, 0.03) 1px, transparent 1px)`,
|
||
backgroundSize: '48px 48px'
|
||
}} />
|
||
<div className="container mx-auto px-4 sm:px-6 md:px-8 relative">
|
||
<motion.div
|
||
className="max-w-screen-lg mx-auto mb-16 sm:mb-20 md:mb-24 lg:mb-32 text-center space-y-4 sm:space-y-6"
|
||
>
|
||
<motion.div
|
||
className="font-montserrat tracking-[0.3em] sm:tracking-[0.5em] text-black/70 text-xs sm:text-sm uppercase font-medium"
|
||
>
|
||
Categories
|
||
</motion.div>
|
||
<motion.h2
|
||
className="font-playfair text-2xl sm:text-3xl md:text-4xl lg:text-5xl text-black font-bold"
|
||
>
|
||
Modeling Services
|
||
</motion.h2>
|
||
</motion.div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12 max-w-5xl mx-auto">
|
||
<motion.div
|
||
className="text-center space-y-4"
|
||
>
|
||
<div className="w-16 h-16 mx-auto mb-6">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" className="w-full h-full text-black/70">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M3 3h18v18H3zM9 3v18M15 3v18M3 9h18M3 15h18" />
|
||
</svg>
|
||
</div>
|
||
<h3 className="font-montserrat text-black uppercase tracking-[0.2em] text-base font-semibold">Commercial</h3>
|
||
<p className="font-montserrat text-black/70 text-sm leading-relaxed max-w-xs mx-auto font-medium">
|
||
Product campaigns, lifestyle shoots, and brand representation with a focus on authentic engagement and natural presence.
|
||
</p>
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
className="text-center space-y-4"
|
||
>
|
||
<div className="w-16 h-16 mx-auto mb-6">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" className="w-full h-full text-black/70">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M6.5 6.5h11v11h-11z" />
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M3 9V6.5h3.5M21 9V6.5h-3.5M3 15v2.5h3.5M21 15v2.5h-3.5" />
|
||
</svg>
|
||
</div>
|
||
<h3 className="font-montserrat text-black uppercase tracking-[0.2em] text-base font-semibold">Fitness</h3>
|
||
<p className="font-montserrat text-black/70 text-sm leading-relaxed max-w-xs mx-auto font-medium">
|
||
Athletic wear, sports campaigns, and dynamic movement shoots showcasing strength and functional fitness.
|
||
</p>
|
||
</motion.div>
|
||
|
||
<motion.div
|
||
className="text-center space-y-4"
|
||
>
|
||
<div className="w-16 h-16 mx-auto mb-6">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" className="w-full h-full text-black/70">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="M4 21V3h16v18M8 3v18M16 3v18" />
|
||
</svg>
|
||
</div>
|
||
<h3 className="font-montserrat text-black uppercase tracking-[0.2em] text-base font-semibold">Editorial</h3>
|
||
<p className="font-montserrat text-black/70 text-sm leading-relaxed max-w-xs mx-auto font-medium">
|
||
Fashion editorials, magazine features, and artistic collaborations with a focus on storytelling and style.
|
||
</p>
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="portfolio" className="relative py-16 sm:py-20 md:py-24 lg:py-32 bg-white">
|
||
<div className="absolute inset-0" style={{
|
||
backgroundImage: `linear-gradient(to right, rgba(0, 0, 0, 0.03) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 0, 0, 0.03) 1px, transparent 1px)`,
|
||
backgroundSize: '48px 48px'
|
||
}} />
|
||
<div className="container mx-auto px-4 sm:px-6 md:px-8 relative">
|
||
<motion.div
|
||
className="max-w-screen-lg mx-auto mb-16 sm:mb-20 md:mb-24 lg:mb-32 text-center space-y-4 sm:space-y-6"
|
||
>
|
||
<motion.div
|
||
className="font-montserrat tracking-[0.3em] sm:tracking-[0.5em] text-black/70 text-xs sm:text-sm uppercase font-medium"
|
||
>
|
||
Portfolio
|
||
</motion.div>
|
||
<motion.h2
|
||
className="font-playfair text-2xl sm:text-3xl md:text-4xl lg:text-5xl text-black font-bold"
|
||
>
|
||
Selected Work
|
||
</motion.h2>
|
||
</motion.div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 md:gap-8 lg:gap-12">
|
||
{[1, 2, 3, 4, 5, 6].map((index) => (
|
||
<MotionCard
|
||
key={index}
|
||
className="overflow-hidden group border-0 bg-transparent relative"
|
||
initial={{ opacity: 0, y: 30 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true, margin: "-100px" }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1], delay: index * 0.05 }}
|
||
>
|
||
<motion.div
|
||
className="relative w-full aspect-[3/4] overflow-hidden"
|
||
whileHover={{ scale: 1.02 }}
|
||
transition={{ duration: 0.4, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<Image
|
||
src={`/images/portfolio-${index}.jpg`}
|
||
alt={`Portfolio image ${index} - Professional modeling work by Johannes Behrens`}
|
||
fill
|
||
className="object-cover grayscale group-hover:grayscale-0 transition-all duration-200"
|
||
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||
quality={85}
|
||
priority={index <= 2}
|
||
loading={index <= 2 ? "eager" : "lazy"}
|
||
/>
|
||
<motion.div
|
||
className="absolute inset-0 bg-gradient-to-b from-black/0 to-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
|
||
/>
|
||
</motion.div>
|
||
</MotionCard>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="contact" className="py-16 sm:py-20 md:py-24 lg:py-32 bg-black">
|
||
<div className="container mx-auto px-4 sm:px-6 md:px-8">
|
||
<motion.div
|
||
className="max-w-screen-lg mx-auto"
|
||
initial={{ opacity: 0, y: 30 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<div className="text-center mb-12 sm:mb-16 md:mb-20 lg:mb-24">
|
||
<motion.div
|
||
className="font-montserrat tracking-[0.3em] sm:tracking-[0.4em] md:tracking-[0.5em] text-white/60 text-xs sm:text-sm uppercase mb-4 font-semibold"
|
||
initial={{ opacity: 0 }}
|
||
whileInView={{ opacity: 1 }}
|
||
viewport={{ once: true }}
|
||
>
|
||
Contact
|
||
</motion.div>
|
||
</div>
|
||
|
||
<motion.div
|
||
className="grid grid-cols-1 md:grid-cols-2 gap-12 sm:gap-16 md:gap-24 max-w-5xl mx-auto text-center"
|
||
initial={{ opacity: 0, y: 30 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1], delay: 0.2 }}
|
||
>
|
||
<div className="space-y-12">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<h3 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Email</h3>
|
||
<motion.p
|
||
className="font-montserrat text-lg sm:text-xl md:text-2xl lg:text-3xl break-words font-medium"
|
||
whileHover={{ opacity: 0.8 }}
|
||
transition={{ duration: 0.4, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<Link
|
||
href="mailto:contact@johannesbehrens.com"
|
||
className={`hover:opacity-80 transition-opacity ${focusStyles}`}
|
||
tabIndex={0}
|
||
>
|
||
contact@johannesbehrens.com
|
||
</Link>
|
||
</motion.p>
|
||
</motion.div>
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1], delay: 0.1 }}
|
||
>
|
||
<h3 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Based in</h3>
|
||
<p className="font-montserrat text-lg sm:text-xl md:text-2xl lg:text-3xl font-medium">Hamburg, Germany</p>
|
||
</motion.div>
|
||
</div>
|
||
<div className="space-y-12">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1], delay: 0.2 }}
|
||
>
|
||
<h3 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Agency</h3>
|
||
<p className="font-montserrat text-lg sm:text-xl md:text-2xl lg:text-3xl font-medium">Model Management</p>
|
||
</motion.div>
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.9, ease: [0.32, 0.72, 0, 1], delay: 0.3 }}
|
||
>
|
||
<h3 className="font-montserrat text-white/60 uppercase tracking-[0.2em] text-[10px] sm:text-xs mb-2 font-semibold">Social</h3>
|
||
<div className="space-y-3">
|
||
<motion.p
|
||
className="font-montserrat text-lg sm:text-xl md:text-2xl lg:text-3xl font-medium"
|
||
whileHover={{ opacity: 0.8 }}
|
||
transition={{ duration: 0.4, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<Link
|
||
href="https://www.instagram.com/johannes.brh/"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className={`hover:opacity-80 transition-opacity duration-200 ${focusStyles}`}
|
||
tabIndex={0}
|
||
>
|
||
Instagram
|
||
</Link>
|
||
</motion.p>
|
||
<motion.p
|
||
className="font-montserrat text-lg sm:text-xl md:text-2xl lg:text-3xl font-medium"
|
||
whileHover={{ opacity: 0.8 }}
|
||
transition={{ duration: 0.4, ease: [0.32, 0.72, 0, 1] }}
|
||
>
|
||
<Link href="#" className="hover:opacity-80 transition-opacity duration-200">
|
||
LinkedIn
|
||
</Link>
|
||
</motion.p>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
</motion.div>
|
||
</motion.div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<footer
|
||
className="py-8 bg-black border-t border-white/10"
|
||
role="contentinfo"
|
||
>
|
||
<div className="container mx-auto px-4 text-center">
|
||
<p className="font-montserrat text-sm text-white/90">
|
||
Made with ❤️ by{" "}
|
||
<Link
|
||
href="https://jleibl.net"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className={`text-white hover:text-white/80 transition-colors duration-200 ${focusStyles}`}
|
||
aria-label="Visit Jan-Marlon Leibl's website (opens in new tab)"
|
||
>
|
||
Jan-Marlon Leibl
|
||
</Link>
|
||
</p>
|
||
</div>
|
||
</footer>
|
||
</motion.main>
|
||
</>
|
||
</AnimatePresence>
|
||
);
|
||
}
|