Initial Commit
21
components.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/styles/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
@ -9,9 +9,16 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"framer-motion": "^11.17.0",
|
||||||
|
"lucide-react": "^0.471.0",
|
||||||
|
"next": "15.1.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"next": "15.1.4"
|
"tailwind-merge": "^2.6.0",
|
||||||
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
BIN
public/images/hero-background.jpg
Normal file
After Width: | Height: | Size: 3.9 MiB |
BIN
public/images/portfolio-0.jpg
Normal file
After Width: | Height: | Size: 19 MiB |
BIN
public/images/portfolio-1.jpg
Normal file
After Width: | Height: | Size: 21 MiB |
BIN
public/images/portfolio-2.jpg
Normal file
After Width: | Height: | Size: 17 MiB |
BIN
public/images/portfolio-3.jpg
Normal file
After Width: | Height: | Size: 16 MiB |
BIN
public/images/portfolio-4.jpg
Normal file
After Width: | Height: | Size: 4.3 MiB |
BIN
public/images/portfolio-5.jpg
Normal file
After Width: | Height: | Size: 7.3 MiB |
BIN
public/images/portfolio-6.jpg
Normal file
After Width: | Height: | Size: 3.3 MiB |
5
src/components/ui/aspect-ratio.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
||||||
|
|
||||||
|
const AspectRatio = AspectRatioPrimitive.Root
|
||||||
|
|
||||||
|
export { AspectRatio }
|
76
src/components/ui/card.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"bg-card text-card-foreground shadow",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
6
src/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { type ClassValue, clsx } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
@ -3,7 +3,9 @@ import { Html, Head, Main, NextScript } from "next/document";
|
|||||||
export default function Document() {
|
export default function Document() {
|
||||||
return (
|
return (
|
||||||
<Html lang="en">
|
<Html lang="en">
|
||||||
<Head />
|
<Head>
|
||||||
|
<meta name="color-scheme" content="dark" />
|
||||||
|
</Head>
|
||||||
<body className="antialiased">
|
<body className="antialiased">
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
|
@ -1,114 +1,724 @@
|
|||||||
|
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 Image from "next/image";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import React from "react";
|
||||||
|
import Head from 'next/head';
|
||||||
|
|
||||||
const geistSans = Geist({
|
const playfair = Playfair_Display({
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
display: "swap",
|
||||||
|
variable: "--font-playfair",
|
||||||
});
|
});
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
const montserrat = Montserrat({
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
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() {
|
export default function Home() {
|
||||||
return (
|
const [isScrolled, setIsScrolled] = React.useState(false);
|
||||||
<div
|
const [isMenuVisible, setIsMenuVisible] = React.useState(false);
|
||||||
className={`${geistSans.variable} ${geistMono.variable} grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]`}
|
const lastScrollY = React.useRef(0);
|
||||||
>
|
|
||||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/next.svg"
|
|
||||||
alt="Next.js logo"
|
|
||||||
width={180}
|
|
||||||
height={38}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
|
||||||
<li className="mb-2">
|
|
||||||
Get started by editing{" "}
|
|
||||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
|
||||||
src/pages/index.tsx
|
|
||||||
</code>
|
|
||||||
.
|
|
||||||
</li>
|
|
||||||
<li>Save and see your changes instantly.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
React.useEffect(() => {
|
||||||
<a
|
const updateScroll = () => {
|
||||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
const currentScrollY = window.scrollY;
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
|
setIsScrolled(currentScrollY > 100);
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
// Always hide menu when at the very top
|
||||||
>
|
if (currentScrollY === 0) {
|
||||||
<Image
|
setIsMenuVisible(false);
|
||||||
className="dark:invert"
|
return;
|
||||||
src="/vercel.svg"
|
}
|
||||||
alt="Vercel logomark"
|
|
||||||
width={20}
|
// Show menu when scrolling up and past initial threshold
|
||||||
height={20}
|
if (currentScrollY < lastScrollY.current && currentScrollY > 100) {
|
||||||
|
setIsMenuVisible(true);
|
||||||
|
}
|
||||||
|
// Hide menu when scrolling down
|
||||||
|
else if (currentScrollY > lastScrollY.current) {
|
||||||
|
setIsMenuVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScrollY.current = currentScrollY;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', updateScroll);
|
||||||
|
updateScroll(); // Initial check
|
||||||
|
|
||||||
|
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."
|
||||||
/>
|
/>
|
||||||
Deploy now
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</a>
|
</Head>
|
||||||
<a
|
<motion.main
|
||||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
className={`${playfair.variable} ${montserrat.variable} min-h-screen font-sans bg-black`}
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
|
initial={{ opacity: 0 }}
|
||||||
target="_blank"
|
animate={{ opacity: 1 }}
|
||||||
rel="noopener noreferrer"
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}
|
||||||
>
|
>
|
||||||
Read our docs
|
{/* Skip Link */}
|
||||||
|
<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>
|
</a>
|
||||||
|
|
||||||
|
{/* Menu Bar */}
|
||||||
|
<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>
|
</div>
|
||||||
</main>
|
</motion.button>
|
||||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
<div className="flex items-center gap-8" role="menubar">
|
||||||
<a
|
<motion.button
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
className={`font-montserrat text-sm text-white/60 hover:text-white transition-colors ${focusStyles}`}
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
|
onClick={() => scrollToSection('about')}
|
||||||
target="_blank"
|
whileHover={{ scale: 1.02 }}
|
||||||
rel="noopener noreferrer"
|
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>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div id="main-content" tabIndex={-1}>
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section
|
||||||
|
className="relative h-[100svh] bg-black overflow-hidden"
|
||||||
|
aria-label="Hero section"
|
||||||
|
>
|
||||||
|
{/* Background Image */}
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
{/* Mobile Image */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Desktop Image */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Content Container */}
|
||||||
|
<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">
|
||||||
|
{/* Main Title */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Bottom Content */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Mobile Categories */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Scroll Indicator/To Top Button */}
|
||||||
|
<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}
|
||||||
|
>
|
||||||
|
{/* Background */}
|
||||||
|
<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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Scroll Text and Line */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Arrow Up */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Details 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">
|
||||||
|
{/* Left Column - About */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Right Column - Measurements */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Categories 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">
|
||||||
|
{/* Commercial */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Fitness */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Editorial */}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Portfolio 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
|
<Image
|
||||||
aria-hidden
|
src={`/images/portfolio-${index}.jpg`}
|
||||||
src="/file.svg"
|
alt={`Portfolio image ${index} - Professional modeling work by Johannes Behrens`}
|
||||||
alt="File icon"
|
fill
|
||||||
width={16}
|
className="object-cover grayscale group-hover:grayscale-0 transition-all duration-200"
|
||||||
height={16}
|
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||||
|
quality={85}
|
||||||
|
priority={index <= 2}
|
||||||
|
loading={index <= 2 ? "eager" : "lazy"}
|
||||||
/>
|
/>
|
||||||
Learn
|
<motion.div
|
||||||
</a>
|
className="absolute inset-0 bg-gradient-to-b from-black/0 to-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
|
||||||
<a
|
/>
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
</motion.div>
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
|
</MotionCard>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Contact 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"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
className={`hover:opacity-80 transition-opacity duration-200 ${focusStyles}`}
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<Image
|
Instagram
|
||||||
aria-hidden
|
</Link>
|
||||||
src="/window.svg"
|
</motion.p>
|
||||||
alt="Window icon"
|
<motion.p
|
||||||
width={16}
|
className="font-montserrat text-lg sm:text-xl md:text-2xl lg:text-3xl font-medium"
|
||||||
height={16}
|
whileHover={{ opacity: 0.8 }}
|
||||||
/>
|
transition={{ duration: 0.4, ease: [0.32, 0.72, 0, 1] }}
|
||||||
Examples
|
>
|
||||||
</a>
|
<Link href="#" className="hover:opacity-80 transition-opacity duration-200">
|
||||||
<a
|
LinkedIn
|
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
</Link>
|
||||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=default-template-tw&utm_campaign=create-next-app"
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<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"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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)"
|
||||||
>
|
>
|
||||||
<Image
|
Jan-Marlon Leibl
|
||||||
aria-hidden
|
</Link>
|
||||||
src="/globe.svg"
|
</p>
|
||||||
alt="Globe icon"
|
</div>
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Go to nextjs.org →
|
|
||||||
</a>
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</motion.main>
|
||||||
|
</>
|
||||||
|
</AnimatePresence>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,48 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
@layer base {
|
||||||
--background: #ffffff;
|
html {
|
||||||
--foreground: #171717;
|
scroll-behavior: smooth;
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: var(--foreground);
|
@apply bg-black text-white;
|
||||||
background: var(--background);
|
text-rendering: optimizeLegibility;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.vertical-text {
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
text-orientation: mixed;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 0%;
|
||||||
|
--foreground: 0 0% 100%;
|
||||||
|
--card: 0 0% 0%;
|
||||||
|
--card-foreground: 0 0% 100%;
|
||||||
|
--popover: 0 0% 0%;
|
||||||
|
--popover-foreground: 0 0% 100%;
|
||||||
|
--primary: 0 0% 100%;
|
||||||
|
--primary-foreground: 0 0% 0%;
|
||||||
|
--secondary: 0 0% 5%;
|
||||||
|
--secondary-foreground: 0 0% 100%;
|
||||||
|
--muted: 0 0% 10%;
|
||||||
|
--muted-foreground: 0 0% 70%;
|
||||||
|
--accent: 0 0% 100%;
|
||||||
|
--accent-foreground: 0 0% 0%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
--border: 0 0% 20%;
|
||||||
|
--input: 0 0% 20%;
|
||||||
|
--ring: 0 0% 100%;
|
||||||
|
--radius: 0;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
|
import animate from "tailwindcss-animate";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
darkMode: ["class"],
|
||||||
content: [
|
content: [
|
||||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
@ -9,10 +11,57 @@ export default {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
background: "var(--background)",
|
background: 'hsl(var(--background))',
|
||||||
foreground: "var(--foreground)",
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
card: {
|
||||||
|
DEFAULT: 'hsl(var(--card))',
|
||||||
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
},
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
},
|
},
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
},
|
},
|
||||||
plugins: [],
|
secondary: {
|
||||||
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
|
},
|
||||||
|
border: 'hsl(var(--border))',
|
||||||
|
input: 'hsl(var(--input))',
|
||||||
|
ring: 'hsl(var(--ring))',
|
||||||
|
chart: {
|
||||||
|
'1': 'hsl(var(--chart-1))',
|
||||||
|
'2': 'hsl(var(--chart-2))',
|
||||||
|
'3': 'hsl(var(--chart-3))',
|
||||||
|
'4': 'hsl(var(--chart-4))',
|
||||||
|
'5': 'hsl(var(--chart-5))'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
prompt:['Prompt', 'sans-serif'],
|
||||||
|
playfair: ['Playfair Display', 'serif'],
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: 'var(--radius)',
|
||||||
|
md: 'calc(var(--radius) - 2px)',
|
||||||
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [animate],
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|