Compare commits

..

8 Commits

Author SHA1 Message Date
Basilosaurusrex
6af24d28e7 impressum 2026-02-02 22:03:15 +01:00
Basilosaurusrex
217bbdc6a7 new version 2026-02-02 21:42:41 +01:00
Basilosaurusrex
7e8d40878b light 2026-02-02 16:17:08 +01:00
Basilosaurusrex
2dc5401179 imeges 2026-02-02 15:16:36 +01:00
Basilosaurusrex
3b9e35a447 Values-Section: Hintergrundbild backgroud_effect.png (groß, transparent) 2026-02-02 12:11:43 +01:00
Basilosaurusrex
a95932cd79 CountUp-Effekt für Zahlen, Navbar-Button wie Hero-Button (dark) 2026-02-02 10:41:41 +01:00
Basilosaurusrex
01102ef3f7 Custom CTA-Buttons: Sparkle/Demo-Button, blauer Primary-Button mit Text-Animation, einheitlicher Stil für alle blauen Buttons 2026-02-02 09:29:37 +01:00
22d641e4e5 Neues WEBklar-Logo, Favicon und Tab-Titel 2026-02-02 00:08:51 +01:00
30 changed files with 2433 additions and 359 deletions

View File

@@ -1,21 +1,20 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="de">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- TODO: Set the document title to the name of your application --> <title>WEBklar</title>
<title>Lovable App</title> <meta name="description" content="WEBklar Ihre Webagentur" />
<meta name="description" content="Lovable Generated Project" /> <meta name="author" content="WEBklar" />
<meta name="author" content="Lovable" />
<!-- TODO: Update og:title to match your application name --> <meta property="og:title" content="WEBklar" />
<meta property="og:title" content="Lovable App" /> <meta property="og:description" content="WEBklar Ihre Webagentur" />
<meta property="og:description" content="Lovable Generated Project" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" /> <meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@Lovable" /> <meta name="twitter:site" content="@WEBklar" />
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" /> <meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
</head> </head>

720
package-lock.json generated
View File

