Merge branch 'design/critique-fixes'

Design-Verbesserungen aus Code-Review: Copy, Layout-Rhythmus,
About/Process-Sektionen, Pakete mit Features, interaktive Karte.
This commit is contained in:
Matthias
2026-05-18 18:12:30 +02:00
14 changed files with 2294 additions and 58 deletions

BIN
.pnpm-store/v11/index.db Normal file

Binary file not shown.

View File

@@ -28,6 +28,7 @@
"Authorization": "Bearer ${SHADCNBLOCKS_API_KEY}"
}
},
"@aceternity": "https://ui.aceternity.com/registry/{name}.json"
"@aceternity": "https://ui.aceternity.com/registry/{name}.json",
"@mapcn": "https://mapcn.dev/r/{name}.json"
}
}

View File

@@ -21,6 +21,7 @@
"clsx": "^2.1.1",
"framer-motion": "^12.38.0",
"lucide-react": "^1.8.0",
"maplibre-gl": "^5.24.0",
"motion": "^12.38.0",
"radix-ui": "^1.4.3",
"react": "^19.2.5",

190
pnpm-lock.yaml generated
View File

@@ -35,6 +35,9 @@ importers:
lucide-react:
specifier: ^1.8.0
version: 1.8.0(react@19.2.5)
maplibre-gl:
specifier: ^5.24.0
version: 5.24.0
motion:
specifier: ^12.38.0
version: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
@@ -657,6 +660,42 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@mapbox/jsonlint-lines-primitives@2.0.2':
resolution: {integrity: sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==}
engines: {node: '>= 0.6'}
'@mapbox/point-geometry@1.1.0':
resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==}
'@mapbox/tiny-sdf@2.2.0':
resolution: {integrity: sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==}
'@mapbox/unitbezier@0.0.1':
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
'@mapbox/vector-tile@2.0.4':
resolution: {integrity: sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==}
'@mapbox/whoots-js@3.1.0':
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
engines: {node: '>=6.0.0'}
'@maplibre/geojson-vt@5.0.4':
resolution: {integrity: sha512-KGg9sma45S+stfH9vPCJk1J0lSDLWZgCT9Y8u8qWZJyjFlP8MNP1WGTxIMYJZjDvVT3PDn05kN1C95Sut1HpgQ==}
'@maplibre/geojson-vt@6.1.0':
resolution: {integrity: sha512-2eIY4gZxeKIVOZVNkAMb+5NgXhgsMQpOveTQAvnp53LYqHGJZDidk7Ew0Tged9PThidpbS+NFTh0g4zivhPDzQ==}
'@maplibre/maplibre-gl-style-spec@24.8.5':
resolution: {integrity: sha512-EzEJmMt6thioRH7GI9LWS7ahXTcAhAPGWCe6oTP2Ps4YnsXOOAfeqx854lZaiDnwURfHmcCKV1mr6oo0i23x6w==}
hasBin: true
'@maplibre/mlt@1.1.9':
resolution: {integrity: sha512-g/tD8EYJB97udq33ipuJ9a4Q7fcbZnTEnUrgnEc/tLMmEL+zaCbR+X5fkDBO2dgpaAMsLH179qE3UXg2N0Nc/g==}
'@maplibre/vt-pbf@4.3.0':
resolution: {integrity: sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w==}
'@modelcontextprotocol/sdk@1.29.0':
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
engines: {node: '>=18'}
@@ -1706,6 +1745,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/geojson@7946.0.16':
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -1735,6 +1777,9 @@ packages:
'@types/statuses@2.0.6':
resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
'@types/supercluster@7.1.3':
resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==}
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -1743,6 +1788,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@vitejs/plugin-react@5.2.0':
resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==}
@@ -2111,6 +2157,9 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
earcut@3.0.2:
resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==}
eciesjs@0.4.18:
resolution: {integrity: sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==}
engines: {bun: '>=1', deno: '>=2', node: '>=16'}
@@ -2372,6 +2421,9 @@ packages:
github-slugger@2.0.0:
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
gl-matrix@3.4.4:
resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -2601,6 +2653,9 @@ packages:
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
json-stringify-pretty-compact@4.0.0:
resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -2609,6 +2664,9 @@ packages:
jsonfile@6.2.1:
resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
kdbush@4.0.2:
resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==}
kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
@@ -2719,6 +2777,10 @@ packages:
magicast@0.5.2:
resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==}
maplibre-gl@5.24.0:
resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==}
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
@@ -2934,6 +2996,9 @@ packages:
typescript:
optional: true
murmurhash-js@1.0.0:
resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==}
mute-stream@3.0.0:
resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==}
engines: {node: ^20.17.0 || >=22.9.0}
@@ -3094,6 +3159,10 @@ packages:
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
pbf@4.0.1:
resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==}
hasBin: true
piccolore@0.1.3:
resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==}
@@ -3120,6 +3189,9 @@ packages:
resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
engines: {node: ^10 || ^12 || >=14}
potpack@2.1.0:
resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==}
powershell-utils@0.1.0:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
@@ -3139,6 +3211,9 @@ packages:
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
protocol-buffers-schema@3.6.1:
resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -3150,6 +3225,9 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
quickselect@3.0.0:
resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==}
radix-ui@1.4.3:
resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
peerDependencies:
@@ -3280,6 +3358,9 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolve-protobuf-schema@2.1.0:
resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==}
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -3459,6 +3540,9 @@ packages:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
supercluster@8.0.1:
resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==}
svgo@4.0.1:
resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==}
engines: {node: '>=16'}
@@ -3496,6 +3580,9 @@ packages:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
tinyqueue@3.0.0:
resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==}
tldts-core@7.0.28:
resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==}
@@ -4411,6 +4498,51 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@mapbox/jsonlint-lines-primitives@2.0.2': {}
'@mapbox/point-geometry@1.1.0': {}
'@mapbox/tiny-sdf@2.2.0': {}
'@mapbox/unitbezier@0.0.1': {}
'@mapbox/vector-tile@2.0.4':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@types/geojson': 7946.0.16
pbf: 4.0.1
'@mapbox/whoots-js@3.1.0': {}
'@maplibre/geojson-vt@5.0.4': {}
'@maplibre/geojson-vt@6.1.0':
dependencies:
kdbush: 4.0.2
'@maplibre/maplibre-gl-style-spec@24.8.5':
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/unitbezier': 0.0.1
json-stringify-pretty-compact: 4.0.0
minimist: 1.2.8
quickselect: 3.0.0
tinyqueue: 3.0.0
'@maplibre/mlt@1.1.9':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@maplibre/vt-pbf@4.3.0':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@mapbox/vector-tile': 2.0.4
'@maplibre/geojson-vt': 5.0.4
'@types/geojson': 7946.0.16
'@types/supercluster': 7.1.3
pbf: 4.0.1
supercluster: 8.0.1
'@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
dependencies:
'@hono/node-server': 1.19.14(hono@4.12.14)
@@ -5454,6 +5586,8 @@ snapshots:
'@types/estree@1.0.8': {}
'@types/geojson@7946.0.16': {}
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -5486,6 +5620,10 @@ snapshots:
'@types/statuses@2.0.6': {}
'@types/supercluster@7.1.3':
dependencies:
'@types/geojson': 7946.0.16
'@types/unist@3.0.3': {}
'@types/validate-npm-package-name@4.0.2': {}
@@ -5891,6 +6029,8 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
earcut@3.0.2: {}
eciesjs@0.4.18:
dependencies:
'@ecies/ciphers': 0.2.6(@noble/ciphers@1.3.0)
@@ -6190,6 +6330,8 @@ snapshots:
github-slugger@2.0.0: {}
gl-matrix@3.4.4: {}
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -6424,6 +6566,8 @@ snapshots:
json-schema-typed@8.0.2: {}
json-stringify-pretty-compact@4.0.0: {}
json5@2.2.3: {}
jsonfile@6.2.1:
@@ -6432,6 +6576,8 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
kdbush@4.0.2: {}
kleur@3.0.3: {}
kleur@4.1.5: {}
@@ -6514,6 +6660,28 @@ snapshots:
'@babel/types': 7.29.0
source-map-js: 1.2.1
maplibre-gl@5.24.0:
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.2
'@mapbox/point-geometry': 1.1.0
'@mapbox/tiny-sdf': 2.2.0
'@mapbox/unitbezier': 0.0.1
'@mapbox/vector-tile': 2.0.4
'@mapbox/whoots-js': 3.1.0
'@maplibre/geojson-vt': 6.1.0
'@maplibre/maplibre-gl-style-spec': 24.8.5
'@maplibre/mlt': 1.1.9
'@maplibre/vt-pbf': 4.3.0
'@types/geojson': 7946.0.16
earcut: 3.0.2
gl-matrix: 3.4.4
kdbush: 4.0.2
murmurhash-js: 1.0.0
pbf: 4.0.1
potpack: 2.1.0
quickselect: 3.0.0
tinyqueue: 3.0.0
markdown-table@3.0.4: {}
math-intrinsics@1.1.0: {}
@@ -6903,6 +7071,8 @@ snapshots:
transitivePeerDependencies:
- '@types/node'
murmurhash-js@1.0.0: {}
mute-stream@3.0.0: {}
nanoid@3.3.11: {}
@@ -7058,6 +7228,10 @@ snapshots:
path-to-regexp@8.4.2: {}
pbf@4.0.1:
dependencies:
resolve-protobuf-schema: 2.1.0
piccolore@0.1.3: {}
picocolors@1.1.1: {}
@@ -7079,6 +7253,8 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
potpack@2.1.0: {}
powershell-utils@0.1.0: {}
pretty-ms@9.3.0:
@@ -7094,6 +7270,8 @@ snapshots:
property-information@7.1.0: {}
protocol-buffers-schema@3.6.1: {}
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -7105,6 +7283,8 @@ snapshots:
queue-microtask@1.2.3: {}
quickselect@3.0.0: {}
radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
dependencies:
'@radix-ui/primitive': 1.1.3
@@ -7311,6 +7491,10 @@ snapshots:
resolve-from@4.0.0: {}
resolve-protobuf-schema@2.1.0:
dependencies:
protocol-buffers-schema: 3.6.1
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
@@ -7608,6 +7792,10 @@ snapshots:
strip-final-newline@4.0.0: {}
supercluster@8.0.1:
dependencies:
kdbush: 4.0.2
svgo@4.0.1:
dependencies:
commander: 11.1.0
@@ -7639,6 +7827,8 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyqueue@3.0.0: {}
tldts-core@7.0.28: {}
tldts@7.0.28:

