diff --git a/bun.lockb b/bun.lockb index 8d58651..760f3a8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components.json b/components.json new file mode 100644 index 0000000..90e9bf9 --- /dev/null +++ b/components.json @@ -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" +} \ No newline at end of file diff --git a/package.json b/package.json index 127dd22..e505cc1 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,16 @@ "lint": "next lint" }, "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-dom": "^19.0.0", - "next": "15.1.4" + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "typescript": "^5", diff --git a/public/images/hero-background.jpg b/public/images/hero-background.jpg new file mode 100644 index 0000000..b9a92f8 Binary files /dev/null and b/public/images/hero-background.jpg differ diff --git a/public/images/portfolio-0.jpg b/public/images/portfolio-0.jpg new file mode 100644 index 0000000..fcaa72d Binary files /dev/null and b/public/images/portfolio-0.jpg differ diff --git a/public/images/portfolio-1.jpg b/public/images/portfolio-1.jpg new file mode 100644 index 0000000..6e9ff9e Binary files /dev/null and b/public/images/portfolio-1.jpg differ diff --git a/public/images/portfolio-2.jpg b/public/images/portfolio-2.jpg new file mode 100644 index 0000000..957e5b8 Binary files /dev/null and b/public/images/portfolio-2.jpg differ diff --git a/public/images/portfolio-3.jpg b/public/images/portfolio-3.jpg new file mode 100644 index 0000000..0128cb3 Binary files /dev/null and b/public/images/portfolio-3.jpg differ diff --git a/public/images/portfolio-4.jpg b/public/images/portfolio-4.jpg new file mode 100644 index 0000000..8caf236 Binary files /dev/null and b/public/images/portfolio-4.jpg differ diff --git a/public/images/portfolio-5.jpg b/public/images/portfolio-5.jpg new file mode 100644 index 0000000..1ca6a65 Binary files /dev/null and b/public/images/portfolio-5.jpg differ diff --git a/public/images/portfolio-6.jpg b/public/images/portfolio-6.jpg new file mode 100644 index 0000000..f32e509 Binary files /dev/null and b/public/images/portfolio-6.jpg differ diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..c4abbf3 --- /dev/null +++ b/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..f8ed53c --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,76 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..d084cca --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 628a733..f5fd8b2 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -3,7 +3,9 @@ import { Html, Head, Main, NextScript } from "next/document"; export default function Document() { return ( - + + +
diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 2aff580..d296af2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -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 { Geist, Geist_Mono } from "next/font/google"; +import React from "react"; +import Head from 'next/head'; -const geistSans = Geist({ - variable: "--font-geist-sans", +const playfair = Playfair_Display({ subsets: ["latin"], + display: "swap", + variable: "--font-playfair", }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +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() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/pages/index.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
+ const [isScrolled, setIsScrolled] = React.useState(false); + const [isMenuVisible, setIsMenuVisible] = React.useState(false); + const lastScrollY = React.useRef(0); -
- { + const updateScroll = () => { + const currentScrollY = window.scrollY; + setIsScrolled(currentScrollY > 100); + + // Always hide menu when at the very top + if (currentScrollY === 0) { + setIsMenuVisible(false); + return; + } + + // Show menu when scrolling up and past initial threshold + 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 ( + + <> + + Johannes Behrens - International Model + + + + + {/* Skip Link */} + - Vercel logomark - Deploy now + Skip to main content - + +
+ +
+ Johannes + Behrens +
+
+
+ scrollToSection('about')} + whileHover={{ scale: 1.02 }} + transition={{ duration: 0.2 }} + aria-label="Go to about section" + role="menuitem" + tabIndex={0} + > + About + + scrollToSection('portfolio')} + whileHover={{ scale: 1.02 }} + transition={{ duration: 0.2 }} + aria-label="Go to portfolio section" + role="menuitem" + > + Portfolio + + scrollToSection('contact')} + whileHover={{ scale: 1.02 }} + transition={{ duration: 0.2 }} + aria-label="Go to contact section" + role="menuitem" + > + Contact + +
+
+
+ + + {/* Main Content */} +
+ {/* Hero Section */} +
+ {/* Background Image */} + + + {/* Content Container */} +
+
+
+ {/* Main Title */} + +
+ +
+ International Model +
+
+ + + Johannes + + + Behrens + +
+
+ + {/* Bottom Content */} + +
+
+

Based in

+

Hamburg

+
+
+

Height

+

183 cm

+
+
+

Bookings

+

Worldwide

+
+
+
+ + {/* Mobile Categories */} + +

+ Commercial • Fitness +

+
+
+
+
+ + {/* Scroll Indicator/To Top Button */} + + + {/* Background */} + + + {/* Scroll Text and Line */} + +
+ Scroll +
+
+ + + {/* Arrow Up */} + + + + + + + +
+ + {/* Details Section */} +
+
+
+ {/* Left Column - About */} + +
+ + About + + + 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. + +
+
+ + {/* Right Column - Measurements */} + +
+ + Measurements + +
+
+

Height

+

183 cm

+
+
+

Chest

+

95 cm

+
+
+

Waist

+

73 cm

+
+
+

Hip

+

88 cm

+
+
+

Suit Size

+

48

+
+
+

Shoe Size

+

45

+
+
+
+ +
+

Features

+
+

Brown Eyes

+

Brown Hair

+
+
+
+
+
+
+ + {/* Categories Section */} +
+
+
+ + + Categories + + + Modeling Services + + + +
+ {/* Commercial */} + +
+ + + +
+

Commercial

+

+ Product campaigns, lifestyle shoots, and brand representation with a focus on authentic engagement and natural presence. +

+
+ + {/* Fitness */} + +
+ + + + +
+

Fitness

+

+ Athletic wear, sports campaigns, and dynamic movement shoots showcasing strength and functional fitness. +

+
+ + {/* Editorial */} + +
+ + + +
+

Editorial

+

+ Fashion editorials, magazine features, and artistic collaborations with a focus on storytelling and style. +

+
+
+
+
+ + {/* Portfolio Section */} +
+
+
+ + + Portfolio + + + Selected Work + + + +
+ {[1, 2, 3, 4, 5, 6].map((index) => ( + + + {`Portfolio + + + + ))} +
+
+
+ + {/* Contact Section */} +
+
+ +
+ + Contact + +
+ + +
+ +

Email

+ + + contact@johannesbehrens.com + + +
+ +

Based in

+

Hamburg, Germany

+
+
+
+ +

Agency

+

Model Management

+
+ +

Social

+
+ + + Instagram + + + + + LinkedIn + + +
+
+
+
+
+
+
+
+ + {/* Footer */} +
-
- -
+
+

+ Made with ❤️ by{" "} + + Jan-Marlon Leibl + +

+
+ + + + ); } diff --git a/src/styles/globals.css b/src/styles/globals.css index 6b717ad..64dc68a 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -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 components; @tailwind utilities; -:root { - --background: #ffffff; - --foreground: #171717; -} +@layer base { + html { + scroll-behavior: smooth; + } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; + body { + @apply bg-black text-white; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } } -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; +@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; } diff --git a/tailwind.config.ts b/tailwind.config.ts index 109807b..3061f76 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,18 +1,67 @@ import type { Config } from "tailwindcss"; +import animate from "tailwindcss-animate"; export default { - content: [ + darkMode: ["class"], + content: [ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { - extend: { - colors: { - background: "var(--background)", - foreground: "var(--foreground)", - }, - }, + extend: { + colors: { + background: 'hsl(var(--background))', + 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))' + }, + 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: [], + plugins: [animate], } satisfies Config;