1
0
forked from jleibl/jleibl.net

refactor: update dependencies and enhance animations

This commit is contained in:
2025-02-25 09:44:15 +01:00
parent cd1281e7cf
commit 502199af23
3 changed files with 505 additions and 97 deletions

View File

@ -163,15 +163,15 @@
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.25.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/type-utils": "8.25.0", "@typescript-eslint/utils": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.25.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/types": "8.25.0", "@typescript-eslint/typescript-estree": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0" } }, "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.25.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.25.0", "@typescript-eslint/utils": "8.25.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.25.0", "", {}, "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw=="],
"@typescript-eslint/types": ["@typescript-eslint/types@6.21.0", "", {}, "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.25.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/types": "8.25.0", "@typescript-eslint/typescript-estree": "8.25.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA=="],
@ -201,6 +201,8 @@
"array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
"array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
"array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ=="],
@ -289,6 +291,8 @@
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
@ -403,6 +407,8 @@
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
@ -597,6 +603,8 @@
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@ -689,6 +697,8 @@
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"stable-hash": ["stable-hash@0.0.4", "", {}, "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g=="],
@ -793,10 +803,28 @@
"@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0" } }, "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg=="],
"@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="],
"@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.25.0", "", {}, "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q=="],
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
"@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.25.0", "", {}, "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw=="],
"@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q=="],
"@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.25.0", "", {}, "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw=="],
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@ -805,6 +833,8 @@
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-react/doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@ -817,6 +847,8 @@
"sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@ -827,20 +859,44 @@
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@typescript-eslint/parser/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.25.0", "", {}, "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
}
}

View File

@ -13,20 +13,20 @@
"@fontsource/instrument-sans": "^5.1.1",
"@studio-freight/lenis": "^1.0.42",
"framer-motion": "^12.4.7",
"next": "14.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"next": "15.1.7",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-intersection-observer": "^9.15.1"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"eslint": "^9",
"eslint-config-next": "15.1.7",
"@eslint/eslintrc": "^3"
}
}

View File