@@ -48,6 +48,7 @@
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"motion": "^12.29.2", "motion": "^12.29.2",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"ogl": "^1.0.11",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@@ -70,7 +71,7 @@
"@types/node": "^22.16.5", "@types/node": "^22.16.5",
"@types/react": "^18.3.23", "@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"@vitejs/plugin-react-swc": "^3.11.0", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"eslint": "^9.32.0", "eslint": "^9.32.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
@@ -120,6 +121,173 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/compat-data": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helpers": "^7.28.6",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/generator": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz",
"integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
@@ -130,6 +298,78 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-validator-option": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
"integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.28.2", "version": "7.28.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
@@ -139,6 +379,54 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/parser": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -849,17 +1137,24 @@
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24" "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
@@ -871,15 +1166,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@@ -887,9 +1173,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25", "version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@@ -2593,232 +2879,6 @@
"win32" "win32"
] ]
}, },
"node_modules/@swc/core": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz",
"integrity": "sha512-YWqn+0IKXDhqVLKoac4v2tV6hJqB/wOh8/Br8zjqeqBkKa77Qb0Kw2i7LOFzjFNZbZaPH6AlMGlBwNrxaauaAg==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.23"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.13.2",
"@swc/core-darwin-x64": "1.13.2",
"@swc/core-linux-arm-gnueabihf": "1.13.2",
"@swc/core-linux-arm64-gnu": "1.13.2",
"@swc/core-linux-arm64-musl": "1.13.2",
"@swc/core-linux-x64-gnu": "1.13.2",
"@swc/core-linux-x64-musl": "1.13.2",
"@swc/core-win32-arm64-msvc": "1.13.2",
"@swc/core-win32-ia32-msvc": "1.13.2",
"@swc/core-win32-x64-msvc": "1.13.2"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.2.tgz",
"integrity": "sha512-44p7ivuLSGFJ15Vly4ivLJjg3ARo4879LtEBAabcHhSZygpmkP8eyjyWxrH3OxkY1eRZSIJe8yRZPFw4kPXFPw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.2.tgz",
"integrity": "sha512-Lb9EZi7X2XDAVmuUlBm2UvVAgSCbD3qKqDCxSI4jEOddzVOpNCnyZ/xEampdngUIyDDhhJLYU9duC+Mcsv5Y+A==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.2.tgz",
"integrity": "sha512-9TDe/92ee1x57x+0OqL1huG4BeljVx0nWW4QOOxp8CCK67Rpc/HHl2wciJ0Kl9Dxf2NvpNtkPvqj9+BUmM9WVA==",
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.2.tgz",
"integrity": "sha512-KJUSl56DBk7AWMAIEcU83zl5mg3vlQYhLELhjwRFkGFMvghQvdqQ3zFOYa4TexKA7noBZa3C8fb24rI5sw9Exg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.2.tgz",
"integrity": "sha512-teU27iG1oyWpNh9CzcGQ48ClDRt/RCem7mYO7ehd2FY102UeTws2+OzLESS1TS1tEZipq/5xwx3FzbVgiolCiQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.2.tgz",
"integrity": "sha512-dRPsyPyqpLD0HMRCRpYALIh4kdOir8pPg4AhNQZLehKowigRd30RcLXGNVZcc31Ua8CiPI4QSgjOIxK+EQe4LQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.2.tgz",
"integrity": "sha512-CCxETW+KkYEQDqz1SYC15YIWYheqFC+PJVOW76Maa/8yu8Biw+HTAcblKf2isrlUtK8RvrQN94v3UXkC2NzCEw==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.2.tgz",
"integrity": "sha512-Wv/QTA6PjyRLlmKcN6AmSI4jwSMRl0VTLGs57PHTqYRwwfwd7y4s2fIPJVBNbAlXd795dOEP6d/bGSQSyhOX3A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.2.tgz",
"integrity": "sha512-PuCdtNynEkUNbUXX/wsyUC+t4mamIU5y00lT5vJcAvco3/r16Iaxl5UCzhXYaWZSNVZMzPp9qN8NlSL8M5pPxw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.2.tgz",
"integrity": "sha512-qlmMkFZJus8cYuBURx1a3YAG2G7IW44i+FEYV5/32ylKkzGNAr9tDJSA53XNnNXkAB5EXSPsOz7bn5C3JlEtdQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@swc/types": {
"version": "0.1.23",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz",
"integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@tabler/icons": { "node_modules/@tabler/icons": {
"version": "3.36.1", "version": "3.36.1",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.36.1.tgz", "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.36.1.tgz",
@@ -2991,7 +3051,53 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
"@types/babel__generator": "*",
"@types/babel__template": "*",
"@types/babel__traverse": "*"
}
},
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.2"
}
}, },
"node_modules/@types/chai": { "node_modules/@types/chai": {
"version": "5.2.3", "version": "5.2.3",
@@ -3094,7 +3200,6 @@
"integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -3110,7 +3215,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
@@ -3122,7 +3226,6 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^18.0.0" "@types/react": "^18.0.0"
} }
@@ -3188,7 +3291,6 @@
"integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/scope-manager": "8.38.0",
"@typescript-eslint/types": "8.38.0", "@typescript-eslint/types": "8.38.0",
@@ -3401,18 +3503,25 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@vitejs/plugin-react-swc": { "node_modules/@vitejs/plugin-react": {
"version": "3.11.0", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
"integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/core": "^7.28.0",
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
"@rolldown/pluginutils": "1.0.0-beta.27", "@rolldown/pluginutils": "1.0.0-beta.27",
"@swc/core": "^1.12.11" "@types/babel__core": "^7.20.5",
"react-refresh": "^0.17.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"vite": "^4 || ^5 || ^6 || ^7" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
@@ -3544,7 +3653,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -3833,7 +3941,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001726", "caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173", "electron-to-chromium": "^1.5.173",
@@ -4098,6 +4205,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4304,7 +4418,6 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/kossnocorp" "url": "https://github.com/sponsors/kossnocorp"
@@ -4401,7 +4514,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/dom-helpers": { "node_modules/dom-helpers": {
"version": "5.2.1", "version": "5.2.1",
@@ -4459,8 +4573,7 @@
"version": "8.6.0", "version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/embla-carousel-react": { "node_modules/embla-carousel-react": {
"version": "8.6.0", "version": "8.6.0",
@@ -4649,7 +4762,6 @@
"integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -5089,6 +5201,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -5567,7 +5689,6 @@
"integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"abab": "^2.0.6", "abab": "^2.0.6",
"acorn": "^8.8.1", "acorn": "^8.8.1",
@@ -5608,6 +5729,19 @@
} }
} }
}, },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/json-buffer": { "node_modules/json-buffer": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5629,6 +5763,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -6197,6 +6344,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"lz-string": "bin/bin.js" "lz-string": "bin/bin.js"
} }
@@ -6443,6 +6591,12 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/ogl": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz",
"integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==",
"license": "Unlicense"
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -6638,7 +6792,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -6779,6 +6932,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1", "ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0", "ansi-styles": "^5.0.0",
@@ -6794,6 +6948,7 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -6804,6 +6959,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@@ -6816,7 +6972,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/prop-types": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
@@ -6890,7 +7047,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -6917,7 +7073,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@@ -6931,7 +7086,6 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.61.1.tgz", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.61.1.tgz",
"integrity": "sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==", "integrity": "sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
}, },
@@ -6974,6 +7128,16 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
@@ -7647,7 +7811,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -7714,8 +7877,7 @@
"version": "0.182.0", "version": "0.182.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz",
"integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/tiny-invariant": { "node_modules/tiny-invariant": {
"version": "1.3.3", "version": "1.3.3",
@@ -7778,7 +7940,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -7901,7 +8062,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -8102,7 +8262,6 @@
"integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",
@@ -8496,6 +8655,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
"license": "ISC"
},
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",

View File

@@ -53,6 +53,7 @@
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"motion": "^12.29.2", "motion": "^12.29.2",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"ogl": "^1.0.11",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@@ -75,7 +76,7 @@
"@types/node": "^22.16.5", "@types/node": "^22.16.5",
"@types/react": "^18.3.23", "@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"@vitejs/plugin-react-swc": "^3.11.0", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"eslint": "^9.32.0", "eslint": "^9.32.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",

BIN
public/backgroud_effect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

8
public/favicon.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<rect x="48" y="48" width="416" height="416" rx="92" fill="#FFFFFF"/>
<ellipse cx="256" cy="256" rx="210" ry="150" fill="#111111" transform="rotate(-28 256 256)"/>
<circle cx="356" cy="172" r="18" fill="#FFFFFF"/>
<g transform="translate(256 256) rotate(-28)">
<path d="M 0,-170 C 22,-105 60,-72 120,-56 C 60,-40 22,-8 0,64 C -22,-8 -60,-40 -120,-56 C -60,-72 -22,-105 0,-170 Z" fill="#FFFFFF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -5,6 +5,8 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index"; import Index from "./pages/Index";
import ContactPage from "./pages/Contact"; import ContactPage from "./pages/Contact";
import AGBPage from "./pages/AGB";
import ImpressumPage from "./pages/Impressum";
import NotFound from "./pages/NotFound"; import NotFound from "./pages/NotFound";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -18,6 +20,8 @@ const App = () => (
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<Index />} />
<Route path="/kontakt" element={<ContactPage />} /> <Route path="/kontakt" element={<ContactPage />} />
<Route path="/agb" element={<AGBPage />} />
<Route path="/impressum" element={<ImpressumPage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Routes> </Routes>

View File

@@ -36,13 +36,13 @@ const Contact = () => {
{/* Contact Info */} {/* Contact Info */}
<div className="divider mb-12" /> <div className="divider mb-12" />
<div className="flex flex-col sm:flex-row gap-8 text-muted-foreground"> <div className="flex flex-col sm:flex-row gap-8 text-muted-foreground">
<a href="mailto:hello@webklar.de" className="flex items-center gap-3 hover:text-foreground transition-colors group"> <a href="mailto:support@webklar.com" className="flex items-center gap-3 hover:text-foreground transition-colors group">
<Mail className="w-5 h-5" /> <Mail className="w-5 h-5" />
<span>hello@webklar.de</span> <span>support@webklar.com</span>
</a> </a>
<a href="tel:+4912345678" className="flex items-center gap-3 hover:text-foreground transition-colors group"> <a href="tel:+491704969375" className="flex items-center gap-3 hover:text-foreground transition-colors group">
<Phone className="w-5 h-5" /> <Phone className="w-5 h-5" />
<span>+49 123 456 78</span> <span>0170 4969375</span>
</a> </a>
</div> </div>
</div> </div>

128
src/components/CountUp.tsx Normal file
View File

@@ -0,0 +1,128 @@
import { useInView, useMotionValue, useSpring } from "motion/react";
import { useCallback, useEffect, useRef } from "react";
interface CountUpProps {
to: number;
from?: number;
direction?: "up" | "down";
delay?: number;
duration?: number;
className?: string;
startWhen?: boolean;
separator?: string;
suffix?: string;
prefix?: string;
padMinLength?: number;
onStart?: () => void;
onEnd?: () => void;
}
export default function CountUp({
to,
from = 0,
direction = "up",
delay = 0,
duration = 2,
className = "",
startWhen = true,
separator = "",
suffix = "",
prefix = "",
padMinLength,
onStart,
onEnd,
}: CountUpProps) {
const ref = useRef<HTMLSpanElement>(null);
const containerRef = useRef<HTMLSpanElement>(null);
const motionValue = useMotionValue(direction === "down" ? to : from);
const damping = 20 + 40 * (1 / duration);
const stiffness = 100 * (1 / duration);
const springValue = useSpring(motionValue, {
damping,
stiffness,
});
const isInView = useInView(containerRef, { once: true, margin: "0px" });
const getDecimalPlaces = (num: number) => {
const str = num.toString();
if (str.includes(".")) {
const decimals = str.split(".")[1];
if (decimals && parseInt(decimals, 10) !== 0) {
return decimals.length;
}
}
return 0;
};
const maxDecimals = Math.max(getDecimalPlaces(from), getDecimalPlaces(to));
const formatValue = useCallback(
(latest: number) => {
if (padMinLength != null) {
const n = Math.round(latest);
return n.toString().padStart(padMinLength, "0");
}
const hasDecimals = maxDecimals > 0;
const options: Intl.NumberFormatOptions = {
useGrouping: !!separator,
minimumFractionDigits: hasDecimals ? maxDecimals : 0,
maximumFractionDigits: hasDecimals ? maxDecimals : 0,
};
let formattedNumber = Intl.NumberFormat("de-DE", options).format(latest);
if (separator) {
formattedNumber = formattedNumber.replace(/\s/g, separator);
}
return formattedNumber;
},
[maxDecimals, separator, padMinLength]
);
useEffect(() => {
if (ref.current) {
ref.current.textContent = formatValue(direction === "down" ? to : from);
}
}, [from, to, direction, formatValue]);
useEffect(() => {
if (isInView && startWhen) {
if (typeof onStart === "function") onStart();
const timeoutId = setTimeout(() => {
motionValue.set(direction === "down" ? from : to);
}, delay * 1000);
const durationTimeoutId = setTimeout(
() => {
if (typeof onEnd === "function") onEnd();
},
delay * 1000 + duration * 1000
);
return () => {
clearTimeout(timeoutId);
clearTimeout(durationTimeoutId);
};
}
}, [isInView, startWhen, motionValue, direction, from, to, delay, onStart, onEnd, duration]);
useEffect(() => {
const unsubscribe = springValue.on("change", (latest) => {
if (ref.current) {
ref.current.textContent = formatValue(latest);
}
});
return () => unsubscribe();
}, [springValue, formatValue]);
return (
<span ref={containerRef} className={className}>
{prefix && <span>{prefix}</span>}
<span ref={ref} />
{suffix && <span>{suffix}</span>}
</span>
);
}

View File

@@ -1,6 +1,7 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ArrowRight } from "lucide-react"; import { ArrowRight } from "lucide-react";
import CountUp from "@/components/CountUp";
const DifferentiationSection = () => { const DifferentiationSection = () => {
return ( return (
@@ -15,7 +16,9 @@ const DifferentiationSection = () => {
<div className="grid md:grid-cols-3 gap-8 mb-12"> <div className="grid md:grid-cols-3 gap-8 mb-12">
<div className="p-6 border border-border rounded-lg bg-background"> <div className="p-6 border border-border rounded-lg bg-background">
<div className="text-4xl font-display font-medium text-foreground mb-2">01</div> <div className="text-4xl font-display font-medium text-foreground mb-2">
<CountUp from={0} to={1} duration={1} padMinLength={2} startWhen={true} />
</div>
<h3 className="text-lg font-display font-medium text-foreground uppercase tracking-tight mb-2"> <h3 className="text-lg font-display font-medium text-foreground uppercase tracking-tight mb-2">
Alles aus einer Hand Alles aus einer Hand
</h3> </h3>
@@ -25,7 +28,9 @@ const DifferentiationSection = () => {
</div> </div>
<div className="p-6 border border-border rounded-lg bg-background"> <div className="p-6 border border-border rounded-lg bg-background">
<div className="text-4xl font-display font-medium text-foreground mb-2">02</div> <div className="text-4xl font-display font-medium text-foreground mb-2">
<CountUp from={0} to={2} duration={1} padMinLength={2} startWhen={true} />
</div>
<h3 className="text-lg font-display font-medium text-foreground uppercase tracking-tight mb-2"> <h3 className="text-lg font-display font-medium text-foreground uppercase tracking-tight mb-2">
Systeme statt Inseln Systeme statt Inseln
</h3> </h3>
@@ -35,7 +40,9 @@ const DifferentiationSection = () => {
</div> </div>
<div className="p-6 border border-border rounded-lg bg-background"> <div className="p-6 border border-border rounded-lg bg-background">
<div className="text-4xl font-display font-medium text-foreground mb-2">03</div> <div className="text-4xl font-display font-medium text-foreground mb-2">
<CountUp from={0} to={3} duration={1} padMinLength={2} startWhen={true} />
</div>
<h3 className="text-lg font-display font-medium text-foreground uppercase tracking-tight mb-2"> <h3 className="text-lg font-display font-medium text-foreground uppercase tracking-tight mb-2">
Langfristige Partnerschaft Langfristige Partnerschaft
</h3> </h3>

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import Logo from '@/components/Logo';
const DevStudio: React.FC = () => { const DevStudio: React.FC = () => {
return ( return (
@@ -15,7 +16,7 @@ const Footer: React.FC = () => {
<div> <div>
<div className="mr-0 mb-4 md:mr-4 md:flex"> <div className="mr-0 mb-4 md:mr-4 md:flex">
<a className="relative z-20 mr-4 flex items-center space-x-2 px-2 py-1 text-sm font-normal text-white" href="/"> <a className="relative z-20 mr-4 flex items-center space-x-2 px-2 py-1 text-sm font-normal text-white" href="/">
<img alt="logo" width="30" height="30" src="https://assets.aceternity.com/logo-dark.png" /> <Logo width={30} height={30} />
<span className="font-medium text-white">WEBklar</span> <span className="font-medium text-white">WEBklar</span>
</a> </a>
</div> </div>

View File

@@ -13,6 +13,7 @@ import {
MobileNavToggle, MobileNavToggle,
MobileNavMenu, MobileNavMenu,
} from "@/components/ui/resizable-navbar"; } from "@/components/ui/resizable-navbar";
import Logo from "@/components/Logo";
const Header = () => { const Header = () => {
const navItems = [ const navItems = [
@@ -30,6 +31,7 @@ const Header = () => {
{/* Desktop Navigation */} {/* Desktop Navigation */}
<NavBody> <NavBody>
<NavbarLogo href="#"> <NavbarLogo href="#">
<Logo width={30} height={30} />
<span className="font-display text-lg font-medium tracking-tight"> <span className="font-display text-lg font-medium tracking-tight">
Webklar Webklar
</span> </span>
@@ -39,8 +41,7 @@ const Header = () => {
<Link to="/kontakt"> <Link to="/kontakt">
<NavbarButton <NavbarButton
as="span" as="span"
variant="primary" variant="dark"
className="!text-black"
> >
Kontakt Kontakt
</NavbarButton> </NavbarButton>
@@ -52,6 +53,7 @@ const Header = () => {
<MobileNav> <MobileNav>
<MobileNavHeader> <MobileNavHeader>
<NavbarLogo href="#"> <NavbarLogo href="#">
<Logo width={30} height={30} />
<span className="font-display text-lg font-medium tracking-tight"> <span className="font-display text-lg font-medium tracking-tight">
Webklar Webklar
</span> </span>
@@ -82,8 +84,8 @@ const Header = () => {
<Link to="/kontakt" onClick={() => setIsMobileMenuOpen(false)}> <Link to="/kontakt" onClick={() => setIsMobileMenuOpen(false)}>
<NavbarButton <NavbarButton
as="span" as="span"
variant="primary" variant="dark"
className="block w-full text-center !text-black" className="block w-full text-center"
> >
Kontakt Kontakt
</NavbarButton> </NavbarButton>

View File

@@ -1,13 +1,79 @@
import { Link } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowRight } from "lucide-react"; import { ArrowRight } from "lucide-react";
import { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } from "react";
import Silk from "@/components/Silk"; import Silk from "@/components/Silk";
import CountUp from "@/components/CountUp";
const SPARKLE_SVG = (
<svg className="btn-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456ZM16.894 20.567 16.5 21.75l-.394-1.183a2.25 2.25 0 0 0-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 0 0 1.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 0 0 1.423 1.423l1.183.394-1.183.394a2.25 2.25 0 0 0-1.423 1.423Z"
/>
</svg>
);
function DemoButtonLetters({ text }: { text: string }) {
// #region agent log
const chars = text.split("");
const spaceIndex = chars.findIndex((c) => c === " ");
const lastIndex = chars.length - 1;
const lastChar = chars[lastIndex];
fetch("http://127.0.0.1:7244/ingest/72f53105-0a54-4d4c-a295-fb93aa72afcc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
location: "Hero.tsx:DemoButtonLetters",
message: "Letter split for button text",
data: { text, len: chars.length, spaceIndex, spaceChar: spaceIndex >= 0 ? chars[spaceIndex] : null, lastIndex, lastChar },
timestamp: Date.now(),
sessionId: "debug-session",
hypothesisId: "A,C",
}),
}).catch(() => {});
// #endregion
return (
<>
{chars.map((char, i) => (
<span key={i} className={char === " " ? "btn-letter btn-letter-space" : "btn-letter"}>
{char}
</span>
))}
</>
);
}
const FOUNDING_DATE = new Date("2026-01-25"); // Samstag, 25. Januar 2026 const FOUNDING_DATE = new Date("2026-01-25"); // Samstag, 25. Januar 2026
const Hero = () => { const Hero = () => {
const navigate = useNavigate();
const [companyAge, setCompanyAge] = useState(""); const [companyAge, setCompanyAge] = useState("");
const secondBtnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
const el = secondBtnRef.current;
if (!el) return;
const firstTxtWrapper = el.querySelector(".txt-wrapper");
const letters = firstTxtWrapper ? firstTxtWrapper.querySelectorAll(".btn-letter") : [];
const spaceIdx = 8;
const lastIdx = 16;
const wSpace = letters[spaceIdx]?.getBoundingClientRect?.()?.width ?? -1;
const wLast = letters[lastIdx]?.getBoundingClientRect?.()?.width ?? -1;
fetch("http://127.0.0.1:7244/ingest/72f53105-0a54-4d4c-a295-fb93aa72afcc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
location: "Hero.tsx:useEffect:measure",
message: "Measured btn-letter widths (space + last)",
data: { letterCount: letters.length, wSpace, wLast, spaceIdx, lastIdx },
timestamp: Date.now(),
sessionId: "debug-session",
runId: "post-fix",
hypothesisId: "B,D,E",
}),
}).catch(() => {});
}, []);
useEffect(() => { useEffect(() => {
const calculateAge = () => { const calculateAge = () => {
@@ -40,7 +106,7 @@ const Hero = () => {
<Silk <Silk
speed={3} speed={3}
scale={0.5} scale={0.5}
color="#373737" color="#6a6a6a"
noiseIntensity={4 noiseIntensity={4
} }
rotation={0} rotation={0}
@@ -66,23 +132,54 @@ const Hero = () => {
</p> </p>
{/* CTA Buttons */} {/* CTA Buttons */}
<div className="flex flex-col sm:flex-row gap-4 mb-6 animate-fade-in" style={{ animationDelay: '0.5s' }}> <div className="flex flex-col sm:flex-row flex-nowrap items-stretch sm:items-center gap-3 sm:gap-4 mb-6 animate-fade-in" style={{ animationDelay: '0.5s' }}>
<Link to="/kontakt"> <div className="btn-wrapper shrink-0 w-full sm:w-auto">
<Button <button
size="lg" type="button"
className="btn-minimal rounded-full px-8 py-6 text-base font-medium group" className="btn btn-primary w-full sm:w-auto justify-center"
onClick={() => navigate("/kontakt")}
aria-label="Kostenlose Potenzialanalyse sichern"
> >
Kostenlose Potenzialanalyse sichern <ArrowRight className="btn-icon" size={24} strokeWidth={2} aria-hidden />
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" /> <div className="txt-wrapper">
</Button> <span className="txt-width-helper" aria-hidden="true">
</Link> <DemoButtonLetters text="Kostenlose Potenzialanalyse sichern" />
<Button </span>
size="lg" <div className="txt-1">
variant="outline" <DemoButtonLetters text="Kostenlose Potenzialanalyse sichern" />
className="btn-outline rounded-full px-8 py-6 text-base font-medium" </div>
> <div className="txt-2">
System-Demo anfordern <DemoButtonLetters text="Wird weitergeleitet..." />
</Button> </div>
</div>
</button>
</div>
<div className="btn-wrapper w-full sm:w-auto">
<button
ref={secondBtnRef}
type="button"
className="btn w-full sm:w-auto justify-center"
onClick={() => {
const el = document.getElementById("projects");
if (el) el.scrollIntoView({ behavior: "smooth" });
else navigate("/#projects");
}}
aria-label="Projekte ansehen"
>
{SPARKLE_SVG}
<div className="txt-wrapper">
<span className="txt-width-helper" aria-hidden="true">
<DemoButtonLetters text="Projekte ansehen" />
</span>
<div className="txt-1">
<DemoButtonLetters text="Projekte ansehen" />
</div>
<div className="txt-2">
<DemoButtonLetters text="Wird geladen..." />
</div>
</div>
</button>
</div>
</div> </div>
{/* Trust Line */} {/* Trust Line */}
@@ -97,7 +194,17 @@ const Hero = () => {
<div className="divider mb-12" /> <div className="divider mb-12" />
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12"> <div className="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12">
<div className="animate-fade-in" style={{ animationDelay: '0.6s' }}> <div className="animate-fade-in" style={{ animationDelay: '0.6s' }}>
<div className="stat-number text-4xl md:text-5xl text-foreground mb-2">10+</div> <div className="stat-number text-4xl md:text-5xl text-foreground mb-2">
<CountUp
from={0}
to={10}
direction="up"
duration={1.2}
className="count-up-text"
startWhen={true}
suffix="+"
/>
</div>
<div className="label-tag">Projekte</div> <div className="label-tag">Projekte</div>
</div> </div>
<div className="animate-fade-in" style={{ animationDelay: '0.7s' }}> <div className="animate-fade-in" style={{ animationDelay: '0.7s' }}>
@@ -105,7 +212,17 @@ const Hero = () => {
<div className="label-tag">Am Markt</div> <div className="label-tag">Am Markt</div>
</div> </div>
<div className="animate-fade-in" style={{ animationDelay: '0.8s' }}> <div className="animate-fade-in" style={{ animationDelay: '0.8s' }}>
<div className="stat-number text-4xl md:text-5xl text-foreground mb-2">99,9%</div> <div className="stat-number text-4xl md:text-5xl text-foreground mb-2">
<CountUp
from={0}
to={99.9}
direction="up"
duration={1.5}
className="count-up-text"
startWhen={true}
suffix="%"
/>
</div>
<div className="label-tag">Systemverfügbarkeit</div> <div className="label-tag">Systemverfügbarkeit</div>
</div> </div>
<div className="animate-fade-in" style={{ animationDelay: '0.9s' }}> <div className="animate-fade-in" style={{ animationDelay: '0.9s' }}>

View File

@@ -0,0 +1,16 @@
.light-rays-container {
width: 100%;
height: 100%;
position: relative;
pointer-events: none;
z-index: 3;
overflow: hidden;
}
.light-rays-fallback {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}

View File

@@ -0,0 +1,486 @@
"use client";
import { useRef, useEffect, useState } from "react";
// @ts-expect-error ogl has no type definitions
import { Renderer, Program, Triangle, Mesh } from "ogl";
import "./LightRays.css";
const DEFAULT_COLOR = "#ffffff";
const hexToRgb = (hex: string): [number, number, number] => {
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return m
? [
parseInt(m[1], 16) / 255,
parseInt(m[2], 16) / 255,
parseInt(m[3], 16) / 255,
]
: [1, 1, 1];
};
type RaysOrigin =
| "top-left"
| "top-right"
| "top-center"
| "left"
| "right"
| "bottom-left"
| "bottom-center"
| "bottom-right";
const getAnchorAndDir = (
origin: RaysOrigin,
w: number,
h: number
): { anchor: [number, number]; dir: [number, number] } => {
const outside = 0.2;
switch (origin) {
case "top-left":
return { anchor: [0, -outside * h], dir: [0, 1] };
case "top-right":
return { anchor: [w, -outside * h], dir: [0, 1] };
case "left":
return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] };
case "right":
return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] };
case "bottom-left":
return { anchor: [0, (1 + outside) * h], dir: [0, -1] };
case "bottom-center":
return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] };
case "bottom-right":
return { anchor: [w, (1 + outside) * h], dir: [0, -1] };
default:
return { anchor: [0.5 * w, -outside * h], dir: [0, 1] };
}
};
export interface LightRaysProps {
raysOrigin?: RaysOrigin;
raysColor?: string;
raysSpeed?: number;
lightSpread?: number;
rayLength?: number;
pulsating?: boolean;
fadeDistance?: number;
saturation?: number;
followMouse?: boolean;
mouseInfluence?: number;
noiseAmount?: number;
distortion?: number;
className?: string;
}
export default function LightRays({
raysOrigin = "top-center",
raysColor = DEFAULT_COLOR,
raysSpeed = 1,
lightSpread = 1,
rayLength = 2,
pulsating = false,
fadeDistance = 1.0,
saturation = 1.0,
followMouse = true,
mouseInfluence = 0.1,
noiseAmount = 0.0,
distortion = 0.0,
className = "",
}: LightRaysProps) {
const containerRef = useRef<HTMLDivElement>(null);
const uniformsRef = useRef<Record<string, { value: unknown }> | null>(null);
const rendererRef = useRef<InstanceType<typeof Renderer> | null>(null);
const mouseRef = useRef({ x: 0.5, y: 0.5 });
const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });
const animationIdRef = useRef<number | null>(null);
const meshRef = useRef<InstanceType<typeof Mesh> | null>(null);
const cleanupFunctionRef = useRef<(() => void) | null>(null);
const [isVisible, setIsVisible] = useState(false);
const [useFallback, setUseFallback] = useState(false);
const observerRef = useRef<IntersectionObserver | null>(null);
useEffect(() => {
if (!containerRef.current) return;
observerRef.current = new IntersectionObserver(
(entries) => {
const entry = entries[0];
setIsVisible(entry.isIntersecting);
},
{ threshold: 0.1 }
);
observerRef.current.observe(containerRef.current);
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = null;
}
};
}, []);
useEffect(() => {
if (!isVisible || !containerRef.current) return;
setUseFallback(false);
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
const initializeWebGL = async () => {
if (!containerRef.current) return;
await new Promise((resolve) => setTimeout(resolve, 10));
if (!containerRef.current) return;
const isMobile =
typeof window !== "undefined" &&
(window.innerWidth <= 768 || "ontouchstart" in window);
const dpr = isMobile ? 1 : Math.min(window.devicePixelRatio, 2);
try {
const renderer = new Renderer({
dpr,
alpha: true,
});
rendererRef.current = renderer;
const gl = renderer.gl;
gl.canvas.style.width = "100%";
gl.canvas.style.height = "100%";
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
containerRef.current.appendChild(gl.canvas);
const vert = `
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}`;
const frag = `precision mediump float;
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 rayPos;
uniform vec2 rayDir;
uniform vec3 raysColor;
uniform float raysSpeed;
uniform float lightSpread;
uniform float rayLength;
uniform float pulsating;
uniform float fadeDistance;
uniform float saturation;
uniform vec2 mousePos;
uniform float mouseInfluence;
uniform float noiseAmount;
uniform float distortion;
varying vec2 vUv;
float noise(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord,
float seedA, float seedB, float speed) {
vec2 sourceToCoord = coord - raySource;
vec2 dirNorm = normalize(sourceToCoord);
float cosAngle = dot(dirNorm, rayRefDirection);
float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2;
float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));
float distance = length(sourceToCoord);
float maxDistance = iResolution.x * rayLength;
float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);
float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0);
float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0;
float baseStrength = clamp(
(0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +
(0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),
0.0, 1.0
);
return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
vec2 finalRayDir = rayDir;
if (mouseInfluence > 0.0) {
vec2 mouseScreenPos = mousePos * iResolution.xy;
vec2 mouseDirection = normalize(mouseScreenPos - rayPos);
finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));
}
vec4 rays1 = vec4(1.0) *
rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349,
1.5 * raysSpeed);
vec4 rays2 = vec4(1.0) *
rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234,
1.1 * raysSpeed);
fragColor = rays1 * 0.5 + rays2 * 0.4;
if (noiseAmount > 0.0) {
float n = noise(coord * 0.01 + iTime * 0.1);
fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n);
}
float brightness = 1.0 - (coord.y / iResolution.y);
fragColor.x *= 0.1 + brightness * 0.8;
fragColor.y *= 0.3 + brightness * 0.6;
fragColor.z *= 0.5 + brightness * 0.5;
if (saturation != 1.0) {
float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation);
}
fragColor.rgb *= raysColor;
}
void main() {
vec4 color;
mainImage(color, gl_FragCoord.xy);
gl_FragColor = color;
}`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: [1, 1] as [number, number] },
rayPos: { value: [0, 0] as [number, number] },
rayDir: { value: [0, 1] as [number, number] },
raysColor: { value: hexToRgb(raysColor) },
raysSpeed: { value: raysSpeed },
lightSpread: { value: lightSpread },
rayLength: { value: rayLength },
pulsating: { value: pulsating ? 1.0 : 0.0 },
fadeDistance: { value: fadeDistance },
saturation: { value: saturation },
mousePos: { value: [0.5, 0.5] as [number, number] },
mouseInfluence: { value: mouseInfluence },
noiseAmount: { value: noiseAmount },
distortion: { value: distortion },
};
uniformsRef.current = uniforms as Record<string, { value: unknown }>;
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vert,
fragment: frag,
uniforms,
});
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const updatePlacement = () => {
if (!containerRef.current || !renderer) return;
renderer.dpr = isMobile ? 1 : Math.min(window.devicePixelRatio, 2);
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
renderer.setSize(wCSS, hCSS);
const dpr = renderer.dpr;
const w = wCSS * dpr;
const h = hCSS * dpr;
uniforms.iResolution.value = [w, h];
const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h);
uniforms.rayPos.value = anchor;
uniforms.rayDir.value = dir;
};
const loop = (t: number) => {
if (!rendererRef.current || !uniformsRef.current || !meshRef.current) {
return;
}
const uniforms = uniformsRef.current as typeof uniforms;
uniforms.iTime.value = t * 0.001;
if (followMouse && mouseInfluence > 0.0) {
const smoothing = 0.92;
smoothMouseRef.current.x =
smoothMouseRef.current.x * smoothing +
mouseRef.current.x * (1 - smoothing);
smoothMouseRef.current.y =
smoothMouseRef.current.y * smoothing +
mouseRef.current.y * (1 - smoothing);
uniforms.mousePos.value = [
smoothMouseRef.current.x,
smoothMouseRef.current.y,
];
}
try {
renderer.render({ scene: mesh });
animationIdRef.current = requestAnimationFrame(loop);
} catch (error) {
console.warn("WebGL rendering error:", error);
return;
}
};
window.addEventListener("resize", updatePlacement);
const resizeObserver =
typeof ResizeObserver !== "undefined" &&
new ResizeObserver(() => updatePlacement());
if (resizeObserver && containerRef.current) {
resizeObserver.observe(containerRef.current);
}
updatePlacement();
animationIdRef.current = requestAnimationFrame(loop);
cleanupFunctionRef.current = () => {
if (resizeObserver && containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
if (animationIdRef.current) {
cancelAnimationFrame(animationIdRef.current);
animationIdRef.current = null;
}
window.removeEventListener("resize", updatePlacement);
if (renderer) {
try {
const canvas = renderer.gl.canvas;
const loseContextExt =
renderer.gl.getExtension("WEBGL_lose_context");
if (loseContextExt) {
loseContextExt.loseContext();
}
if (canvas && canvas.parentNode) {
canvas.parentNode.removeChild(canvas);
}
} catch (error) {
console.warn("Error during WebGL cleanup:", error);
}
}
rendererRef.current = null;
uniformsRef.current = null;
meshRef.current = null;
};
} catch (error) {
console.warn("LightRays WebGL init failed (e.g. on mobile):", error);
setUseFallback(true);
}
};
initializeWebGL();
return () => {
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
};
}, [
isVisible,
raysOrigin,
raysColor,
raysSpeed,
lightSpread,
rayLength,
pulsating,
fadeDistance,
saturation,
followMouse,
mouseInfluence,
noiseAmount,
distortion,
]);
useEffect(() => {
if (!uniformsRef.current || !containerRef.current || !rendererRef.current)
return;
const u = uniformsRef.current as Record<string, { value: unknown }>;
const renderer = rendererRef.current;
u.raysColor.value = hexToRgb(raysColor);
u.raysSpeed.value = raysSpeed;
u.lightSpread.value = lightSpread;
u.rayLength.value = rayLength;
u.pulsating.value = pulsating ? 1.0 : 0.0;
u.fadeDistance.value = fadeDistance;
u.saturation.value = saturation;
u.mouseInfluence.value = mouseInfluence;
u.noiseAmount.value = noiseAmount;
u.distortion.value = distortion;
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
const dpr = renderer.dpr;
const { anchor, dir } = getAnchorAndDir(
raysOrigin,
wCSS * dpr,
hCSS * dpr
);
u.rayPos.value = anchor;
u.rayDir.value = dir;
}, [
raysColor,
raysSpeed,
lightSpread,
raysOrigin,
rayLength,
pulsating,
fadeDistance,
saturation,
mouseInfluence,
noiseAmount,
distortion,
]);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!containerRef.current || !rendererRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
mouseRef.current = { x, y };
};
if (followMouse) {
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}
}, [followMouse]);
return (
<div
ref={containerRef}
className={`light-rays-container ${className}`.trim()}
>
{useFallback && (
<div
className="light-rays-fallback"
style={{
background: `linear-gradient(to bottom, ${raysColor}50 0%, ${raysColor}20 25%, ${raysColor}08 50%, transparent 85%)`,
}}
aria-hidden
/>
)}
</div>
);
}

39
src/components/Logo.tsx Normal file
View File

@@ -0,0 +1,39 @@
interface LogoProps {
className?: string;
width?: number;
height?: number;
}
const Logo = ({ className, width = 30, height = 30 }: LogoProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 512 512"
className={className}
aria-hidden
>
{/* rounded square background */}
<rect x="48" y="48" width="416" height="416" rx="92" fill="#FFFFFF" />
{/* tilted oval */}
<ellipse cx="256" cy="256" rx="210" ry="150" fill="#111111" transform="rotate(-28 256 256)" />
{/* small dot */}
<circle cx="356" cy="172" r="18" fill="#FFFFFF" />
{/* enlarged sparkle star */}
<g transform="translate(256 256) rotate(-28)">
<path
d="
M 0,-170
C 22,-105 60,-72 120,-56
C 60,-40 22,-8 0,64
C -22,-8 -60,-40 -120,-56
C -60,-72 -22,-105 0,-170
Z
"
fill="#FFFFFF"
/>
</g>
</svg>
);
export default Logo;

View File

@@ -1,4 +1,6 @@
import { Calendar, MessageSquareOff, TrendingDown, Folders } from "lucide-react"; import { Calendar, MessageSquareOff, TrendingDown, Folders } from "lucide-react";
import { LampTop } from "@/components/ui/lamp";
import LightRays from "@/components/LightRays";
const ProblemSection = () => { const ProblemSection = () => {
const problems = [ const problems = [
@@ -21,8 +23,25 @@ const ProblemSection = () => {
]; ];
return ( return (
<section className="py-24 md:py-32 bg-background relative"> <section className="section-problem-solution py-24 md:py-32 relative overflow-hidden">
<div className="container mx-auto px-6"> <div className="absolute inset-0 w-full overflow-hidden z-0">
<LightRays
raysOrigin="top-center"
raysColor="#ef4444"
raysSpeed={1}
lightSpread={0.5}
rayLength={3}
followMouse={false}
mouseInfluence={0}
noiseAmount={0}
distortion={0}
pulsating
fadeDistance={2}
saturation={2}
/>
</div>
<LampTop />
<div className="container mx-auto px-6 relative z-10">
{/* Section Header */} {/* Section Header */}
<div className="mb-16 md:mb-20 max-w-4xl"> <div className="mb-16 md:mb-20 max-w-4xl">
<div className="label-tag mb-4">Das Problem</div> <div className="label-tag mb-4">Das Problem</div>
@@ -39,7 +58,7 @@ const ProblemSection = () => {
{problems.map((problem, index) => ( {problems.map((problem, index) => (
<div <div
key={index} key={index}
className="flex items-start gap-4 p-6 border border-border rounded-lg bg-card/50 hover:border-foreground/20 transition-colors" className="problem-section-tint flex items-start gap-4 p-6 border border-border rounded-lg bg-card/50 hover:border-foreground/20 transition-colors"
> >
<div className="w-10 h-10 rounded-full border border-destructive/30 bg-destructive/10 flex items-center justify-center flex-shrink-0"> <div className="w-10 h-10 rounded-full border border-destructive/30 bg-destructive/10 flex items-center justify-center flex-shrink-0">
<problem.icon className="w-5 h-5 text-destructive" /> <problem.icon className="w-5 h-5 text-destructive" />

View File

@@ -1,22 +1,24 @@
import CountUp from "@/components/CountUp";
const Process = () => { const Process = () => {
const steps = [ const steps = [
{ {
number: "01", number: 1,
title: "Erstgespräch", title: "Erstgespräch",
description: "Wir lernen Ihr Unternehmen und Ihre Ziele kennen. In einem unverbindlichen Gespräch besprechen wir Ihre Wünsche.", description: "Wir lernen Ihr Unternehmen und Ihre Ziele kennen. In einem unverbindlichen Gespräch besprechen wir Ihre Wünsche.",
}, },
{ {
number: "02", number: 2,
title: "Konzept & Design", title: "Konzept & Design",
description: "Basierend auf unserer Analyse erstellen wir ein individuelles Konzept und Design für Ihre Website.", description: "Basierend auf unserer Analyse erstellen wir ein individuelles Konzept und Design für Ihre Website.",
}, },
{ {
number: "03", number: 3,
title: "Entwicklung", title: "Entwicklung",
description: "Unsere Entwickler setzen Ihre Website mit modernsten Technologien um. Sie bleiben informiert.", description: "Unsere Entwickler setzen Ihre Website mit modernsten Technologien um. Sie bleiben informiert.",
}, },
{ {
number: "04", number: 4,
title: "Launch & Support", title: "Launch & Support",
description: "Nach gründlichen Tests geht Ihre Website live. Wir stehen Ihnen auch danach mit Support zur Seite.", description: "Nach gründlichen Tests geht Ihre Website live. Wir stehen Ihnen auch danach mit Support zur Seite.",
}, },
@@ -45,13 +47,22 @@ const Process = () => {
{/* Number */} {/* Number */}
<div className="hidden md:block"> <div className="hidden md:block">
<span className="stat-number text-6xl text-border group-hover:text-muted-foreground/30 transition-colors"> <span className="stat-number text-6xl text-border group-hover:text-muted-foreground/30 transition-colors">
{step.number} <CountUp
from={0}
to={step.number}
direction="up"
duration={1}
padMinLength={2}
startWhen={true}
/>
</span> </span>
</div> </div>
{/* Content */} {/* Content */}
<div className="flex-1"> <div className="flex-1">
<span className="md:hidden label-tag mb-2 block">{step.number}</span> <span className="md:hidden label-tag mb-2 block">
<CountUp from={0} to={step.number} duration={1} padMinLength={2} startWhen={true} />
</span>
<h3 className="text-2xl md:text-3xl font-display font-medium text-foreground mb-4 uppercase tracking-tight"> <h3 className="text-2xl md:text-3xl font-display font-medium text-foreground mb-4 uppercase tracking-tight">
{step.title} {step.title}
</h3> </h3>

View File

@@ -1,30 +1,42 @@
import { ArrowUpRight } from "lucide-react"; import { ArrowUpRight } from "lucide-react";
const projects = [ type Project = {
title: string;
description: string;
image: string;
url: string;
};
const projects: Project[] = [
{ {
title: "Triple AI", title: "Email Sorter",
description: "Webentwicklung / UI Design / Custom Code", description: "E-Mails automatisch sortieren",
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&h=600&fit=crop", image: "/project%20pics/emailsorter.png",
url: "https://emailsorter.webklar.com/",
}, },
{ {
title: "Neutral", title: "Neutral",
description: "Webentwicklung / Custom Code", description: "Webentwicklung / Custom Code",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&h=600&fit=crop",
url: "#",
}, },
{ {
title: "Verbatim Labs", title: "Verbatim Labs",
description: "Webentwicklung / UI Design / Custom Code", description: "Webentwicklung / UI Design / Custom Code",
image: "https://images.unsplash.com/photo-1559028012-481c04fa702d?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1559028012-481c04fa702d?w=800&h=600&fit=crop",
url: "#",
}, },
{ {
title: "JMK Engineers", title: "JMK Engineers",
description: "Webentwicklung / UI Design / Custom Code", description: "Webentwicklung / UI Design / Custom Code",
image: "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?w=800&h=600&fit=crop",
url: "#",
}, },
{ {
title: "GOODZ Club", title: "GOODZ Club",
description: "Webentwicklung / Custom Code / Lokalisierung", description: "Webentwicklung / Custom Code / Lokalisierung",
image: "https://images.unsplash.com/photo-1542744094-3a31f272c490?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1542744094-3a31f272c490?w=800&h=600&fit=crop",
url: "#",
}, },
]; ];
@@ -45,7 +57,9 @@ const ProjectShowcase = () => {
{projects.map((project, index) => ( {projects.map((project, index) => (
<a <a
key={project.title} key={project.title}
href="#" href={project.url}
target={project.url.startsWith("http") ? "_blank" : undefined}
rel={project.url.startsWith("http") ? "noopener noreferrer" : undefined}
className="group block project-card rounded-lg p-6 md:p-8" className="group block project-card rounded-lg p-6 md:p-8"
style={{ animationDelay: `${index * 0.1}s` }} style={{ animationDelay: `${index * 0.1}s` }}
> >

View File

@@ -1,6 +1,8 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ArrowRight, CheckCircle2 } from "lucide-react"; import { ArrowRight, CheckCircle2 } from "lucide-react";
import { LampTop } from "@/components/ui/lamp";
import LightRays from "@/components/LightRays";
const SolutionSection = () => { const SolutionSection = () => {
const benefits = [ const benefits = [
@@ -10,8 +12,25 @@ const SolutionSection = () => {
]; ];
return ( return (
<section className="py-24 md:py-32 bg-background relative"> <section className="section-problem-solution py-24 md:py-32 relative overflow-hidden">
<div className="container mx-auto px-6"> <div className="absolute inset-0 w-full overflow-hidden z-0">
<LightRays
raysOrigin="top-center"
raysColor="#22d3ee"
raysSpeed={1}
lightSpread={0.5}
rayLength={3}
followMouse={false}
mouseInfluence={0}
noiseAmount={0}
distortion={0}
pulsating
fadeDistance={2}
saturation={2}
/>
</div>
<LampTop lineClassName="bg-cyan-400" />
<div className="container mx-auto px-6 relative z-10">
<div className="grid lg:grid-cols-2 gap-16 items-center"> <div className="grid lg:grid-cols-2 gap-16 items-center">
{/* Left Content */} {/* Left Content */}
<div> <div>
@@ -47,7 +66,7 @@ const SolutionSection = () => {
{/* Right Content - Visual Element */} {/* Right Content - Visual Element */}
<div className="relative"> <div className="relative">
<div className="aspect-square bg-secondary/50 rounded-2xl border border-border p-8 md:p-12 flex flex-col justify-center"> <div className="solution-section-tint aspect-square bg-secondary/50 rounded-2xl border border-border p-8 md:p-12 flex flex-col justify-center">
<div className="space-y-6"> <div className="space-y-6">
<div className="text-sm uppercase tracking-wider text-muted-foreground">Das Ergebnis</div> <div className="text-sm uppercase tracking-wider text-muted-foreground">Das Ergebnis</div>
<h3 className="text-2xl md:text-3xl font-display font-medium text-foreground uppercase tracking-tight"> <h3 className="text-2xl md:text-3xl font-display font-medium text-foreground uppercase tracking-tight">

View File

@@ -35,8 +35,17 @@ const Values = () => {
]; ];
return ( return (
<section id="features" className="py-24 md:py-32 bg-background relative"> <section id="features" className="py-24 md:py-32 bg-background relative overflow-hidden">
<div className="container mx-auto px-6"> {/* Hintergrundbild: kleiner, leicht transparent */}
<div
className="absolute inset-0 bg-right bg-no-repeat opacity-[0.3]"
style={{
backgroundImage: "url(/backgroud_effect.png)",
backgroundSize: "45%",
}}
aria-hidden
/>
<div className="container mx-auto px-6 relative z-10">
{/* Section Header */} {/* Section Header */}
<div className="mb-16 md:mb-24 max-w-3xl"> <div className="mb-16 md:mb-24 max-w-3xl">
<div className="label-tag mb-4">Was Sie bekommen</div> <div className="label-tag mb-4">Was Sie bekommen</div>

View File

@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", default: "btn-primary-style rounded-full bg-[hsl(198,93%,42%)] text-white hover:bg-[hsl(198,93%,48%)] border border-white/20",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",

View File

@@ -0,0 +1,39 @@
"use client";
import React from "react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
const S = 2.5;
export const LampTop = ({
className,
lineClassName = "bg-red-500",
children,
}: {
className?: string;
lineClassName?: string;
children?: React.ReactNode;
}) => {
return (
<div
className={cn(
"absolute top-0 left-0 right-0 w-full min-h-0 pointer-events-none z-50 flex items-start justify-center",
className
)}
>
<motion.div
initial={{ width: `${15 * S}rem` }}
whileInView={{ width: `${30 * S}rem` }}
transition={{
delay: 0.3,
duration: 0.8,
ease: "easeInOut",
}}
className={cn("absolute top-0 left-1/2 -translate-x-1/2 h-0.5", lineClassName)}
style={{ width: `${30 * S}rem` }}
/>
{children}
</div>
);
};

View File

@@ -290,13 +290,13 @@ export const NavbarButton = ({
...props ...props
}: NavbarButtonProps) => { }: NavbarButtonProps) => {
const baseStyles = const baseStyles =
"px-4 py-2 rounded-md bg-white text-black text-sm font-bold relative cursor-pointer hover:-translate-y-0.5 transition duration-200 inline-block text-center"; "px-4 py-2 rounded-full text-sm font-bold relative cursor-pointer transition duration-200 inline-block text-center";
const variantStyles = { const variantStyles = {
primary: primary:
"shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]", "bg-[hsl(198,93%,42%)] text-white border border-white/20 shadow-[inset_0_1px_1px_rgba(255,255,255,0.25),inset_0_2px_2px_rgba(255,255,255,0.2),0_2px_4px_rgba(0,0,0,0.2),0_4px_8px_rgba(0,0,0,0.15)] hover:border-white/40 hover:shadow-[inset_0_1px_1px_rgba(255,255,255,0.3),inset_0_2px_2px_rgba(255,255,255,0.25),0_2px_4px_rgba(0,0,0,0.2),0_4px_8px_rgba(0,0,0,0.15)]",
secondary: "bg-transparent shadow-none dark:text-white", secondary: "bg-transparent shadow-none dark:text-white",
dark: "bg-black text-white shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]", dark: "btn !text-white",
gradient: gradient:
"bg-gradient-to-b from-blue-500 to-blue-700 text-white shadow-[0px_2px_0px_0px_rgba(255,255,255,0.3)_inset]", "bg-gradient-to-b from-blue-500 to-blue-700 text-white shadow-[0px_2px_0px_0px_rgba(255,255,255,0.3)_inset]",
}; };

View File

@@ -1,19 +1,17 @@
/* Fonts first @import must be at the very top */
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap");
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
/* webklar Design System - Muradov Inspired Minimal Dark Theme */ /* webklar Design System - Muradov Inspired Minimal Dark Theme */
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap");
@layer base { @layer base {
:root { :root {
/* Ultra Minimal Deep Black Theme - Muradov Inspired */ /* Ultra Minimal Deep Black Theme - Muradov Inspired */
@@ -156,6 +154,37 @@
} }
@layer components { @layer components {
/* Gemeinsamer Hintergrund für Problem- und Lösungs-Sektion */
.section-problem-solution {
background-color: hsl(var(--background));
}
/* Leichter roter Tint auf Inhaltsblöcken der Problem-Sektion */
.problem-section-tint {
position: relative;
}
.problem-section-tint::before {
content: "";
position: absolute;
inset: 0;
background-color: rgb(239 68 68 / 0.06);
pointer-events: none;
border-radius: inherit;
}
/* Leichter blauer Tint auf dem Ergebnis-Block der Lösungs-Sektion */
.solution-section-tint {
position: relative;
}
.solution-section-tint::before {
content: "";
position: absolute;
inset: 0;
background-color: rgb(34 211 238 / 0.06);
pointer-events: none;
border-radius: inherit;
}
/* Minimal glass nav */ /* Minimal glass nav */
.glass-nav { .glass-nav {
@apply backdrop-blur-xl border-b; @apply backdrop-blur-xl border-b;
@@ -241,6 +270,461 @@
background: hsl(0 0% 10%); background: hsl(0 0% 10%);
} }
/* Custom CTA button (System-Demo) */
.btn-wrapper {
position: relative;
display: inline-block;
width: fit-content;
flex-shrink: 0;
}
.btn {
--border-radius: 24px;
--padding: 4px;
--transition: 0.35s cubic-bezier(0.4, 0, 0.2, 1);
--button-color: #101010;
--highlight-color-hue: 210deg;
outline: none;
user-select: none;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5em 1.1em;
font-family: "Poppins", "Inter", "Segoe UI", sans-serif;
font-size: 1em;
font-weight: 400;
background-color: var(--button-color);
overflow: hidden;
box-shadow:
inset 0px 1px 1px rgba(255, 255, 255, 0.2),
inset 0px 2px 2px rgba(255, 255, 255, 0.15),
inset 0px 4px 4px rgba(255, 255, 255, 0.1),
inset 0px 8px 8px rgba(255, 255, 255, 0.05),
inset 0px 16px 16px rgba(255, 255, 255, 0.05),
0px -1px 1px rgba(0, 0, 0, 0.02),
0px -2px 2px rgba(0, 0, 0, 0.03),
0px -4px 4px rgba(0, 0, 0, 0.05),
0px -8px 8px rgba(0, 0, 0, 0.06),
0px -16px 16px rgba(0, 0, 0, 0.08);
border: solid 1px rgba(255, 255, 255, 0.13);
border-radius: var(--border-radius);
cursor: pointer;
transition: box-shadow var(--transition), border var(--transition), background-color var(--transition);
position: relative;
}
.btn::before {
content: "";
position: absolute;
top: calc(0px - var(--padding));
left: calc(0px - var(--padding));
width: calc(100% + var(--padding) * 2);
height: calc(100% + var(--padding) * 2);
border-radius: calc(var(--border-radius) + var(--padding));
pointer-events: none;
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.27), rgba(0, 0, 0, 0.4));
z-index: -1;
transition: box-shadow var(--transition), filter var(--transition);
box-shadow:
0 -8px 8px -6px transparent inset,
0 -16px 16px -8px transparent inset,
1px 1px 1px rgba(255, 255, 255, 0.13),
2px 2px 2px rgba(255, 255, 255, 0.07),
-1px -1px 1px rgba(0, 0, 0, 0.13),
-2px -2px 2px rgba(0, 0, 0, 0.07);
}
.btn::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
pointer-events: none;
background-image: linear-gradient(
0deg,
#fff,
hsl(var(--highlight-color-hue), 100%, 70%),
hsla(var(--highlight-color-hue), 100%, 70%, 0.5),
8%,
transparent
);
background-position: 0 0;
opacity: 0;
transition: opacity var(--transition), filter var(--transition);
}
.btn-letter {
position: relative;
display: inline-block;
color: rgba(255, 255, 255, 0.33);
animation: letter-anim 2.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
transition: color var(--transition), text-shadow var(--transition), opacity var(--transition), transform var(--transition);
}
@keyframes letter-anim {
50% {
text-shadow: 0 0 3px rgba(255, 255, 255, 0.53);
color: #fff;
}
}
.btn-svg {
flex-shrink: 0;
height: 24px;
width: 24px;
fill: #e8e8e8;
animation: flicker 2.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
animation-delay: 0.5s;
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.6));
transition: fill var(--transition), filter var(--transition), opacity var(--transition);
}
@keyframes flicker {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
.txt-wrapper {
position: relative;
display: flex;
align-items: center;
flex-shrink: 0;
overflow: hidden;
min-width: 0;
padding-right: 6px;
}
.txt-width-helper {
visibility: hidden;
white-space: nowrap;
font: inherit;
word-spacing: -1em;
pointer-events: none;
}
.txt-width-helper .btn-letter {
animation: none;
}
.btn-letter-space {
min-width: 0.25em;
}
.txt-1,
.txt-2 {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
word-spacing: -1em;
white-space: nowrap;
max-width: 100%;
overflow: hidden;
}
.txt-1 {
animation: appear-anim 1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.txt-2 {
opacity: 0;
}
@keyframes appear-anim {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.btn:focus .txt-1 {
animation: opacity-anim 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
animation-delay: 0.8s;
}
.btn:focus .txt-2 {
animation: opacity-anim 0.4s cubic-bezier(0.4, 0, 0.2, 1) reverse forwards;
animation-delay: 0.8s;
}
@keyframes opacity-anim {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.btn:focus .btn-letter {
animation: focused-letter-anim 1.2s cubic-bezier(0.4, 0, 0.2, 1) forwards, letter-anim 1.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
animation-delay: 0s, 1s;
}
@keyframes focused-letter-anim {
0%,
100% {
transform: scale(1);
filter: blur(0px);
}
50% {
transform: scale(1.08);
filter: blur(2px) brightness(150%)
drop-shadow(0 0 8px hsl(var(--highlight-color-hue), 100%, 70%));
}
}
.btn:focus .btn-svg {
animation-duration: 1.2s;
animation-delay: 0.2s;
}
.btn:focus::before {
box-shadow:
0 -8px 12px -6px rgba(255, 255, 255, 0.2) inset,
0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 0.2) inset,
1px 1px 1px rgba(255, 255, 255, 0.2),
2px 2px 2px rgba(255, 255, 255, 0.07),
-1px -1px 1px rgba(0, 0, 0, 0.13),
-2px -2px 2px rgba(0, 0, 0, 0.07);
}
.btn:focus::after {
opacity: 0.6;
mask-image: linear-gradient(0deg, #fff, transparent);
filter: brightness(100%);
}
.btn-letter:nth-child(1),
.btn:focus .btn-letter:nth-child(1) { animation-delay: 0s; }
.btn-letter:nth-child(2),
.btn:focus .btn-letter:nth-child(2) { animation-delay: 0.08s; }
.btn-letter:nth-child(3),
.btn:focus .btn-letter:nth-child(3) { animation-delay: 0.16s; }
.btn-letter:nth-child(4),
.btn:focus .btn-letter:nth-child(4) { animation-delay: 0.24s; }
.btn-letter:nth-child(5),
.btn:focus .btn-letter:nth-child(5) { animation-delay: 0.32s; }
.btn-letter:nth-child(6),
.btn:focus .btn-letter:nth-child(6) { animation-delay: 0.4s; }
.btn-letter:nth-child(7),
.btn:focus .btn-letter:nth-child(7) { animation-delay: 0.48s; }
.btn-letter:nth-child(8),
.btn:focus .btn-letter:nth-child(8) { animation-delay: 0.56s; }
.btn-letter:nth-child(9),
.btn:focus .btn-letter:nth-child(9) { animation-delay: 0.64s; }
.btn-letter:nth-child(10),
.btn:focus .btn-letter:nth-child(10) { animation-delay: 0.72s; }
.btn-letter:nth-child(11),
.btn:focus .btn-letter:nth-child(11) { animation-delay: 0.8s; }
.btn-letter:nth-child(12),
.btn:focus .btn-letter:nth-child(12) { animation-delay: 0.88s; }
.btn-letter:nth-child(13),
.btn:focus .btn-letter:nth-child(13) { animation-delay: 0.96s; }
.btn-letter:nth-child(14),
.btn:focus .btn-letter:nth-child(14) { animation-delay: 1.04s; }
.btn-letter:nth-child(15),
.btn:focus .btn-letter:nth-child(15) { animation-delay: 1.12s; }
.btn-letter:nth-child(16),
.btn:focus .btn-letter:nth-child(16) { animation-delay: 1.2s; }
.btn-letter:nth-child(17),
.btn:focus .btn-letter:nth-child(17) { animation-delay: 1.28s; }
.btn-letter:nth-child(18),
.btn:focus .btn-letter:nth-child(18) { animation-delay: 1.36s; }
.btn-letter:nth-child(19),
.btn:focus .btn-letter:nth-child(19) { animation-delay: 1.44s; }
.btn-letter:nth-child(20),
.btn:focus .btn-letter:nth-child(20) { animation-delay: 1.52s; }
.btn-letter:nth-child(21),
.btn:focus .btn-letter:nth-child(21) { animation-delay: 1.6s; }
.btn-letter:nth-child(22),
.btn:focus .btn-letter:nth-child(22) { animation-delay: 1.68s; }
.btn-letter:nth-child(23),
.btn:focus .btn-letter:nth-child(23) { animation-delay: 1.76s; }
.btn-letter:nth-child(24),
.btn:focus .btn-letter:nth-child(24) { animation-delay: 1.84s; }
.btn-letter:nth-child(25),
.btn:focus .btn-letter:nth-child(25) { animation-delay: 1.92s; }
.btn-letter:nth-child(26),
.btn:focus .btn-letter:nth-child(26) { animation-delay: 2s; }
.btn-letter:nth-child(27),
.btn:focus .btn-letter:nth-child(27) { animation-delay: 2.08s; }
.btn-letter:nth-child(28),
.btn:focus .btn-letter:nth-child(28) { animation-delay: 2.16s; }
.btn-letter:nth-child(29),
.btn:focus .btn-letter:nth-child(29) { animation-delay: 2.24s; }
.btn-letter:nth-child(30),
.btn:focus .btn-letter:nth-child(30) { animation-delay: 2.32s; }
.btn-letter:nth-child(31),
.btn:focus .btn-letter:nth-child(31) { animation-delay: 2.4s; }
.btn-letter:nth-child(32),
.btn:focus .btn-letter:nth-child(32) { animation-delay: 2.48s; }
.btn-letter:nth-child(33),
.btn:focus .btn-letter:nth-child(33) { animation-delay: 2.56s; }
.btn-letter:nth-child(34),
.btn:focus .btn-letter:nth-child(34) { animation-delay: 2.64s; }
.btn-letter:nth-child(35),
.btn:focus .btn-letter:nth-child(35) { animation-delay: 2.72s; }
.btn:active {
border: solid 1px hsla(var(--highlight-color-hue), 100%, 80%, 0.7);
background-color: hsla(var(--highlight-color-hue), 50%, 20%, 0.5);
}
.btn:active::before {
box-shadow:
0 -8px 12px -6px rgba(255, 255, 255, 0.67) inset,
0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 0.8) inset,
1px 1px 1px rgba(255, 255, 255, 0.27),
2px 2px 2px rgba(255, 255, 255, 0.13),
-1px -1px 1px rgba(0, 0, 0, 0.13),
-2px -2px 2px rgba(0, 0, 0, 0.07);
}
.btn:active::after {
opacity: 1;
mask-image: linear-gradient(0deg, #fff, transparent);
filter: brightness(200%);
}
.btn:active .btn-letter {
text-shadow: 0 0 1px hsla(var(--highlight-color-hue), 100%, 90%, 0.9);
animation: none;
}
.btn:hover {
border: solid 1px hsla(var(--highlight-color-hue), 100%, 80%, 0.4);
}
.btn:hover::before {
box-shadow:
0 -8px 8px -6px rgba(255, 255, 255, 0.67) inset,
0 -16px 16px -8px hsla(var(--highlight-color-hue), 100%, 70%, 0.3) inset,
1px 1px 1px rgba(255, 255, 255, 0.13),
2px 2px 2px rgba(255, 255, 255, 0.07),
-1px -1px 1px rgba(0, 0, 0, 0.13),
-2px -2px 2px rgba(0, 0, 0, 0.07);
}
.btn:hover::after {
opacity: 1;
mask-image: linear-gradient(0deg, #fff, transparent);
}
.btn:hover .btn-svg {
fill: #fff;
filter: drop-shadow(0 0 3px hsl(var(--highlight-color-hue), 100%, 70%))
drop-shadow(0 -4px 6px rgba(0, 0, 0, 0.6));
animation: none;
}
@media (max-width: 639px) {
.btn {
font-size: 0.9rem;
padding: 0.5em 0.9em;
gap: 0.4rem;
}
.btn-svg,
.btn-icon {
width: 20px;
height: 20px;
}
}
/* Shared primary button look for all blue/primary buttons */
.btn-primary-style {
padding: 0.5em 1.1em;
border-radius: 24px;
box-shadow:
inset 0px 1px 1px rgba(255, 255, 255, 0.25),
inset 0px 2px 2px rgba(255, 255, 255, 0.2),
0px 2px 4px rgba(0, 0, 0, 0.2),
0px 4px 8px rgba(0, 0, 0, 0.15);
transition: box-shadow 0.35s cubic-bezier(0.4, 0, 0.2, 1),
border 0.35s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-primary-style:hover {
border-color: rgba(255, 255, 255, 0.4);
box-shadow:
inset 0px 1px 1px rgba(255, 255, 255, 0.3),
inset 0px 2px 2px rgba(255, 255, 255, 0.25),
0px 2px 4px rgba(0, 0, 0, 0.2),
0px 4px 8px rgba(0, 0, 0, 0.15);
}
/* Primary (blue) variant same structure, blue color */
.btn-primary {
--button-color: hsl(198, 93%, 42%);
--highlight-color-hue: 198deg;
box-shadow:
inset 0px 1px 1px rgba(255, 255, 255, 0.25),
inset 0px 2px 2px rgba(255, 255, 255, 0.2),
0px 2px 4px rgba(0, 0, 0, 0.2),
0px 4px 8px rgba(0, 0, 0, 0.15);
border: solid 1px rgba(255, 255, 255, 0.2);
}
.btn-primary::before {
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.25));
}
.btn-primary .btn-letter {
color: rgba(255, 255, 255, 0.75);
}
@keyframes letter-anim-primary {
0%, 100% {
text-shadow: none;
color: rgba(255, 255, 255, 0.75);
}
50% {
text-shadow: 0 0 6px rgba(255, 255, 255, 0.95), 0 0 12px rgba(255, 255, 255, 0.4);
color: #fff;
}
}
.btn-primary .btn-letter {
animation: letter-anim-primary 2.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
/* Arrow icon (stroke-based) same size and animation as .btn-svg */
.btn-icon {
flex-shrink: 0;
width: 24px;
height: 24px;
color: #e8e8e8;
animation: flicker 2.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
animation-delay: 0.5s;
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.6));
transition: color var(--transition), filter var(--transition), opacity var(--transition);
}
.btn:hover .btn-icon {
color: #fff;
filter: drop-shadow(0 0 3px hsl(var(--highlight-color-hue), 100%, 70%))
drop-shadow(0 -4px 6px rgba(0, 0, 0, 0.6));
animation: none;
}
.btn:focus .btn-icon {
animation-duration: 1.2s;
animation-delay: 0.2s;
}
/* Stats number */ /* Stats number */
.stat-number { .stat-number {
font-family: 'Space Grotesk', system-ui, sans-serif; font-family: 'Space Grotesk', system-ui, sans-serif;
@@ -249,6 +733,23 @@
line-height: 1; line-height: 1;
} }
/* Count-up with gradient */
.count-up-text {
background: linear-gradient(135deg, hsl(0 0% 92%) 0%, hsl(0 0% 70%) 50%, hsl(0 0% 92%) 100%);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dark .count-up-text {
background: linear-gradient(135deg, hsl(0 0% 98%) 0%, hsl(0 0% 75%) 50%, hsl(0 0% 98%) 100%);
background-size: 200% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Grid line decoration */ /* Grid line decoration */
.grid-lines { .grid-lines {
background-image: background-image:

292
src/pages/AGB.tsx Normal file
View File

@@ -0,0 +1,292 @@
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowLeft, FileText } from "lucide-react";
const contractDivider = (
<div className="my-8 border-t border-border" aria-hidden />
);
const AGB = () => {
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 glass-nav py-4">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">
Webklar
</span>
</Link>
<Link to="/">
<Button
variant="ghost"
className="text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Zurück
</Button>
</Link>
</div>
</div>
</header>
{/* Main Content */}
<main className="pt-32 pb-24">
<div className="container mx-auto px-6">
<div className="max-w-3xl mx-auto">
{/* Page Header */}
<div className="mb-12">
<div className="label-tag mb-4 flex items-center gap-2">
<FileText className="w-4 h-4" />
Vertrag
</div>
<h1 className="text-4xl md:text-5xl font-display font-medium text-foreground tracking-tight uppercase mb-2">
Kaufvertrag WEBklar
</h1>
<p className="text-muted-foreground text-lg mb-8">
(Modularer Projektvertrag)
</p>
<div className="text-muted-foreground space-y-1">
<p><strong className="text-foreground">zwischen</strong></p>
<p>WEBklar<br /><span className="text-sm">(im Folgenden Anbieter)</span></p>
<p className="pt-2"><strong className="text-foreground">und</strong></p>
<p>Kunde laut Angebot<br /><span className="text-sm">(im Folgenden Kunde)</span></p>
</div>
</div>
{/* Contract Content */}
<article className="space-y-8 text-foreground">
{/* 1. Vertragsgegenstand */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
1. Vertragsgegenstand
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Gegenstand dieses Vertrages ist die Erbringung der im Angebot definierten Leistungen.</li>
<li>Der Vertrag besteht aus diesem Grundvertrag sowie den ausgewählten Leistungsmodulen.</li>
<li>Maßgeblich ist das jeweils angenommene Angebot von WEBklar.</li>
</ol>
</section>
{contractDivider}
{/* 2. Leistungsart */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
2. Leistungsart
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Sämtliche Leistungen von WEBklar stellen Dienst- und Entwicklungsleistungen dar.</li>
<li>Ein bestimmter wirtschaftlicher, technischer oder rechtlicher Erfolg wird nicht geschuldet.</li>
<li>WEBklar erbringt keinen laufenden Betrieb, sofern dieser nicht explizit vereinbart wurde.</li>
</ol>
</section>
{contractDivider}
{/* 3. Leistungsmodul A Webseite */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-2">
3. Leistungsmodul A Webseite (einmalig)
</h2>
<p className="text-sm text-muted-foreground mb-4">(aktiv, wenn im Angebot enthalten)</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar erstellt eine individuelle Webseite gemäß Angebot.</li>
<li>Die Umsetzung erfolgt nach den vom Kunden gelieferten Inhalten und Vorgaben.</li>
<li>Zusätzliche Leistungen wie Domain, Hosting, Wartung oder SEO sind nicht Bestandteil, sofern sie nicht gesondert beauftragt wurden.</li>
<li>Der Kunde ist nicht berechtigt, Änderungen am Quellcode selbst vorzunehmen.</li>
<li>Änderungen erfolgen ausschließlich durch WEBklar gegen gesonderte Vergütung.</li>
</ol>
</section>
{contractDivider}
{/* 4. Leistungsmodul B Automatisierung / Virtualisierung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-2">
4. Leistungsmodul B Automatisierung / Virtualisierung (einmalig)
</h2>
<p className="text-sm text-muted-foreground mb-4">(aktiv, wenn im Angebot enthalten)</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar entwickelt individuelle Automatisierungen, Apps oder Virtualisierungssysteme.</li>
<li>Die Leistung stellt eine reine Entwicklungsleistung dar.</li>
<li>Optional kann eine Beratungsleistung Bestandteil des Projektes sein.</li>
<li>Ein laufender Betrieb, Monitoring oder Wartung ist nicht geschuldet, außer dies wurde explizit vereinbart.</li>
<li>Der Kunde entscheidet über Inhalte, Daten und Prozesse und trägt dafür die rechtliche Verantwortung.</li>
</ol>
</section>
{contractDivider}
{/* 5. Leistungsmodul C Hosting */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-2">
5. Leistungsmodul C Hosting (jährlich)
</h2>
<p className="text-sm text-muted-foreground mb-4">(aktiv, wenn im Angebot enthalten)</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar stellt optional Hosting-Leistungen zur Verfügung.</li>
<li>Hostingverträge haben eine jährliche Laufzeit und verlängern sich automatisch, sofern nicht fristgerecht gekündigt wird.</li>
<li>WEBklar ist berechtigt, externe Anbieter (z.B. Rechenzentren) einzusetzen.</li>
<li>WEBklar übernimmt keine Haftung für Ausfälle externer Anbieter.</li>
</ol>
</section>
{contractDivider}
{/* 6. Quellcode und Eigentum */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
6. Quellcode und Eigentum
</h2>
<div className="space-y-4 text-muted-foreground">
<div>
<h3 className="text-foreground font-medium mb-2">6.1 Webseite</h3>
<ol className="list-decimal list-inside space-y-2 leading-relaxed [&>li]:pl-2">
<li>Der Kunde erhält ein einfaches, zeitlich unbegrenztes Nutzungsrecht an der fertigen Webseite.</li>
<li>Der Quellcode der Webseite wird nur auf ausdrückliche Anfrage und nach Vereinbarung herausgegeben.</li>
<li>Ohne Vereinbarung verbleibt der Quellcode bei WEBklar und wird archiviert.</li>
</ol>
</div>
<div>
<h3 className="text-foreground font-medium mb-2">6.2 Apps, Automatisierungen und Backend</h3>
<ol className="list-decimal list-inside space-y-2 leading-relaxed [&>li]:pl-2">
<li>Der Quellcode von Apps, Automatisierungen und Backend-Systemen verbleibt vollständig bei WEBklar.</li>
<li>Eine Herausgabe erfolgt ausschließlich nach gesonderter schriftlicher Vereinbarung.</li>
<li>Der Kunde erhält lediglich Zugriff auf die Bedienoberfläche bzw. das Frontend.</li>
</ol>
</div>
</div>
</section>
{contractDivider}
{/* 7. Mitwirkungspflichten */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
7. Mitwirkungspflichten des Kunden
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Der Kunde stellt alle benötigten Inhalte, Daten und Freigaben rechtzeitig bereit.</li>
<li>Verzögerungen durch fehlende Mitwirkung gehen nicht zu Lasten von WEBklar.</li>
<li>WEBklar ist nicht verpflichtet, rechtliche Prüfungen der Inhalte vorzunehmen.</li>
</ol>
</section>
{contractDivider}
{/* 8. Abnahme */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
8. Abnahme
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Nach Fertigstellung wird dem Kunden die Leistung zur Abnahme bereitgestellt.</li>
<li>Erfolgt innerhalb von 14 Tagen keine Rückmeldung, gilt die Leistung als abgenommen.</li>
<li>Nach Abnahme sind nur noch kostenpflichtige Änderungen möglich.</li>
</ol>
</section>
{contractDivider}
{/* 9. Vergütung und Zahlung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
9. Vergütung und Zahlung
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Die Vergütung ergibt sich aus dem Angebot.</li>
<li>Projektleistungen sind nach Vereinbarung fällig.</li>
<li>Hosting-Leistungen sind jährlich im Voraus zu zahlen.</li>
</ol>
</section>
{contractDivider}
{/* 10. Zahlungsverzug */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
10. Zahlungsverzug
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Bei Zahlungsverzug erfolgen bis zu zwei Mahnungen.</li>
<li>Danach ist WEBklar berechtigt:
<ul className="list-disc list-inside mt-2 space-y-1 pl-2">
<li>Leistungen zu sperren</li>
<li>Verzugszinsen zu berechnen</li>
<li>den Vertrag außerordentlich zu kündigen</li>
</ul>
</li>
</ol>
</section>
{contractDivider}
{/* 11. Haftung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
11. Haftung
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar haftet nur bei Vorsatz und grober Fahrlässigkeit.</li>
<li>Keine Haftung für:
<ul className="list-disc list-inside mt-2 space-y-1 pl-2">
<li>Umsatzausfälle</li>
<li>Datenverlust</li>
<li>Systemausfälle</li>
<li>externe Dienste</li>
</ul>
</li>
<li>Die Haftung ist der Höhe nach auf den Auftragswert begrenzt.</li>
</ol>
</section>
{contractDivider}
{/* 12. Kündigung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
12. Kündigung
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Laufzeitverträge (z.B. Hosting, Wartung) können zum Ende der jeweiligen Laufzeit gekündigt werden.</li>
<li>Das Recht zur außerordentlichen Kündigung bleibt unberührt.</li>
</ol>
</section>
{contractDivider}
{/* 13. Referenzen */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
13. Referenzen
</h2>
<p className="text-muted-foreground leading-relaxed">
WEBklar darf das Projekt nur nach ausdrücklicher Zustimmung des Kunden als Referenz verwenden.
</p>
</section>
{contractDivider}
{/* 14. Schlussbestimmungen */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
14. Schlussbestimmungen
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Es gilt deutsches Recht.</li>
<li>Gerichtsstand ist der Sitz von WEBklar, soweit zulässig.</li>
<li>Sollten einzelne Bestimmungen unwirksam sein, bleibt der Vertrag im Übrigen wirksam.</li>
</ol>
</section>
</article>
{/* Back / Contact */}
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<Link to="/">
<Button variant="outline" className="rounded-full">
Zur Startseite
</Button>
</Link>
<Link to="/kontakt">
<Button className="btn-minimal rounded-full">
Kontakt
</Button>
</Link>
</div>
</div>
</div>
</main>
</div>
);
};
export default AGB;

View File

@@ -171,19 +171,19 @@ const Contact = () => {
<div> <div>
<div className="label-tag mb-2">E-Mail</div> <div className="label-tag mb-2">E-Mail</div>
<a <a
href="mailto:hello@webklar.de" href="mailto:support@webklar.com"
className="text-foreground hover:text-muted-foreground transition-colors" className="text-foreground hover:text-muted-foreground transition-colors"
> >
hello@webklar.de support@webklar.com
</a> </a>
</div> </div>
<div> <div>
<div className="label-tag mb-2">Telefon</div> <div className="label-tag mb-2">Telefon</div>
<a <a
href="tel:+4912345678" href="tel:+491704969375"
className="text-foreground hover:text-muted-foreground transition-colors" className="text-foreground hover:text-muted-foreground transition-colors"
> >
+49 123 456 78 0170 4969375
</a> </a>
</div> </div>
</div> </div>

196
src/pages/Impressum.tsx Normal file
View File

@@ -0,0 +1,196 @@
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowLeft, Scale } from "lucide-react";
const impressumDivider = (
<div className="my-8 border-t border-border" aria-hidden />
);
const Impressum = () => {
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 glass-nav py-4">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">
Webklar
</span>
</Link>
<Link to="/">
<Button
variant="ghost"
className="text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Zurück
</Button>
</Link>
</div>
</div>
</header>
{/* Main Content */}
<main className="pt-32 pb-24">
<div className="container mx-auto px-6">
<div className="max-w-3xl mx-auto">
{/* Page Header */}
<div className="mb-12">
<div className="label-tag mb-4 flex items-center gap-2">
<Scale className="w-4 h-4" />
Rechtliches
</div>
<h1 className="text-4xl md:text-5xl font-display font-medium text-foreground tracking-tight uppercase mb-2">
Impressum
</h1>
<p className="text-muted-foreground text-lg">
Angaben gemäß § 5 TMG
</p>
</div>
{/* Impressum Content */}
<article className="space-y-8 text-foreground">
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
WEBklar GbR
</h2>
<p className="text-muted-foreground leading-relaxed whitespace-pre-line">
Am Schwimmbad 10<br />
67722 Winnweiler<br />
Deutschland
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Vertreten durch
</h2>
<p className="text-muted-foreground leading-relaxed">
Geschäftsführer:<br />
Kenso Gri
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Kontakt
</h2>
<p className="text-muted-foreground leading-relaxed">
Telefon: 0176 23726355<br />
E-Mail: kenso.gri@gmail.com
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Rechtsform
</h2>
<p className="text-muted-foreground leading-relaxed">
Gesellschaft bürgerlichen Rechts (GbR)
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Umsatzsteuer
</h2>
<p className="text-muted-foreground leading-relaxed">
Gemäß § 19 UStG wird keine Umsatzsteuer erhoben (Kleinunternehmerregelung).<br />
(Falls das nicht stimmt oder sich ändert, unbedingt sagen.)
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Verantwortlich für den Inhalt nach § 55 Absatz 2 RStV
</h2>
<p className="text-muted-foreground leading-relaxed whitespace-pre-line">
Kenso Gri<br />
Schliertal 21<br />
67468 Frankenstein<br />
Deutschland
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Haftung für Inhalte
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Als Diensteanbieter sind wir gemäß § 7 Absatz 1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich.<br />
Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Haftung für Links
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Unsere Webseite enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben.<br />
Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen.<br />
Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich.
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Urheberrecht
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht.<br />
Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Online-Streitbeilegung
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:<br />
<a
href="https://ec.europa.eu/consumers/odr"
target="_blank"
rel="noopener noreferrer"
className="text-foreground underline hover:no-underline"
>
https://ec.europa.eu/consumers/odr
</a>
<br /><br />
Wir sind nicht verpflichtet und nicht bereit, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.
</p>
</section>
</article>
{/* Back / Contact */}
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<Link to="/">
<Button variant="outline" className="rounded-full">
Zur Startseite
</Button>
</Link>
<Link to="/kontakt">
<Button className="btn-minimal rounded-full">
Kontakt
</Button>
</Link>
</div>
</div>
</div>
</main>
</div>
);
};
export default Impressum;

View File

@@ -1,5 +1,5 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react";
import path from "path"; import path from "path";
import { componentTagger } from "lovable-tagger"; import { componentTagger } from "lovable-tagger";