View File

@@ -62,6 +62,7 @@ const LandingHeroSection = () => {
</a>
<nav className="flex flex-wrap gap-5">
<a href="#leistungen">Leistungen</a>
<a href="#ablauf">Ablauf</a>
<a href="#pakete">Pakete</a>
<a href="#kontakt">Kontakt</a>
</nav>
@@ -76,9 +77,9 @@ const LandingHeroSection = () => {
</h1>
<div className="mt-8 grid gap-7 border-t border-border pt-7 lg:grid-cols-[0.72fr_1fr]">
<p className="text-sm uppercase tracking-[0.24em] text-muted-foreground">
Strategie trifft Umsetzung
Ihr Betrieb im Netz ohne Stress
</p>
<p className="max-w-2xl text-lg leading-8 text-foreground/78">
<p className="max-w-2xl text-lg leading-8 text-foreground/80">
Ich baue Websites für Handwerk, Praxen, Salons und
Dienstleister aus der Region klar genug, dass Besucher
anrufen statt weiterklicken.
@@ -118,7 +119,7 @@ const LandingHeroSection = () => {
<div className="relative z-10 flex min-h-[520px] flex-1 flex-col lg:min-h-0">
<div className="flex shrink-0 items-start justify-between border-b border-primary-foreground/30 pb-5 text-xs uppercase tracking-[0.28em]">
<span></span>
<span className="text-primary-foreground/60">Webdesign</span>
<span>&copy;2026</span>
</div>
@@ -177,7 +178,7 @@ const LandingHeroSection = () => {
href="#kontakt"
className="group relative z-10 inline-flex w-fit shrink-0 items-center gap-3 border border-primary-foreground px-5 py-4 text-sm font-semibold uppercase tracking-[0.18em] transition hover:bg-primary-foreground hover:text-primary"
>
Projekt anfragen
Unverbindlich anfragen
<ArrowUpRight className="size-5 transition group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
</a>
</div>

View File

@@ -1,15 +1,15 @@
import { ContactSection } from "@/components/landing/contact-section";
import { DeliverablesSection } from "@/components/landing/deliverables-section";
import { AboutSection } from "@/components/landing/about-section";
import { PackagesSection } from "@/components/landing/packages-section";
import { ProcessSection } from "@/components/landing/process-section";
import { ServicesSection } from "@/components/landing/services-section";
const LandingPageSections = () => {
return (
<>
<ServicesSection />
<DeliverablesSection />
<AboutSection />
<ProcessSection />
<PackagesSection />
<ContactSection />
</>
);
};

View File

@@ -0,0 +1,36 @@
const AboutSection = () => {
return (
<section
id="ueber"
className="grid border-b border-border lg:grid-cols-[0.36fr_0.64fr]"
>
<div className="border-b border-border bg-foreground px-5 py-12 text-background sm:px-8 lg:border-b-0 lg:border-r lg:px-12 lg:py-20">
<p className="text-sm uppercase tracking-[0.3em] text-primary">
Über mich (03)
</p>
<h2 className="mt-6 max-w-[12ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
Ein Mensch. Kein Ticketsystem.
</h2>
</div>
<div className="flex flex-col justify-center gap-8 px-5 py-12 sm:px-8 lg:px-12 lg:py-20">
<p className="max-w-xl text-[clamp(1.5rem,3vw,2rem)] font-black uppercase leading-[0.95] text-foreground">
Ich bin Matthias Webdesigner aus Sachsen.
</p>
<div className="flex flex-col gap-5">
<p className="max-w-2xl text-lg leading-8 text-muted-foreground">
Ich baue Websites für Betriebe, die ihre Energie lieber in Kunden
stecken als in Technik. Kein Großraumbüro, kein Agentur-Overhead
ein Ansprechpartner von der ersten Idee bis zum Go-live.
</p>
<p className="max-w-2xl text-lg leading-8 text-muted-foreground">
Meine Kunden sind Handwerker, Praxen, Salons und Dienstleister aus
der Region. Menschen, die eine Website wollen, die funktioniert
und sich dann wieder um ihren Betrieb kümmern möchten.
</p>
</div>
</div>
</section>
);
};
export { AboutSection };

View File

@@ -1,43 +1,65 @@
"use client";
import { CornerDownRight, Mail, MapPin, Phone } from "lucide-react";
import { Map, MapControls, MapMarker, MarkerContent } from "@/components/ui/map";
/** Karl-Marx-Str. 22, 08451 Crimmitschau (OpenStreetMap) */
const OFFICE: [number, number] = [12.3829769, 50.8131218];
const ContactSection = () => {
return (
<section
id="kontakt"
className="grid min-h-[620px] lg:grid-cols-[0.72fr_0.28fr]"
>
<div className="px-5 py-14 sm:px-8 lg:px-12 lg:py-24">
<p className="text-sm uppercase tracking-[0.3em] text-primary">
Kontakt (05)
</p>
<h2 className="mt-8 max-w-[12ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl lg:text-8xl">
Erzählen Sie mir kurz von Ihrem Betrieb
</h2>
<p className="mt-8 max-w-2xl text-xl leading-8 text-muted-foreground">
Ein paar Sätze reichen: Was bieten Sie an, was soll die Website für
Sie tun, und wann soll sie online sein?
</p>
<a
href="mailto:support@matthias-meister-webdesign.de"
className="mt-10 inline-flex items-center gap-3 bg-primary px-6 py-5 text-sm font-black uppercase tracking-[0.18em] text-primary-foreground transition hover:bg-foreground hover:text-background"
>
<CornerDownRight className="size-5" />
Anfrage per Mail senden
</a>
<section id="kontakt" className="grid min-h-[620px] lg:grid-cols-2">
<div className="flex flex-col justify-between px-5 py-14 sm:px-8 lg:px-12 lg:py-24">
<div>
<p className="text-sm uppercase tracking-[0.3em] text-primary">
Kontakt (06)
</p>
<h2 className="mt-8 max-w-[12ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl lg:text-8xl">
Erzählen Sie mir kurz von Ihrem Betrieb
</h2>
<p className="mt-8 max-w-2xl text-xl leading-8 text-muted-foreground">
Ein paar Sätze reichen: Was bieten Sie an, was soll die Website für
Sie tun, und wann soll sie online sein?
</p>
<a
href="mailto:support@matthias-meister-webdesign.de"
className="mt-10 inline-flex items-center gap-3 bg-primary px-6 py-5 text-sm font-black uppercase tracking-[0.18em] text-primary-foreground transition hover:bg-foreground hover:text-background"
>
<CornerDownRight className="size-5" />
Kurze Nachricht schreiben
</a>
</div>
<div className="mt-12 flex flex-col gap-5 border-t border-border pt-8 text-sm text-muted-foreground">
<div className="flex gap-3">
<Mail className="size-4 shrink-0 text-primary" />
<span>support@matthias-meister-webdesign.de</span>
</div>
<div className="flex gap-3">
<Phone className="size-4 shrink-0 text-primary" />
<span>Rückmeldung innerhalb von 24 Stunden</span>
</div>
<div className="flex gap-3">
<MapPin className="size-4 shrink-0 text-primary" />
<span>Karl-Marx-Str. 22, 08451 Crimmitschau</span>
</div>
</div>
</div>
<div className="flex flex-col justify-end gap-6 bg-primary px-5 py-10 text-primary-foreground sm:px-8 lg:px-10">
<div className="flex gap-3">
<Mail className="size-5" />
<span>support@matthias-meister-webdesign.de</span>
</div>
<div className="flex gap-3">
<Phone className="size-5" />
<span>Rückmeldung innerhalb von 24 Stunden</span>
</div>
<div className="flex gap-3">
<MapPin className="size-5" />
<span>Betriebe aus der Region</span>
</div>
<div className="contact-map relative min-h-[400px] bg-primary lg:min-h-0">
<Map
center={OFFICE}
zoom={15}
theme="dark"
className="h-full w-full"
>
<MapControls position="bottom-right" showZoom />
<MapMarker longitude={OFFICE[0]} latitude={OFFICE[1]}>
<MarkerContent>
<div className="size-5 rounded-full border-[3px] border-primary-foreground bg-primary shadow-lg ring-4 ring-primary-foreground/50" />
</MarkerContent>
</MapMarker>
</Map>
</div>
</section>
);

View File

@@ -1,18 +1,51 @@
import { Check } from "lucide-react";
const packages = [
{
name: "Basis",
price: "799 EUR",
detail: "Eine starke Seite für ein klares Angebot.",
highlighted: false,
features: [
"One-Page-Website",
"Mobil optimiert",
"Kontaktformular",
"DSGVO & Impressum",
"Hosting für 1 Jahr",
],
},
{
name: "Profi",
price: "1.499 EUR",
detail: "Mehrere Seiten für Betriebe mit mehr zu zeigen.",
highlighted: true,
features: [
"Alles aus Basis",
"Bis zu 5 Unterseiten",
"Individuelles Design",
"SEO-Grundoptimierung",
"Google Maps Einbindung",
"Kontaktformular",
"DSGVO & Impressum",
"Hosting für 1 Jahr",
],
},
{
name: "Maßarbeit",
price: "2.499 EUR+",
detail: "Eigene Struktur, selbst pflegbar, für besondere Anforderungen.",
detail: "Ihr Betrieb, Ihre Regeln. Inhalte selbst ändern, Seiten ergänzen, wachsen.",
highlighted: false,
features: [
"Alles aus Profi",
"Unbegrenzte Seiten",
"Inhalte selbst pflegbar (CMS)",
"Individuelles Design & Struktur",
"SEO-Optimierung",
"Blog oder News-Bereich",
"Erweiterte Funktionen",
"DSGVO & Impressum",
"Hosting für 1 Jahr",
],
},
];
@@ -25,29 +58,47 @@ const PackagesSection = () => {
<div className="grid gap-8 lg:grid-cols-[0.45fr_0.55fr]">
<div>
<p className="text-sm uppercase tracking-[0.3em] text-primary">
Pakete (04)
Pakete (05)
</p>
<h2 className="mt-8 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
Kosten ohne Nebel
Festpreis. Punkt.
</h2>
</div>
<div className="grid gap-4">
{packages.map((item) => (
<article
key={item.name}
className="grid gap-4 border border-border p-5 sm:grid-cols-[0.5fr_0.5fr] sm:p-6"
className={`grid gap-6 border p-5 sm:grid-cols-[0.5fr_0.5fr] sm:p-6 ${item.highlighted ? "border-primary bg-primary/6" : "border-border"}`}
>
<div>
<p className="text-sm uppercase tracking-[0.24em] text-primary">
{item.name}
</p>
<div className="flex items-center gap-3">
<p className="text-sm uppercase tracking-[0.24em] text-primary">
{item.name}
</p>
{item.highlighted ? (
<span className="bg-primary px-2 py-0.5 text-[10px] font-bold uppercase tracking-[0.2em] text-primary-foreground">
Beliebteste Wahl
</span>
) : null}
</div>
<p className="mt-4 text-4xl font-black uppercase">
{item.price}
</p>
<p className="mt-3 text-lg leading-7 text-muted-foreground">
{item.detail}
</p>
</div>
<p className="self-end text-lg leading-7 text-muted-foreground">
{item.detail}
</p>
<ul className="flex flex-col gap-2.5 self-center">
{item.features.map((feature) => (
<li
key={feature}
className="flex items-center gap-3 text-sm text-foreground/85"
>
<Check className="size-4 shrink-0 text-primary" />
<span>{feature}</span>
</li>
))}
</ul>
</article>
))}
</div>

View File

@@ -0,0 +1,61 @@
const steps = [
{
number: "01",
title: "Gespräch",
text: "15 Minuten telefonieren. Sie erzählen, ich höre zu.",
},
{
number: "02",
title: "Konzept",
text: "Seitenstruktur und Design-Vorschlag innerhalb einer Woche.",
},
{
number: "03",
title: "Umsetzung",
text: "Fertige Website in 24 Wochen. Feedback, Anpassung, fertig.",
},
{
number: "04",
title: "Online",
text: "Ich schalte live, richte Hosting ein, kümmere mich um den Rest.",
},
];
const ProcessSection = () => {
return (
<section
id="ablauf"
className="border-b border-border px-5 py-14 sm:px-8 lg:px-12 lg:py-24"
>
<div className="mb-12">
<p className="text-sm uppercase tracking-[0.3em] text-primary">
Ablauf (04)
</p>
<h2 className="mt-6 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
Vier Schritte. Fertig.
</h2>
</div>
<div className="grid gap-0 sm:grid-cols-2 lg:grid-cols-4">
{steps.map((step, i) => (
<article
key={step.number}
className={`relative flex flex-col gap-4 border-t border-border py-8 pr-6 lg:border-t-0 lg:border-l lg:py-0 lg:pl-8 lg:pr-10 ${i === 0 ? "lg:border-l-0 lg:pl-0" : ""}`}
>
<span className="text-6xl font-black leading-none text-primary/25">
{step.number}
</span>
<h3 className="text-xl font-black uppercase leading-none">
{step.title}
</h3>
<p className="max-w-xs text-base leading-7 text-muted-foreground">
{step.text}
</p>
</article>
))}
</div>
</section>
);
};
export { ProcessSection };

View File

@@ -2,17 +2,17 @@ const services = [
{
number: "01",
title: "Website",
text: "Eine klare Startseite oder ein kompletter Auftritt, der sofort zeigt, warum man Ihnen vertrauen kann.",
text: "Vom Elektriker bis zur Physiotherapie — eine Seite, die in drei Sekunden zeigt, was Sie machen und wie man Sie erreicht.",
},
{
number: "02",
title: "Struktur",
text: "Angebot, Referenzen, Ablauf und Kontakt werden so sortiert, dass Besucher nicht suchen müssen.",
text: "Angebot, Leistungen, Ablauf und Kontakt — alles dort, wo Besucher es erwarten. Damit aus Klicks Anrufe werden.",
},
{
number: "03",
title: "Technik",
text: "Schnell, mobil sauber, DSGVO-sauber und so gebaut, dass spätere Änderungen nicht zum Projekt werden.",
text: "Lädt in unter zwei Sekunden, sieht auf jedem Handy gut aus und ist rechtssicher. Änderungen später? Ein Anruf genügt.",
},
];

1844
src/components/ui/map.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
---
import { LandingHeroSection } from "@/components/landing-hero-section";
import { LandingPageSections } from "@/components/landing-page-sections";
import { ContactSection } from "@/components/landing/contact-section";
import { Footer27 } from "@/components/footer27";
import "@/styles/global.css";
---
@@ -22,6 +23,7 @@ import "@/styles/global.css";
<main class="min-h-screen overflow-hidden bg-background text-foreground">
<LandingHeroSection client:media="(min-width: 1024px)" />
<LandingPageSections />
<ContactSection client:visible />
<Footer27 />
</main>
<script>

View File

@@ -129,4 +129,31 @@
@apply font-sans;
scroll-behavior: smooth;
}
}
.maplibregl-popup-content {
@apply bg-transparent! shadow-none! p-0! rounded-none!;
}
.maplibregl-popup-tip {
@apply hidden!;
}
.contact-map .maplibregl-canvas {
/* Dunkle Carto-Tiles: Sepia kippt alles in Braun, hue-rotate(325deg)
schiebt Braun (~38°) auf Rot (~3°). Helle Straßen/Labels werden
zu hellem Rot, der dunkle Hintergrund zu dunklem Rot. */
filter: sepia(1) hue-rotate(325deg) saturate(4.5) brightness(0.82);
}
.contact-map .maplibregl-ctrl-attrib {
background: oklch(0.115 0.012 22 / 70%) !important;
}
.contact-map .maplibregl-ctrl-attrib a {
color: oklch(0.985 0.01 76 / 80%) !important;
}
.contact-map .maplibregl-ctrl-group {
background: transparent !important;
box-shadow: none !important;
}
.contact-map .maplibregl-ctrl-group button {
background: oklch(0.115 0.012 22 / 80%) !important;
color: oklch(0.985 0.01 76) !important;
border-color: oklch(0.985 0.01 76 / 20%) !important;
}
}