@ -1,9 +1,10 @@
import { useEffect, createContext, useContext, useState } from 'react';
import { motion } from 'framer-motion';
import { motion, AnimatePresence } 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 React from 'react';
import '@fontsource/dm-sans/400.css';
import '@fontsource/dm-sans/500.css';
@ -67,7 +68,12 @@ const FadeIn = ({ children, className = "", delay = 0 }: { children: React.React
ref={ref}
initial={{ opacity: 0, y: 20 }}
animate={inView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ duration: 0.8, delay, ease: [0.22, 1, 0.36, 1] }}
transition={{
type: "spring",
damping: 20,
stiffness: 100,
delay,
}}
className={className}
>
{children}
@ -93,36 +99,225 @@ const GermanyText = () => {
opacity: 0.6,
cursor: "pointer"
}}
whileHover={{ opacity: 0.8 }}
whileHover={{
opacity: 0.8,
transition: { duration: 0.2 }
}}
>
<motion.button
aria-label="Switch to dark theme"
aria-pressed={theme === 'black'}
style={{ flex: 1, backgroundColor: "rgba(0, 0, 0)", borderLeft: "2px solid rgba(255, 255, 255, 0.1)", borderTop: "2px solid rgba(255, 255, 255, 0.1)", borderBottom: "2px solid rgba(255, 255, 255, 0.1)" }}
whileHover={{ scale: 1.1 }}
style={{ flex: 1, backgroundColor: "rgba(0, 0, 0, 0.5)", borderLeft: "2px solid rgba(255, 255, 255, 0.1)", borderTop: "2px solid rgba(255, 255, 255, 0.1)", borderBottom: "2px solid rgba(255, 255, 255, 0.1)" }}
onClick={() => setTheme('black')}
className={theme === 'black' ? 'ring-1 ring-white/20' : ''}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
/>
<motion.button
aria-label="Switch to red theme"
aria-pressed={theme === 'red'}
style={{ flex: 1, backgroundColor: "rgba(255, 0, 0)" }}
whileHover={{ scale: 1.1 }}
onClick={() => setTheme('red')}
className={theme === 'red' ? 'ring-1 ring-white/20' : ''}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
/>
<motion.button
aria-label="Switch to gold theme"
aria-pressed={theme === 'gold'}
style={{ flex: 1, backgroundColor: "rgba(255, 204, 0)" }}
whileHover={{ scale: 1.1 }}
onClick={() => setTheme('gold')}
className={theme === 'gold' ? 'ring-1 ring-white/20' : ''}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
/>
</motion.div>
);
};
const MobileMenu = () => {
const [isOpen, setIsOpen] = useState(false);
const menuRef = React.useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen]);
useEffect(() => {
const handleEscKey = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
setIsOpen(false);
}
};
document.addEventListener('keydown', handleEscKey);
return () => document.removeEventListener('keydown', handleEscKey);
}, [isOpen]);
return (
<>
<motion.button
onClick={() => setIsOpen(true)}
className="p-2 theme-accent rounded-lg theme-border"
aria-label="Open menu"
aria-expanded={isOpen}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
<svg className="w-5 h-5 theme-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</motion.button>
<AnimatePresence mode="wait">
{isOpen && (
<>
{/* Overlay - increased opacity */}
<motion.div
className="fixed inset-0 bg-black z-[90]"
initial={{ opacity: 0 }}
animate={{ opacity: 0.95 }}
exit={{ opacity: 0 }}
transition={{
type: "spring",
stiffness: 300,
damping: 30
}}
onClick={() => setIsOpen(false)}
aria-hidden="true"
/>
{/* Menu container - full height */}
<motion.div
className="fixed top-0 bottom-0 right-0 w-full sm:w-80 z-[100] h-[100dvh]"
ref={menuRef}
initial={{ x: "100%" }}
animate={{ x: 0 }}
exit={{ x: "100%" }}
transition={{
type: "spring",
damping: 30,
stiffness: 300,
mass: 1
}}
>
{/* Glass background and content wrapper with explicit backdrop filter */}
<div className="nav-glass w-full h-full flex flex-col shadow-2xl"
style={{
backdropFilter: 'blur(16px)',
WebkitBackdropFilter: 'blur(16px)'
}}>
{/* Close button */}
<motion.button
onClick={() => setIsOpen(false)}
className="absolute top-4 right-4 p-2 theme-accent rounded-lg theme-border z-[110]"
aria-label="Close menu"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
delay: 0.2
}}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<svg className="w-5 h-5 theme-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M6 18L18 6M6 6l12 12"></path>
</svg>
</motion.button>
{/* Content wrapper with padding and scrolling */}
<div className="w-full h-full p-6 pt-16 flex flex-col overflow-y-auto">
{/* Navigation links */}
<nav className="mb-8">
<motion.div
className="flex flex-col space-y-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
delay: 0.1
}}
>
{[
{ href: "#work", label: "Work" },
{ href: "#about", label: "About" }
].map((item, index) => (
<motion.a
key={item.href}
href={item.href}
onClick={() => setIsOpen(false)}
className="p-4 text-xl theme-primary rounded-lg theme-accent flex items-center hover:theme-bg-05 transition-colors"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{
type: "spring",
stiffness: 200,
damping: 20,
delay: 0.2 + index * 0.1
}}
>
<span>{item.label}</span>
<svg className="ml-auto w-5 h-5 theme-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</motion.a>
))}
</motion.div>
</nav>
{/* Footer content */}
<motion.div
className="mt-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
type: "spring",
stiffness: 100,
damping: 20,
delay: 0.4
}}
>
<div className="pt-4 border-t theme-border">
<motion.a
href="mailto:jleibl@proton.me"
onClick={() => setIsOpen(false)}
className="flex items-center justify-center gap-3 w-full py-4 px-6 theme-primary border theme-border rounded-lg hover:theme-bg-05 transition-colors"
whileHover={{ scale: 1.02 }}
>
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
<span>Contact Me</span>
</motion.a>
</div>
<div className="pt-6 flex justify-center pb-6">
<GermanyText />
</div>
</motion.div>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
</>
);
};
export default function Home() {
useSmoothScroll();
@ -149,6 +344,98 @@ export default function Home() {
.red .theme-border { border-color: rgba(255, 0, 0, 0.1); }
.gold .theme-border { border-color: rgba(255, 204, 0, 0.1); }
.black .theme-text-40 { color: rgba(255, 255, 255, 0.4); }
.red .theme-text-40 { color: rgba(255, 0, 0, 0.4); }
.gold .theme-text-40 { color: rgba(255, 204, 0, 0.4); }
.black .theme-text-70 { color: rgba(255, 255, 255, 0.7); }
.red .theme-text-70 { color: rgba(255, 0, 0, 0.7); }
.gold .theme-text-70 { color: rgba(255, 204, 0, 0.7); }
.black .theme-text-90 { color: rgba(255, 255, 255, 0.9); }
.red .theme-text-90 { color: rgba(255, 0, 0, 0.9); }
.gold .theme-text-90 { color: rgba(255, 204, 0, 0.9); }
.black .theme-bg-05 { background-color: rgba(255, 255, 255, 0.05); }
.red .theme-bg-05 { background-color: rgba(255, 0, 0, 0.05); }
.gold .theme-bg-05 { background-color: rgba(255, 204, 0, 0.05); }
.black .nav-glass {
background: rgba(10, 10, 10, 0.8);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.red .nav-glass {
background: rgba(26, 0, 0, 0.8);
box-shadow: 0 8px 32px rgba(26, 0, 0, 0.1);
}
.gold .nav-glass {
background: rgba(26, 20, 0, 0.8);
box-shadow: 0 8px 32px rgba(26, 20, 0, 0.1);
}
.black .nav-item-active {
background: rgba(255, 255, 255, 0.05);
border-bottom: 2px solid rgba(255, 255, 255, 0.8);
}
.red .nav-item-active {
background: rgba(255, 0, 0, 0.05);
border-bottom: 2px solid rgba(255, 0, 0, 0.8);
}
.gold .nav-item-active {
background: rgba(255, 204, 0, 0.05);
border-bottom: 2px solid rgba(255, 204, 0, 0.8);
}
.nav-item {
position: relative;
overflow: hidden;
}
.nav-item::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
transform: translateX(-100%);
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
.black .nav-item::after {
background-color: rgba(255, 255, 255, 0.8);
}
.red .nav-item::after {
background-color: rgba(255, 0, 0, 0.8);
}
.gold .nav-item::after {
background-color: rgba(255, 204, 0, 0.8);
}
.nav-item:hover::after {
transform: translateX(0);
}
.black .theme-bg-gradient { background: linear-gradient(to bottom, rgba(255, 255, 255, 0.02), transparent); }
.red .theme-bg-gradient { background: linear-gradient(to bottom, rgba(255, 0, 0, 0.02), transparent); }
.gold .theme-bg-gradient { background: linear-gradient(to bottom, rgba(255, 204, 0, 0.02), transparent); }
.black .theme-button-primary { background-color: rgba(255, 255, 255, 0.9); color: #0A0A0A; }
.red .theme-button-primary { background-color: rgba(255, 0, 0, 0.9); color: #1A0000; }
.gold .theme-button-primary { background-color: rgba(255, 204, 0, 0.9); color: #1A1400; }
.black .theme-button-primary:hover { background-color: rgba(255, 255, 255, 0.8); }
.red .theme-button-primary:hover { background-color: rgba(255, 0, 0, 0.8); }
.gold .theme-button-primary:hover { background-color: rgba(255, 204, 0, 0.8); }
.black .theme-button-secondary { border-color: rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.9); }
.red .theme-button-secondary { border-color: rgba(255, 0, 0, 0.1); color: rgba(255, 0, 0, 0.9); }
.gold .theme-button-secondary { border-color: rgba(255, 204, 0, 0.1); color: rgba(255, 204, 0, 0.9); }
.black .theme-button-secondary:hover { background-color: rgba(255, 255, 255, 0.05); }
.red .theme-button-secondary:hover { background-color: rgba(255, 0, 0, 0.05); }
.gold .theme-button-secondary:hover { background-color: rgba(255, 204, 0, 0.05); }
.theme-transition {
transition: color 0.3s ease, background-color 0.3s ease, border-color 0.3s ease;
}
@ -191,41 +478,91 @@ export default function Home() {
<link rel="canonical" href="https://jleibl.net/" />
</Head>
<header className="fixed top-0 left-0 right-0 z-40 backdrop-blur-md bg-black/20 border-b theme-border theme-transition" role="banner">
<nav className="max-w-screen-xl mx-auto px-4 sm:px-8 py-3 sm:py-4 flex flex-wrap justify-between items-center font-['DM_Sans']" role="navigation" aria-label="Main navigation">
<Link href="/" className="text-xl sm:text-xl font-medium tracking-tight theme-primary theme-transition" aria-label="Home">
JL
</Link>
<div className="flex items-center gap-4 sm:gap-12 text-sm sm:text-base tracking-wide">
<a href="#work" className="theme-secondary hover:theme-primary theme-transition" aria-label="View my work">
Work
</a>
<a href="#about" className="theme-secondary hover:theme-primary theme-transition" aria-label="About me">
About
</a>
<a
href="mailto:jleibl@proton.me"
className="px-4 sm:px-6 py-2 sm:py-2.5 text-sm sm:text-base theme-accent theme-border hover:bg-white/[0.06] theme-transition rounded-lg sm:rounded-none"
aria-label="Contact me via email"
>
Contact
</a>
</div>
</nav>
</header>
<motion.header
className="fixed top-0 left-0 right-0 z-40 nav-glass backdrop-blur-lg theme-transition"
role="banner"
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{
type: "spring",
damping: 25,
stiffness: 100,
mass: 1,
delay: 0.2
}}
>
<div className="max-w-screen-xl mx-auto">
<nav className="py-5 px-6 sm:px-8 flex justify-between items-center font-['DM_Sans']" role="navigation" aria-label="Main navigation">
<div className="flex items-center">
<Link href="/" className="flex items-center gap-2 group" aria-label="Home">
<div className="relative w-10 h-10 rounded-full theme-accent flex items-center justify-center border theme-border overflow-hidden group-hover:border-opacity-80 transition-all duration-300">
<span className="text-lg font-bold tracking-tight theme-primary theme-transition">JL</span>
<div className="absolute inset-0 theme-bg-05 opacity-0 group-hover:opacity-100 transition-opacity duration-500"/>
</div>
<span className="text-lg font-medium tracking-tight theme-primary theme-transition hidden sm:block">
Jan-Marlon Leibl
</span>
</Link>
</div>
<div className="hidden md:flex items-center">
<div className="flex bg-black/10 backdrop-blur-md rounded-full overflow-hidden theme-border border divide-x divide-white/5">
{["Work", "About"].map((item) => (
<a
key={item}
href={`#${item.toLowerCase()}`}
className="nav-item px-6 py-2 theme-secondary hover:theme-primary theme-transition"
aria-label={`View my ${item.toLowerCase()}`}
>
{item}
</a>
))}
</div>
<div className="ml-6">
<motion.a
href="mailto:jleibl@proton.me"
className="flex items-center gap-2 px-6 py-2 theme-bg-05 theme-primary border theme-border rounded-full hover:bg-opacity-100 transition-all duration-300 group"
aria-label="Contact me via email"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
<svg className="w-4 h-4 theme-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
<span className="text-sm">Contact</span>
<div className="w-0 overflow-hidden group-hover:w-4 transition-all duration-500">
<svg className="w-4 h-4 theme-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</div>
</motion.a>
</div>
</div>
<div className="flex md:hidden">
<MobileMenu />
</div>
</nav>
</div>
<div className="h-px w-full theme-border opacity-50"></div>
</motion.header>
<section className="min-h-[100dvh] flex items-center px-4 sm:px-8 relative pt-16 sm:pt-0">
<div className="absolute inset-0 bg-gradient-to-b from-white/[0.02] via-white/[0.01] to-transparent"></div>
<section className="min-h-[100dvh] flex items-center px-4 sm:px-8 relative pt-24 sm:pt-8">
<div className="absolute inset-0 theme-bg-gradient"></div>
<div className="max-w-screen-xl w-full relative pt-8 sm:pt-24">
<FadeIn className="space-y-8 sm:space-y-16">
<div className="space-y-6 sm:space-y-8">
<div className="space-y-6 sm:space-y-8">
<div className="inline-flex items-center gap-2.5 px-4 py-2 rounded-full theme-accent theme-border hover:theme-border theme-transition">
<div className="inline-flex items-center gap-2.5 px-4 py-2 rounded-full theme-accent theme-border theme-transition">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
<span className="theme-secondary uppercase tracking-[0.2em] text-xs sm:text-sm font-['Instrument_Sans']">Available for Work</span>
</div>
<div className="space-y-2 sm:space-y-3">
<h2 className="font-['DM_Sans'] text-xl sm:text-3xl theme-secondary tracking-wide font-medium theme-transition">Jan-Marlon Leibl</h2>
<h2 className="font-['DM_Sans'] text-xl sm:text-3xl theme-secondary tracking-wide font-medium theme-transition">
Jan-Marlon Leibl
</h2>
<div>
<h1 className="font-['DM_Sans'] text-4xl sm:text-6xl md:text-7xl lg:text-8xl font-bold tracking-tight leading-[1.1] sm:leading-[0.95] max-w-4xl theme-primary theme-transition">
Software Developer
@ -239,9 +576,11 @@ export default function Home() {
Passionate about creating digital experiences, with a focus on PHP and modern web technologies.
</p>
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
<a
<motion.a
href="#work"
className="w-full sm:w-auto text-center inline-flex items-center justify-center gap-2 px-6 sm:px-8 py-3 sm:py-4 bg-white text-black rounded-lg sm:rounded-full hover:bg-white/90 transition-colors text-sm sm:text-base tracking-wide font-medium group"
className="w-full sm:w-auto text-center inline-flex items-center justify-center gap-2 px-6 sm:px-8 py-3 sm:py-4 theme-button-primary rounded-lg sm:rounded-full transition-colors text-sm sm:text-base tracking-wide font-medium group"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
View My Work
<svg
@ -253,10 +592,12 @@ export default function Home() {
>
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
<a
</motion.a>
<motion.a
href="mailto:jleibl@proton.me"
className="w-full sm:w-auto text-center inline-flex items-center justify-center gap-2 px-6 sm:px-8 py-3 sm:py-4 border border-white/10 rounded-lg sm:rounded-full hover:bg-white/[0.05] transition-colors text-sm sm:text-base tracking-wide group"
className="w-full sm:w-auto text-center inline-flex items-center justify-center gap-2 px-6 sm:px-8 py-3 sm:py-4 border theme-border rounded-lg sm:rounded-full theme-button-secondary hover:theme-bg-05 transition-colors text-sm sm:text-base tracking-wide group"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
Get in Touch
<svg
@ -268,41 +609,51 @@ export default function Home() {
>
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</motion.a>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-8 font-['Instrument_Sans'] text-sm sm:text-base tracking-wide border-t border-white/10 pt-6 sm:pt-8">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-8 font-['Instrument_Sans'] text-sm sm:text-base tracking-wide border-t theme-border pt-6 sm:pt-8">
<div className="space-y-1.5 sm:space-y-2.5 group">
<span className="text-white/40 block uppercase tracking-[0.2em] text-xs sm:text-sm">Email</span>
<span className="theme-text-40 block uppercase tracking-[0.2em] text-xs sm:text-sm">Email</span>
<a
href="mailto:jleibl@proton.me"
className="hover:text-white/90 transition-colors flex items-center gap-2 sm:gap-2.5 group-hover:gap-3 duration-300"
className="theme-text-90 hover:theme-primary transition-colors flex items-center gap-2 sm:gap-2.5 group-hover:gap-3 duration-300"
>
jleibl@proton.me
<svg className="w-4 h-4 sm:w-5 sm:h-5 opacity-0 group-hover:opacity-100 transition-all -translate-x-4 group-hover:translate-x-0 duration-300" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<svg
className="w-4 h-4 sm:w-5 sm:h-5 opacity-0 group-hover:opacity-100 transition-all -translate-x-4 group-hover:translate-x-0 duration-300"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</div>
<div className="space-y-1.5 sm:space-y-2.5">
<span className="text-white/40 block uppercase tracking-[0.2em] text-xs sm:text-sm">Role</span>
<span className="text-white/90">Fullstack Developer</span>
<span className="theme-text-40 block uppercase tracking-[0.2em] text-xs sm:text-sm">Role</span>
<span className="theme-text-90">
Fullstack Developer
</span>
</div>
<div className="space-y-1.5 sm:space-y-2.5">
<span className="text-white/40 block uppercase tracking-[0.2em] text-xs sm:text-sm">Experience</span>
<span className="text-white/90">5+ Years</span>
<span className="theme-text-40 block uppercase tracking-[0.2em] text-xs sm:text-sm">Experience</span>
<span className="theme-text-90">
5+ Years
</span>
</div>
</div>
</FadeIn>
</div>
</section>
<section id="about" className="py-20 sm:py-40 px-4 sm:px-8 bg-gradient-to-b from-white/[0.02] to-transparent">
<section id="about" className="py-20 sm:py-40 px-4 sm:px-8 theme-bg-gradient">
<div className="max-w-screen-xl mx-auto">
<FadeIn>
<div className="flex items-baseline gap-4 mb-12 sm:mb-24">
<h2 className="font-['DM_Sans'] text-3xl sm:text-6xl font-semibold tracking-tight">About</h2>
<h2 className="font-['DM_Sans'] text-3xl sm:text-6xl font-semibold tracking-tight theme-primary">About</h2>
<div className="h-px flex-grow bg-white/10 relative top-[-4px]"></div>
</div>
</FadeIn>
@ -310,7 +661,7 @@ export default function Home() {
<div className="grid md:grid-cols-[1fr,2fr] gap-12 sm:gap-24">
<div className="space-y-6 sm:space-y-8">
<FadeIn>
<div className="aspect-square bg-gradient-to-tr from-white/[0.05] to-white/[0.02] rounded-2xl overflow-hidden border border-white/[0.05] hover:border-white/10 transition-colors mx-auto md:mx-0 max-w-[280px] md:max-w-none">
<div className="aspect-square bg-gradient-to-tr theme-bg-05 rounded-2xl overflow-hidden border theme-border hover:border-white/10 transition-colors mx-auto md:mx-0 max-w-[280px] md:max-w-none">
<Image
src="/profile-image.jpg"
alt="Jan-Marlon Leibl - Fullstack Software Developer"
@ -323,8 +674,8 @@ export default function Home() {
</FadeIn>
<FadeIn delay={0.1}>
<div className="space-y-2 text-center md:text-left">
<h3 className="font-['DM_Sans'] text-xl sm:text-2xl font-medium">Jan-Marlon Leibl</h3>
<p className="font-['Instrument_Sans'] text-white/70 text-base sm:text-lg">Fullstack Developer</p>
<h3 className="font-['DM_Sans'] text-xl sm:text-2xl font-medium theme-primary">Jan-Marlon Leibl</h3>
<p className="font-['Instrument_Sans'] theme-text-70 text-base sm:text-lg">Fullstack Developer</p>
</div>
</FadeIn>
</div>
@ -332,10 +683,10 @@ export default function Home() {
<FadeIn delay={0.2}>
<div className="space-y-10 sm:space-y-16 font-['Instrument_Sans']">
<div className="space-y-6 sm:space-y-8">
<p className="text-xl sm:text-2xl text-white/90 leading-relaxed">
<p className="text-xl sm:text-2xl theme-text-90 leading-relaxed">
Hello! I&apos;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.
</p>
<p className="text-base sm:text-xl text-white/70 leading-relaxed">
<p className="text-base sm:text-xl theme-text-70 leading-relaxed">
Today, I specialize in PHP and TypeScript development, constantly pushing the boundaries of what&apos;s possible on the web. My journey has led me from creating simple applications to developing complex systems used by thousands.
</p>
</div>
@ -343,16 +694,16 @@ export default function Home() {
<div className="grid grid-cols-1 sm:grid-cols-2 gap-10 sm:gap-16">
<div className="space-y-6 sm:space-y-8">
<div className="flex items-baseline gap-4">
<h3 className="text-sm sm:text-base text-white/40 uppercase tracking-[0.2em]">Technologies</h3>
<div className="h-px flex-grow bg-white/10"></div>
<h3 className="text-sm sm:text-base theme-text-40 uppercase tracking-[0.2em]">Technologies</h3>
<div className="h-px flex-grow theme-border"></div>
</div>
<ul className="space-y-4 sm:space-y-5">
{['PHP', 'JavaScript', 'MySQL', 'React'].map((tech) => (
<li
key={tech}
className="text-base sm:text-lg group flex items-center gap-3 hover:text-white/90 transition-colors cursor-default"
className="text-base sm:text-lg group flex items-center gap-3 theme-text-70 hover:theme-text-90 transition-colors cursor-default"
>
<span className="w-2 h-2 rounded-full bg-white/40 group-hover:bg-white/90 transition-colors"></span>
<span className="w-2 h-2 rounded-full theme-text-40 group-hover:theme-text-90 transition-colors"></span>
{tech}
</li>
))}
@ -361,8 +712,8 @@ export default function Home() {
<div className="space-y-6 sm:space-y-8">
<div className="flex items-baseline gap-4">
<h3 className="text-sm sm:text-base text-white/40 uppercase tracking-[0.2em]">Interests</h3>
<div className="h-px flex-grow bg-white/10"></div>
<h3 className="text-sm sm:text-base theme-text-40 uppercase tracking-[0.2em]">Interests</h3>
<div className="h-px flex-grow theme-border"></div>
</div>
<ul className="space-y-4 sm:space-y-5">
{[
@ -373,9 +724,9 @@ export default function Home() {
].map((interest) => (
<li
key={interest}
className="text-base sm:text-lg group flex items-center gap-3 hover:text-white/90 transition-colors cursor-default"
className="text-base sm:text-lg group flex items-center gap-3 theme-text-70 hover:theme-text-90 transition-colors cursor-default"
>
<span className="w-2 h-2 rounded-full bg-white/40 group-hover:bg-white/90 transition-colors"></span>
<span className="w-2 h-2 rounded-full theme-text-40 group-hover:theme-text-90 transition-colors"></span>
{interest}
</li>
))}
@ -384,22 +735,24 @@ export default function Home() {
</div>
<div className="pt-6 sm:pt-8 flex flex-col sm:flex-row gap-3 sm:gap-6">
<a
<motion.a
href="https://github.com/AtomicWasTaken"
target="_blank"
rel="noopener noreferrer"
className="w-full text-center px-6 sm:px-8 py-3 sm:py-4 border border-white/10 rounded-lg sm:rounded-full hover:bg-white/[0.05] transition-colors text-sm sm:text-base tracking-wide font-medium"
className="w-full text-center px-6 sm:px-8 py-3 sm:py-4 border theme-border rounded-lg sm:rounded-full hover:theme-bg-05 transition-colors text-sm sm:text-base tracking-wide font-medium theme-text-90"
whileHover={{ scale: 1.02 }}
>
View GitHub
</a>
<a
</motion.a>
<motion.a
href="https://www.linkedin.com/in/janmarlonleibl/"
target="_blank"
rel="noopener noreferrer"
className="w-full text-center px-6 sm:px-8 py-3 sm:py-4 border border-white/10 rounded-lg sm:rounded-full hover:bg-white/[0.05] transition-colors text-sm sm:text-base tracking-wide font-medium"
className="w-full text-center px-6 sm:px-8 py-3 sm:py-4 border theme-border rounded-lg sm:rounded-full hover:theme-bg-05 transition-colors text-sm sm:text-base tracking-wide font-medium theme-text-90"
whileHover={{ scale: 1.02 }}
>
Connect on LinkedIn
</a>
</motion.a>
</div>
</div>
</FadeIn>
@ -408,14 +761,14 @@ export default function Home() {
</section>
<section id="work" className="py-20 sm:py-40 px-4 sm:px-8 relative">
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-white/[0.02] to-transparent pointer-events-none"></div>
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-white/[0.02] to-transparent pointer-events-none theme-bg-gradient"></div>
<div className="max-w-screen-xl mx-auto relative">
<FadeIn>
<div className="flex flex-col gap-3 mb-12 sm:mb-24">
<span className="text-white/40 uppercase tracking-[0.2em] text-sm sm:text-base font-['Instrument_Sans']">Portfolio</span>
<span className="theme-text-40 uppercase tracking-[0.2em] text-sm sm:text-base font-['Instrument_Sans']">Portfolio</span>
<div className="flex items-baseline gap-4">
<h2 className="font-['DM_Sans'] text-3xl sm:text-6xl font-semibold tracking-tight">Selected Work</h2>
<div className="h-px flex-grow bg-white/10"></div>
<h2 className="font-['DM_Sans'] text-3xl sm:text-6xl font-semibold tracking-tight theme-primary">Selected Work</h2>
<div className="h-px flex-grow theme-border"></div>
</div>
</div>
</FadeIn>
@ -450,33 +803,33 @@ export default function Home() {
<FadeIn key={index} delay={index * 0.1}>
<div className="group relative">
<div className="absolute top-0 left-0 right-0 flex items-center gap-4">
<div className="font-['Instrument_Sans'] text-sm sm:text-base text-white/40 uppercase tracking-[0.2em] py-2 pr-4">
<div className="font-['Instrument_Sans'] text-sm sm:text-base theme-text-40 uppercase tracking-[0.2em] py-2 pr-4">
{project.year}
</div>
<div className="h-px flex-grow bg-white/10"></div>
<div className="h-px flex-grow theme-border"></div>
</div>
<div className="pt-12 sm:pt-16 grid grid-cols-1 lg:grid-cols-[1.5fr,1fr] gap-6 sm:gap-16">
<div className="space-y-4 sm:space-y-8">
<h3 className="font-['DM_Sans'] text-3xl sm:text-6xl font-semibold tracking-tight text-white group-hover:text-white/90 transition-colors">
<h3 className="font-['DM_Sans'] text-3xl sm:text-6xl font-semibold tracking-tight theme-primary group-hover:theme-text-90 transition-colors">
{project.title}
</h3>
<p className="font-['Instrument_Sans'] text-base sm:text-xl text-white/70 leading-relaxed group-hover:text-white/80 transition-colors max-w-xl">
<p className="font-['Instrument_Sans'] text-base sm:text-xl theme-text-70 leading-relaxed group-hover:theme-text-70 transition-colors max-w-xl">
{project.description}
</p>
</div>
<div className="space-y-6 sm:space-y-12">
<div className="space-y-4 sm:space-y-6">
<h4 className="font-['Instrument_Sans'] text-sm sm:text-base text-white/40 uppercase tracking-[0.2em]">Technologies</h4>
<h4 className="font-['Instrument_Sans'] text-sm sm:text-base theme-text-40 uppercase tracking-[0.2em]">Technologies</h4>
<div className="flex flex-wrap items-center gap-3 sm:gap-4">
{project.tags.map((tag, tagIndex) => (
<span
key={tagIndex}
className="text-sm sm:text-base text-white/70 font-['Instrument_Sans'] tracking-wide py-1 sm:py-2 group-hover:text-white/80 transition-colors"
className="text-sm sm:text-base theme-text-70 font-['Instrument_Sans'] tracking-wide py-1 sm:py-2 group-hover:theme-text-90 transition-colors"
>
{tag}{tagIndex !== project.tags.length - 1 && (
<span className="mx-3 sm:mx-4 text-white/20 select-none"></span>
<span className="mx-3 sm:mx-4 theme-text-40 select-none"></span>
)}
</span>
))}
@ -485,7 +838,7 @@ export default function Home() {
</div>
</div>
<div className="absolute -inset-x-4 sm:-inset-x-8 -inset-y-4 sm:-inset-y-6 rounded-2xl sm:rounded-3xl border border-white/0 group-hover:border-white/5 transition-colors"></div>
<div className="absolute -inset-x-4 sm:-inset-x-8 -inset-y-4 sm:-inset-y-6 rounded-2xl sm:rounded-3xl border border-white/0 group-hover:theme-border transition-colors"></div>
</div>
</FadeIn>
))}
@ -493,13 +846,13 @@ export default function Home() {
</div>
</section>
<footer role="contentinfo" className="mt-20 sm:mt-32 bg-gradient-to-b from-white/[0.02] via-white/[0.03] to-white/[0.02] border-t border-white/[0.05]">
<footer role="contentinfo" className="mt-20 sm:mt-32 theme-bg-gradient border-t theme-border">
<div className="max-w-screen-xl mx-auto px-4 sm:px-8 py-20 sm:py-32">
<div className="grid gap-16 sm:gap-24">
<FadeIn>
<div className="text-center space-y-4 sm:space-y-5">
<h2 className="font-['DM_Sans'] text-4xl sm:text-7xl font-semibold tracking-tight">Let&apos;s Connect</h2>
<p className="font-['Instrument_Sans'] text-white/70 text-lg sm:text-2xl max-w-2xl mx-auto">
<h2 className="font-['DM_Sans'] text-4xl sm:text-7xl font-semibold tracking-tight theme-primary">Let&apos;s Connect</h2>
<p className="font-['Instrument_Sans'] theme-text-70 text-lg sm:text-2xl max-w-2xl mx-auto">
Always interested in new opportunities and collaborations.
</p>
</div>
@ -509,20 +862,19 @@ export default function Home() {
<div className="flex flex-col items-center gap-8 sm:gap-12">
<a
href="mailto:jleibl@proton.me"
className="group flex items-center gap-2 sm:gap-3 text-xl sm:text-4xl text-white/90 hover:text-white transition-colors"
className="group flex items-center gap-2 sm:gap-3 text-xl sm:text-4xl theme-text-90 hover:theme-primary transition-colors"
>
jleibl@proton.me
<svg className="w-5 h-5 sm:w-8 sm:h-8 opacity-0 group-hover:opacity-100 transition-all -translate-x-4 group-hover:translate-x-0 duration-300" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</a>
</div>
</FadeIn>
<FadeIn delay={0.2}>
<div className="pt-6 sm:pt-12 text-center">
<p className="text-white/40 text-xs sm:text-sm tracking-[0.1em]">
<p className="theme-text-40 text-xs sm:text-sm tracking-[0.1em]">
© {new Date().getFullYear()} Jan-Marlon Leibl. All rights reserved.
</p>
</div>