diff --git a/bun.lock b/bun.lock index 04f302d..4de0407 100644 --- a/bun.lock +++ b/bun.lock @@ -4,9 +4,14 @@ "": { "name": "jleibl.net", "dependencies": { + "@fontsource/dm-sans": "^5.1.1", + "@fontsource/instrument-sans": "^5.1.1", + "@studio-freight/lenis": "^1.0.42", + "framer-motion": "^12.4.7", "next": "15.1.7", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-intersection-observer": "^9.15.1", }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -42,6 +47,10 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.7", "", { "dependencies": { "@eslint/core": "^0.12.0", "levn": "^0.4.1" } }, "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g=="], + "@fontsource/dm-sans": ["@fontsource/dm-sans@5.1.1", "", {}, "sha512-xApcrecLZkQMM6bl7U+Eokg8Y4LeYMiR+cQUTUNx4Adun5ZpiIlPB9+JEXg/k1gFea/72nmn30AekCBA+HhUCA=="], + + "@fontsource/instrument-sans": ["@fontsource/instrument-sans@5.1.1", "", {}, "sha512-hBus0cnHsLFc+AQ6tsHqK2Iqice5QHTEfLiUQ19dDzpeEBxIyOgQ7vkbO1E9ZNpWtX7KoRzz2Qe2Y/qfxEbSKA=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], @@ -134,6 +143,8 @@ "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.10.5", "", {}, "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A=="], + "@studio-freight/lenis": ["@studio-freight/lenis@1.0.42", "", {}, "sha512-HJAGf2DeM+BTvKzHv752z6Z7zy6bA643nZM7W88Ft9tnw2GsJSp6iJ+3cekjyMIWH+cloL2U9X82dKXgdU8kPg=="], + "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -366,6 +377,8 @@ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "framer-motion": ["framer-motion@12.4.7", "", { "dependencies": { "motion-dom": "^12.4.5", "motion-utils": "^12.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-VhrcbtcAMXfxlrjeHPpWVu2+mkcoR31e02aNSR7OUS/hZAciKa8q6o3YN2mA1h+jjscRsSyKvX6E1CiY/7OLMw=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -530,6 +543,10 @@ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "motion-dom": ["motion-dom@12.4.5", "", { "dependencies": { "motion-utils": "^12.0.0" } }, "sha512-Q2xmhuyYug1CGTo0jdsL05EQ4RhIYXlggFS/yPhQQRNzbrhjKQ1tbjThx5Plv68aX31LsUQRq4uIkuDxdO5vRQ=="], + + "motion-utils": ["motion-utils@12.0.0", "", {}, "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], @@ -616,6 +633,8 @@ "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], + "react-intersection-observer": ["react-intersection-observer@9.15.1", "", { "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["react-dom"] }, "sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA=="], + "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], diff --git a/package.json b/package.json index 707412a..c2ac8b5 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,14 @@ "lint": "next lint" }, "dependencies": { + "@fontsource/dm-sans": "^5.1.1", + "@fontsource/instrument-sans": "^5.1.1", + "@studio-freight/lenis": "^1.0.42", + "framer-motion": "^12.4.7", + "next": "15.1.7", "react": "^19.0.0", "react-dom": "^19.0.0", - "next": "15.1.7" + "react-intersection-observer": "^9.15.1" }, "devDependencies": { "typescript": "^5", diff --git a/public/profile-image.jpg b/public/profile-image.jpg new file mode 100644 index 0000000..47d8c84 Binary files /dev/null and b/public/profile-image.jpg differ diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 2aff580..506b048 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,114 +1,508 @@ +import { useEffect, useRef, useMemo, createContext, useContext, useState } from 'react'; +import { motion } from 'framer-motion'; +import { useInView } from 'react-intersection-observer'; +import Lenis from '@studio-freight/lenis'; +import Head from 'next/head'; import Image from "next/image"; -import { Geist, Geist_Mono } from "next/font/google"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], +import '@fontsource/dm-sans/400.css'; +import '@fontsource/dm-sans/500.css'; +import '@fontsource/dm-sans/600.css'; +import '@fontsource/dm-sans/700.css'; +import '@fontsource/instrument-sans/400.css'; +import '@fontsource/instrument-sans/500.css'; + +type Theme = 'black' | 'red' | 'gold'; +type ThemeContextType = { + theme: Theme; + setTheme: (theme: Theme) => void; +}; + +const ThemeContext = createContext({ + theme: 'black', + setTheme: () => {}, }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +const ThemeProvider = ({ children }: { children: React.ReactNode }) => { + const [theme, setTheme] = useState('black'); + return ( + +
+ {children} +
+
+ ); +}; + +const useSmoothScroll = () => { + useEffect(() => { + const lenis = new Lenis({ + duration: 1.2, + easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), + orientation: 'vertical', + smoothWheel: true, + touchMultiplier: 2, + }); + + function raf(time: number) { + lenis.raf(time); + requestAnimationFrame(raf); + } + + requestAnimationFrame(raf); + return () => { lenis.destroy(); }; + }, []); +}; + +const FadeIn = ({ children, className = "", delay = 0 }: { children: React.ReactNode; className?: string; delay?: number }) => { + const [ref, inView] = useInView({ + triggerOnce: true, + threshold: 0.1, + rootMargin: "0px 0px -10% 0px" + }); + + return ( + + {children} + + ); +}; + +const shine = ` + @keyframes shine { + 0% { + background-position: 200% 0; + } + 45% { + background-position: 0% 0; + } + 55% { + background-position: -50% 0; + } + 100% { + background-position: -200% 0; + } + } +`; + +const GermanyText = () => { + const { theme, setTheme } = useContext(ThemeContext); + + return ( + + setTheme('black')} + className={theme === 'black' ? 'ring-1 ring-white/20' : ''} + /> + setTheme('red')} + className={theme === 'red' ? 'ring-1 ring-white/20' : ''} + /> + setTheme('gold')} + className={theme === 'gold' ? 'ring-1 ring-white/20' : ''} + /> + + ); +}; 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. -
+ useSmoothScroll(); -
- - Vercel logomark - Deploy now + return ( + + +
-
+ + +
+
+ +
+

About

+
+
+
+ +
+
+ +
+ Jan-Marlon Leibl +
+
+ +
+

Jan-Marlon Leibl

+

Fullstack Developer

+
+
+
+ + +
+
+

+ Hello! I'm Jan-Marlon, but please call me Jan. I started my journey in programming at the age of 11 with C#, fascinated by a desktop application my friend created. +

+

+ Today, I specialize in PHP and TypeScript development, constantly pushing the boundaries of what's possible on the web. My journey has led me from creating simple applications to developing complex systems used by thousands. +

+
+ +
+
+
+

Technologies

+
+
+
    + {['PHP', 'JavaScript', 'MySQL', 'React'].map((tech) => ( +
  • + + {tech} +
  • + ))} +
+
+ +
+
+

Interests

+
+
+
    + {[ + 'Web Development', + 'System Architecture', + 'UI/UX Design', + 'Performance Optimization' + ].map((interest) => ( +
  • + + {interest} +
  • + ))} +
+
+
+ +
+ - File icon - Learn + className="flex-1 text-center px-8 py-4 border border-white/10 rounded-full hover:bg-white/[0.05] transition-colors text-base tracking-wide font-medium" + > + View GitHub - Window icon - Examples - - - Globe icon - Go to nextjs.org → - + className="flex-1 text-center px-8 py-4 border border-white/10 rounded-full hover:bg-white/[0.05] transition-colors text-base tracking-wide font-medium" + > + Connect on LinkedIn + +
+
+
+
+
+
+ +
+
+
+ +
+ Portfolio +
+

Selected Work

+
+
+
+
+ +
+ {[ + { + title: 'ventry.host v2', + year: '2025', + description: 'Free file hosting revamped with a modern design and improved user experience.', + tags: ['Next.js', 'Tailwind CSS', 'TypeScript'] + }, + { + title: 'ventry.host', + year: '2023', + description: 'A free file hosting solution with thousands of daily visitors.', + tags: ['PHP', 'JavaScript', 'MySQL'] + }, + { + title: 'ShareUpload', + year: '2022', + description: 'High-performance file sharing platform with unlimited storage.', + tags: ['PHP', 'MySQL', 'Performance'] + }, + { + title: 'RestoreM', + year: '2023', + description: 'Discord server backup and restoration service.', + tags: ['PHP', 'MySQL', 'Discord API'] + } + ].map((project, index) => ( + +
+
+
+ {project.year} +
+
+
+ +
+
+

+ {project.title} +

+

+ {project.description} +

+
+ +
+
+

Technologies

+
+ {project.tags.map((tag, tagIndex) => ( + + {tag}{tagIndex !== project.tags.length - 1 && ( + + )} + + ))} +
+
+
+
+ +
+
+
+ ))} +
+
+
+ + + ); }