Compare commits

...

155 Commits

Author SHA1 Message Date
506c8682fe umami
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m52s
2026-01-29 17:43:06 +01:00
a909de30f3 filter products
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m52s
2026-01-29 17:34:15 +01:00
a2f94f15bc og
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 4m48s
2026-01-29 17:26:02 +01:00
13e56a88bc headings
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 4m3s
2026-01-29 16:51:11 +01:00
bb7d17001b og
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m57s
2026-01-29 16:33:04 +01:00
920efa0083 pdf sheets 2026-01-29 16:27:09 +01:00
0b81d1a4cb og
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 14s
2026-01-29 16:18:37 +01:00
1d5bdeba26 spacing 2026-01-29 15:59:53 +01:00
a0c3fbbc7e application field to mdx 2026-01-29 15:45:35 +01:00
8101a9f156 reveal
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m57s
2026-01-29 15:11:04 +01:00
7b6f4b5ea4 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 4m2s
2026-01-29 14:47:43 +01:00
658057cdb1 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 5m55s
2026-01-29 13:57:35 +01:00
2aa5d5b00e deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m53s
2026-01-29 13:52:37 +01:00
7f2f6f5aca deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 5m48s
2026-01-29 11:33:30 +01:00
4e50482769 remove redis
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 5m50s
2026-01-29 02:23:41 +01:00
1da1f05cdd remove varnish
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 4m55s
2026-01-29 02:12:39 +01:00
15cfb314b1 fixes
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m53s
2026-01-29 01:05:57 +01:00
a3da6192e3 remove smoke test 2026-01-29 00:34:41 +01:00
af33c6225d deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 5m32s
2026-01-29 00:09:15 +01:00
9ee09bbe4b deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 4m27s
2026-01-28 23:46:51 +01:00
3f0858a1ba smoke tests
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 4m17s
2026-01-28 21:39:16 +01:00
a85fe64ccb build
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 4m14s
2026-01-28 19:41:01 +01:00
21b16a5e6c deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m51s
2026-01-28 19:05:20 +01:00
6115e0e0d4 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m51s
2026-01-28 17:27:43 +01:00
859d034ed7 env
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m17s
2026-01-28 15:26:36 +01:00
91ebc54571 env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 4m12s
2026-01-28 15:14:01 +01:00
d6c1d6bae6 env
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 4m2s
2026-01-28 15:06:21 +01:00
407b2227b3 env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m53s
2026-01-28 01:00:30 +01:00
2896556659 env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m52s
2026-01-28 00:41:29 +01:00
8242687b07 env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m46s
2026-01-28 00:34:40 +01:00
dab4f3f5b5 logging 2026-01-28 00:13:19 +01:00
b18ee8d7a0 envs
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m52s
2026-01-28 00:02:24 +01:00
cbca29cbcf env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m52s
2026-01-27 23:51:48 +01:00
5a5c10ca36 env
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m46s
2026-01-27 23:43:14 +01:00
ad6bfe1457 404
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m48s
2026-01-27 23:36:12 +01:00
b5c3fc6649 404
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m43s
2026-01-27 23:09:56 +01:00
03609f113d 404
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 2m31s
2026-01-27 22:38:05 +01:00
622180c483 404s
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m40s
2026-01-27 22:27:04 +01:00
41865ab9ab deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m45s
2026-01-27 20:32:32 +01:00
986fcd067e deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m40s
2026-01-27 19:09:43 +01:00
d27616ed43 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m46s
2026-01-27 18:46:02 +01:00
b9a3e47662 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m43s
2026-01-27 18:30:54 +01:00
a5e34053f7 gotify 2026-01-27 18:22:55 +01:00
e6f9ad36d3 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m33s
2026-01-27 18:16:26 +01:00
2e0456b081 gotify
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 11s
2026-01-27 17:55:38 +01:00
ad37a372a7 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 10s
2026-01-27 12:37:49 +01:00
8490b691e1 contact 2026-01-27 12:36:00 +01:00
334c76935e env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m39s
2026-01-27 11:32:01 +01:00
6624cfc3ad sentry
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m37s
2026-01-27 10:31:36 +01:00
20d7d8405a toc
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m39s
2026-01-27 10:26:17 +01:00
12501ea51a fixes 2026-01-27 10:12:36 +01:00
70f1813e33 fix 404
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m38s
2026-01-27 09:23:15 +01:00
69e39b06cf fix redirects
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m39s
2026-01-27 09:17:55 +01:00
3b5174cd12 lightbox
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m46s
2026-01-27 08:57:43 +01:00
875cf1bd07 buttons 2026-01-27 08:47:52 +01:00
e284bb94af hero
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m39s
2026-01-27 08:30:18 +01:00
8eea94ceda hero
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m35s
2026-01-27 08:21:55 +01:00
05b10018a6 hero center
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m39s
2026-01-27 08:07:25 +01:00
3b493abb3d env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m34s
2026-01-27 00:50:52 +01:00
f54d8277b3 hero on mobile 2026-01-27 00:49:19 +01:00
5c71e9a064 env
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m36s
2026-01-27 00:30:12 +01:00
3cab376cd1 lightbox
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m31s
2026-01-27 00:21:03 +01:00
3c45e5563e build
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m34s
2026-01-27 00:14:10 +01:00
13ab4bde75 og image
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 13s
2026-01-27 00:10:10 +01:00
a805c7b8de titles
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Has been cancelled
2026-01-27 00:07:00 +01:00
b8fdbfb10b lightbox
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m39s
2026-01-26 23:59:10 +01:00
081adb02be fix
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 13s
2026-01-26 23:45:37 +01:00
3f17d08b04 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m40s
2026-01-26 23:28:23 +01:00
d40f4544ea deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m30s
2026-01-26 23:12:22 +01:00
041b5534c9 build
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m37s
2026-01-26 23:00:45 +01:00
dea3b57627 forms 2026-01-26 22:55:13 +01:00
baec7cc94a reveals
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 2m4s
2026-01-26 22:51:11 +01:00
b43b1c4314 fix 2026-01-26 22:19:13 +01:00
615757963c hero entrance 2026-01-26 22:04:49 +01:00
0094871358 ssr 2026-01-26 22:00:48 +01:00
8fef3b6c7f fix 2026-01-26 21:52:30 +01:00
cbb7855804 frontend fixes
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m37s
2026-01-26 20:21:17 +01:00
a8f7c5370b colors
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 11s
2026-01-26 17:42:43 +01:00
f459bf230d colors
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Has been cancelled
2026-01-26 17:40:33 +01:00
570a4977dd deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Has been cancelled
2026-01-26 17:38:24 +01:00
da04c108ef deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Has been cancelled
2026-01-26 17:37:25 +01:00
cb207d6a01 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 10s
2026-01-26 16:57:20 +01:00
6dc0ff4644 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m33s
2026-01-26 15:56:29 +01:00
e648d30767 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m30s
2026-01-26 15:36:46 +01:00
9c26ddddbf deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m28s
2026-01-26 14:08:17 +01:00
61b4b37111 build
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m29s
2026-01-26 13:38:48 +01:00
feedf30be1 env vars
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m30s
2026-01-26 12:57:06 +01:00
b596c22011 logs 2026-01-26 12:15:34 +01:00
377f583ff1 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m29s
2026-01-26 11:08:51 +01:00
4d72a5bf86 colors
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m35s
2026-01-26 02:39:22 +01:00
f50f41530d deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Has been cancelled
2026-01-26 02:36:58 +01:00
f8274cad1b logs 2026-01-26 02:34:54 +01:00
f2dd76a7a6 build
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m30s
2026-01-26 02:30:19 +01:00
574d5a8a9a deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m32s
2026-01-26 02:19:48 +01:00
ac1e22017e logging
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m46s
2026-01-26 02:02:46 +01:00
a503d3e539 colors 2026-01-26 01:59:40 +01:00
c2eeeafd56 deploy
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m43s
2026-01-26 01:45:10 +01:00
78cb7207e6 deploy 2026-01-26 01:41:07 +01:00
ca4c36ad01 deploy 2026-01-26 01:35:12 +01:00
c9dcf73021 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m49s
2026-01-26 01:28:04 +01:00
8146ee78fa deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Has been cancelled
2026-01-26 01:25:52 +01:00
58878e9f64 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m21s
2026-01-26 01:20:41 +01:00
f43c97e712 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m23s
2026-01-26 01:14:54 +01:00
888b46eed0 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m19s
2026-01-26 01:03:16 +01:00
f1c64f000c deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m22s
2026-01-26 00:57:43 +01:00
9b561f2176 deploy 2026-01-26 00:48:28 +01:00
f7930503d5 deploy 2026-01-26 00:34:50 +01:00
bb98014ea1 deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 4m43s
2026-01-25 23:44:44 +01:00
defde86fc9 build
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 4m45s
2026-01-25 23:20:39 +01:00
69141175cf deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 3m51s
2026-01-25 23:11:03 +01:00
4fdbc0f5cf deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 17s
2026-01-25 18:07:19 +01:00
97950d574e deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 17s
2026-01-25 17:56:04 +01:00
c04a134eca deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Has been cancelled
2026-01-25 17:52:57 +01:00
3b03572fb0 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Has been cancelled
2026-01-25 17:51:28 +01:00
a4c926ceb1 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 28s
2026-01-25 17:40:04 +01:00
f5edc08e9d deploy 2026-01-25 17:38:56 +01:00
28a1cb4b4c deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 36s
2026-01-25 17:30:54 +01:00
8dcc27ffcd deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 36s
2026-01-25 17:23:54 +01:00
a9d256ea55 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 36s
2026-01-25 17:13:55 +01:00
97b1a94012 simple deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 35s
2026-01-25 17:10:47 +01:00
831c8be588 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 29s
2026-01-25 17:01:00 +01:00
c121398381 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Has been cancelled
2026-01-25 17:00:02 +01:00
a831bed335 build
All checks were successful
Build & Deploy KLZ Cables / deploy (push) Successful in 5m45s
2026-01-25 13:53:27 +01:00
5659073f95 build
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 36s
2026-01-25 13:51:08 +01:00
2d93321a91 build
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 37s
2026-01-25 13:50:09 +01:00
fb6af84a42 interactive map
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 39s
2026-01-25 13:46:31 +01:00
8affb7878f deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Has been cancelled
2026-01-25 13:44:09 +01:00
c074a5d935 logging 2026-01-25 13:42:28 +01:00
4dbf566f0c hero on mobile 2026-01-25 13:38:14 +01:00
a0cfa8ef62 mailing
All checks were successful
Build & Deploy KLZ Cables / deploy (push) Successful in 5m8s
2026-01-25 13:36:25 +01:00
ae1e0ad8a9 fix 2026-01-25 13:33:09 +01:00
5ba3afc393 error pages 2026-01-25 13:29:11 +01:00
1380d40b4d proxy urls
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 2m21s
2026-01-25 13:25:37 +01:00
cf5df1b46b proxy urls
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Has been cancelled
2026-01-25 13:22:19 +01:00
dd9f427ad5 deploy
All checks were successful
Build & Deploy KLZ Cables / deploy (push) Successful in 3m53s
2026-01-25 13:13:47 +01:00
2c4345a7bd env 2026-01-25 12:34:57 +01:00
6fdf9c3464 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 4m22s
2026-01-25 12:28:18 +01:00
aac2cb2041 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 3m49s
2026-01-25 12:20:31 +01:00
b3827b95c2 vcf + terms 2026-01-25 12:15:10 +01:00
339a272105 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 3m48s
2026-01-25 11:47:00 +01:00
3582370449 font sizes
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 3m50s
2026-01-25 11:34:57 +01:00
3288bbd745 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 3m51s
2026-01-25 11:28:23 +01:00
c2d6e082e8 umami migration
All checks were successful
Build & Deploy KLZ Cables / deploy (push) Successful in 3m50s
2026-01-25 11:23:34 +01:00
4777091d8e deploy
All checks were successful
Build & Deploy KLZ Cables / deploy (push) Successful in 3m43s
2026-01-25 00:14:30 +01:00
5afa5395d4 deploy
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 29s
2026-01-24 23:42:28 +01:00
1568ecdf7d deploy
All checks were successful
Build & Deploy KLZ Cables / deploy (push) Successful in 5m11s
2026-01-24 23:13:09 +01:00
807a604e39 font sizes
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 1m1s
2026-01-24 23:09:00 +01:00
72711c74ba umami
Some checks failed
Build & Deploy KLZ Cables / deploy (push) Failing after 13m20s
2026-01-24 22:03:06 +01:00
7e94feaf19 sheets 2026-01-24 21:44:14 +01:00
d90d7502c3 sheets 2026-01-23 13:59:35 +01:00
e5e2b646a0 pdf sheets from new excel 2026-01-23 13:10:08 +01:00
899b3c7ed4 schema 2026-01-23 12:16:18 +01:00
1a646282a0 json 2026-01-23 12:09:23 +01:00
84438f1492 json schema 2026-01-23 12:07:11 +01:00
dd5636450c excel 2026-01-22 14:16:18 +01:00
377 changed files with 163802 additions and 2170 deletions

View File

@@ -6,5 +6,4 @@ node_modules
*.md
docs
reference
scripts
public/datasheets/*.pdf

19
.env
View File

@@ -1,4 +1,21 @@
# Application
NODE_ENV=production
NEXT_PUBLIC_BASE_URL=https://klz-cables.com
UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1
LOG_LEVEL=info
# WooCommerce & WordPress
WOOCOMMERCE_URL=https://klz-cables.com
WOOCOMMERCE_CONSUMER_KEY=ck_38d97df86880e8fefbd54ab5cdf47a9c5a9e5b39
WOOCOMMERCE_CONSUMER_SECRET=cs_d675ee2ac2ec7c22de84ae5451c07e42b1717759
WORDPRESS_APP_PASSWORD=DlJH 49dp fC3a Itc3 Sl7Z Wz0k
WORDPRESS_APP_PASSWORD="DlJH 49dp fC3a Itc3 Sl7Z Wz0k"
# SMTP Configuration
MAIL_HOST=smtp.eu.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@mg.mintel.me
MAIL_PASSWORD=4592fcb94599ee1a45b4ac2386fd0a64-102c75d8-ca2870e6
MAIL_FROM="KLZ Cables <postmaster@mg.mintel.me>"
MAIL_RECIPIENTS=marc@cablecreations.de,info@klz-cables.com

72
.env.example Normal file
View File

@@ -0,0 +1,72 @@
# ============================================================================
# KLZ Cables - Environment Configuration
# ============================================================================
# Copy this file to .env for local development
# For production, use .env.production as a template
# ============================================================================
# ────────────────────────────────────────────────────────────────────────────
# Application Configuration
# ────────────────────────────────────────────────────────────────────────────
NODE_ENV=development
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# ────────────────────────────────────────────────────────────────────────────
# Analytics (Umami)
# ────────────────────────────────────────────────────────────────────────────
# Optional: Leave empty to disable analytics
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
# ────────────────────────────────────────────────────────────────────────────
# Error Tracking (GlitchTip/Sentry)
# ────────────────────────────────────────────────────────────────────────────
# Optional: Leave empty to disable error tracking
SENTRY_DSN=
# ────────────────────────────────────────────────────────────────────────────
# Email Configuration (SMTP)
# ────────────────────────────────────────────────────────────────────────────
# Required for contact form functionality
MAIL_HOST=smtp.eu.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
MAIL_RECIPIENTS=info@klz-cables.com
# ────────────────────────────────────────────────────────────────────────────
# Logging
# ────────────────────────────────────────────────────────────────────────────
LOG_LEVEL=info
# ────────────────────────────────────────────────────────────────────────────
# Varnish Cache (Docker only)
# ────────────────────────────────────────────────────────────────────────────
VARNISH_CACHE_SIZE=256m
# ============================================================================
# IMPORTANT NOTES
# ============================================================================
#
# BUILD-TIME vs RUNTIME Variables:
# ─────────────────────────────────
# • NEXT_PUBLIC_* variables are baked into the client bundle at BUILD time
# They must be provided as --build-arg when building the Docker image
#
# • All other variables are used at RUNTIME only
# They are loaded from the .env file by docker-compose
#
# Docker Deployment:
# ──────────────────
# 1. Build-time: Only NEXT_PUBLIC_* vars are needed (via --build-arg)
# 2. Runtime: All vars are loaded from .env file on the server
# 3. The .env file should exist at: /home/deploy/sites/klz-cables.com/.env
#
# Security:
# ─────────
# • NEVER commit .env files with real credentials to git
# • Use Gitea/GitHub secrets for CI/CD workflows
# • Store production .env file securely on the server only
#
# ============================================================================

31
.env.production Normal file
View File

@@ -0,0 +1,31 @@
# ============================================================================
# KLZ Cables - Production Environment Configuration
# ============================================================================
# This file contains runtime environment variables for the production deployment.
# It should be placed on the production server at: /home/deploy/sites/klz-cables.com/.env
#
# IMPORTANT: This file contains sensitive data and should NEVER be committed to git.
# ============================================================================
# Application
NODE_ENV=production
NEXT_PUBLIC_BASE_URL=https://klz-cables.com
# Analytics (Umami)
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
# Error Tracking (GlitchTip/Sentry)
SENTRY_DSN=
# Email Configuration (Mailgun)
MAIL_HOST=smtp.eu.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
MAIL_RECIPIENTS=info@klz-cables.com
# Varnish Cache Size (optional)
VARNISH_CACHE_SIZE=256m

View File

@@ -2,159 +2,184 @@ name: Build & Deploy KLZ Cables
on:
push:
branches:
- main
branches: [main]
jobs:
deploy:
build-and-deploy:
runs-on: docker
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install tools
# ═══════════════════════════════════════════════════════════════════════════════
# LOGGING: Workflow Start - Full Transparency
# ═══════════════════════════════════════════════════════════════════════════════
- name: 📋 Log Workflow Start
run: |
apt-get update
apt-get install -y \
docker.io \
openssh-client \
rsync
echo "🚀 Starting deployment for ${{ github.repository }} (${{ github.ref }})"
echo " • Commit: ${{ github.sha }}"
echo " • Timestamp: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
- name: Login to registry
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASS: ${{ secrets.REGISTRY_PASS }}
run: |
echo "$REGISTRY_PASS" | docker login registry.infra.mintel.me -u "$REGISTRY_USER" --password-stdin
- name: Checkout repository
uses: actions/checkout@v4
- name: Build image
# ═══════════════════════════════════════════════════════════════════════════════
# LOGGING: Registry Login Phase
# ═══════════════════════════════════════════════════════════════════════════════
- name: 🔐 Login to private registry
run: |
docker build \
echo "🔐 Authenticating with registry.infra.mintel.me..."
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
# ═══════════════════════════════════════════════════════════════════════════════
# LOGGING: Build Phase
# ═══════════════════════════════════════════════════════════════════════════════
- name: 🏗️ Build Docker image
run: |
echo "🏗️ Building Docker image (linux/arm64)..."
docker buildx build \
--pull \
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }} \
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL=${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }} \
--build-arg NEXT_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }} \
-t registry.infra.mintel.me/mintel/klz-cables.com:latest .
--platform linux/arm64 \
--build-arg NEXT_PUBLIC_BASE_URL="${{ secrets.NEXT_PUBLIC_BASE_URL }}" \
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}" \
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}" \
-t registry.infra.mintel.me/mintel/klz-cables.com:latest \
--push .
- name: Push image
run: docker push registry.infra.mintel.me/mintel/klz-cables.com:latest
- name: Setup SSH
# ═══════════════════════════════════════════════════════════════════════════════
# LOGGING: Deployment Phase
# ═══════════════════════════════════════════════════════════════════════════════
- name: 🚀 Deploy to production server
run: |
echo "🚀 Deploying to alpha.mintel.me..."
# Setup SSH
mkdir -p ~/.ssh
printf "%s\n" "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts
- name: Sync docker-compose file to server
run: |
echo "Checking for docker-compose file..."
COMPOSE_FILE=""
if [ -f "docker-compose.yml" ]; then
COMPOSE_FILE="docker-compose.yml"
elif [ -f "docker-compose.yaml" ]; then
COMPOSE_FILE="docker-compose.yaml"
else
echo "ERROR: Keine docker-compose.yml oder .yaml gefunden!"
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
# Create .env file content
cat > /tmp/klz-cables.env << EOF
# ============================================================================
# KLZ Cables - Production Environment Configuration
# ============================================================================
# Auto-generated by CI/CD workflow
# DO NOT EDIT MANUALLY - Changes will be overwritten on next deployment
# ============================================================================
# Application
NODE_ENV=production
NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}
# Analytics (Umami)
NEXT_PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
NEXT_PUBLIC_UMAMI_SCRIPT_URL=${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}
# Error Tracking (GlitchTip/Sentry)
SENTRY_DSN=${{ secrets.SENTRY_DSN }}
# Email Configuration (Mailgun)
MAIL_HOST=${{ secrets.MAIL_HOST }}
MAIL_PORT=${{ secrets.MAIL_PORT }}
MAIL_USERNAME=${{ secrets.MAIL_USERNAME }}
MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
MAIL_FROM=${{ secrets.MAIL_FROM }}
MAIL_RECIPIENTS=${{ secrets.MAIL_RECIPIENTS }}
EOF
# Upload .env and deploy
scp -o StrictHostKeyChecking=accept-new /tmp/klz-cables.env root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/.env
ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me bash << EOF
set -e
cd /home/deploy/sites/klz-cables.com
chmod 600 .env
chown deploy:deploy .env
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
docker pull registry.infra.mintel.me/mintel/klz-cables.com:latest
docker-compose down
echo "🚀 Starting containers..."
docker-compose up -d
echo "⏳ Giving the app a few seconds to warm up..."
sleep 10
echo "🔍 Checking container status..."
docker-compose ps
if ! docker-compose ps | grep -q "Up"; then
echo "❌ Container failed to start"
docker-compose logs --tail=100
exit 1
fi
echo "Found and syncing: $COMPOSE_FILE"
# Use tar to bundle files and send them via SSH in a single connection
tar czf - "$COMPOSE_FILE" varnish 2>/dev/null || tar czf - "$COMPOSE_FILE" | \
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o IPQoS=0x00 deploy@alpha.mintel.me \
"mkdir -p /home/deploy/sites/klz-cables.com/ && tar xzf - -C /home/deploy/sites/klz-cables.com/ && echo 'Files synced successfully' && ls -la /home/deploy/sites/klz-cables.com/"
echo "✅ Deployment complete!"
EOF
echo "File sync completed"
rm -f /tmp/klz-cables.env
- name: Deploy on server
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASS: ${{ secrets.REGISTRY_PASS }}
# ═══════════════════════════════════════════════════════════════════════════════
# LOGGING: Workflow Summary
# ═══════════════════════════════════════════════════════════════════════════════
- name: 📊 Workflow Summary
if: always()
run: |
echo "Starting deployment on server..."
echo "📊 Status: ${{ job.status }}"
echo "🎯 Target: alpha.mintel.me"
# ═══════════════════════════════════════════════════════════════════════════════
# NOTIFICATION: Gotify
# ═══════════════════════════════════════════════════════════════════════════════
- name: 🔔 Gotify Notification (Success)
if: success()
run: |
echo "Sending success notification to Gotify..."
RESPONSE=$(curl -k -s -w "\n%{http_code}" -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
-F "title=✅ Deployment Success: ${{ github.repository }}" \
-F "message=The deployment of ${{ github.repository }} (branch: ${{ github.ref }}) was successful.
Commit: ${{ github.sha }}
Actor: ${{ github.actor }}
Run ID: ${{ github.run_id }}" \
-F "priority=5")
# Execute deployment commands directly with proper error handling
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o IPQoS=0x00 deploy@alpha.mintel.me "
set -e
echo '=== Starting deployment ==='
cd /home/deploy/sites/klz-cables.com
echo '=== Creating .env ==='
echo 'SENTRY_DSN=${{ secrets.SENTRY_DSN }}' > .env
echo '=== Logging into Docker registry ==='
echo '${{ secrets.REGISTRY_PASS }}' | docker login registry.infra.mintel.me -u '${{ secrets.REGISTRY_USER }}' --password-stdin
if [ $? -eq 0 ]; then
echo '✓ Registry login successful'
else
echo '✗ Registry login failed'
exit 1
fi
echo '=== Checking current containers ==='
docker compose ps
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
echo "HTTP Status: $HTTP_CODE"
echo "Response Body: $BODY"
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "Failed to send Gotify notification"
exit 0 # Don't fail the workflow because of notification failure
fi
echo '=== Checking current image ==='
docker images registry.infra.mintel.me/mintel/klz-cables.com:latest
echo '=== Removing local image to force fresh pull ==='
docker rmi registry.infra.mintel.me/mintel/klz-cables.com:latest || true
echo '=== Pulling latest image ==='
docker compose pull
echo '✓ Image pull command completed'
echo '=== Verifying new image after pull ==='
docker images registry.infra.mintel.me/mintel/klz-cables.com:latest
echo '=== Checking if image was actually updated ==='
if docker inspect registry.infra.mintel.me/mintel/klz-cables.com:latest >/dev/null 2>&1; then
echo '✓ Image exists locally'
else
echo '✗ Image pull failed - image not found locally'
exit 1
fi
- name: 🔔 Gotify Notification (Failure)
if: failure()
run: |
echo "Sending failure notification to Gotify..."
RESPONSE=$(curl -k -s -w "\n%{http_code}" -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
-F "title=❌ Deployment Failed: ${{ github.repository }}" \
-F "message=The deployment of ${{ github.repository }} (branch: ${{ github.ref }}) failed!
echo '=== Stopping all containers ==='
docker compose down
echo '✓ Containers stopped'
Commit: ${{ github.sha }}
Actor: ${{ github.actor }}
Run ID: ${{ github.run_id }}
echo '=== Starting all containers ==='
docker compose up -d
echo '✓ Containers started'
echo '=== Waiting for containers to be ready (20 seconds) ==='
sleep 20
echo '=== Verifying deployment status ==='
docker compose ps
echo '=== Checking app container logs ==='
docker compose logs --tail=30 app
echo '=== Checking varnish container logs ==='
docker compose logs --tail=20 varnish
echo '=== Verifying application health ==='
# Wait a bit more for the app to be fully ready
sleep 10
if curl -f -s http://localhost:80/health > /dev/null 2>&1; then
echo '✓ Application health check passed'
else
echo '✗ Application health check failed - checking app logs'
docker compose logs --tail=50 app
exit 1
fi
echo '=== Cleaning up old images ==='
docker image prune -f --filter 'until=24h'
echo '=== Deployment completed successfully ==='
"
echo "Deployment completed"
Please check the logs for details." \
-F "priority=8")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
echo "HTTP Status: $HTTP_CODE"
echo "Response Body: $BODY"
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "Failed to send Gotify notification"
exit 0 # Don't fail the workflow because of notification failure
fi

1
.tmp-mv170126-err.txt Normal file
View File

@@ -0,0 +1 @@
Sheet 1

1
.tmp-mv170126-out.txt Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,237 @@
# Analytics Migration Complete ✅
## Summary
Successfully migrated analytics data from Independent Analytics (WordPress) to Umami.
## Files Created
### 1. Migration Script
**Location:** `scripts/migrate-analytics-to-umami.py`
- Converts Independent Analytics CSV to Umami format
- Supports 3 output formats: JSON (API), SQL (database), API payload
- Preserves page view counts and average duration data
### 2. Deployment Script
**Location:** `scripts/deploy-analytics-to-umami.sh`
- Tailored for your server setup (`deploy@alpha.mintel.me`)
- Copies files to your Umami server
- Provides import instructions for your specific environment
### 3. Output Files
#### JSON Import File
**Location:** `data/umami-import.json`
- **Size:** 2.1 MB
- **Records:** 7,634 page view events
- **Website ID:** `59a7db94-0100-4c7e-98ef-99f45b17f9c3`
- **Use:** Import via Umami API
#### SQL Import File
**Location:** `data/umami-import.sql`
- **Size:** 1.8 MB
- **Records:** 5,250 SQL statements
- **Website ID:** `59a7db94-0100-4c7e-98ef-99f45b17f9c3`
- **Use:** Direct database import
### 4. Documentation
**Location:** `scripts/README-migration.md`
- Step-by-step migration guide
- Prerequisites and setup instructions
- Import methods (API and database)
- Troubleshooting tips
**Location:** `MIGRATION_SUMMARY.md`
- Complete migration overview
- Data summary and limitations
- Verification steps
- Next steps
**Location:** `ANALYTICS_MIGRATION_COMPLETE.md` (this file)
- Quick reference guide
- Deployment instructions
## Quick Start
### Option 1: Automated Deployment (Recommended)
```bash
# Run the deployment script
./scripts/deploy-analytics-to-umami.sh
```
This script will:
1. Copy files to your server
2. Provide import instructions
3. Show you the exact commands to run
### Option 2: Manual Deployment
#### Step 1: Copy files to server
```bash
scp data/umami-import.json deploy@alpha.mintel.me:/home/deploy/sites/klz-cables.com/data/
```
#### Step 2: SSH into server
```bash
ssh deploy@alpha.mintel.me
cd /home/deploy/sites/klz-cables.com
```
#### Step 3: Import data
**Method A: API Import (if API key is available)**
```bash
# Get your API key from Umami dashboard
# Add to .env: UMAMI_API_KEY=your-api-key
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d @data/umami-import.json \
http://localhost:3000/api/import
```
**Method B: Database Import (direct)**
```bash
# Import SQL file into PostgreSQL
docker exec -i $(docker compose ps -q postgres) psql -U umami -d umami < data/umami-import.sql
```
**Method C: Manual via Umami Dashboard**
1. Access Umami dashboard: https://analytics.infra.mintel.me
2. Go to Settings → Import
3. Upload `data/umami-import.json`
4. Select website ID: `59a7db94-0100-4c7e-98ef-99f45b17f9c3`
5. Click Import
## Your Umami Configuration
**Website ID:** `59a7db94-0100-4c7e-98ef-99f45b17f9c3`
**Environment Variables** (from docker-compose.yml):
```bash
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
```
**Server Details:**
- **Host:** alpha.mintel.me
- **User:** deploy
- **Path:** /home/deploy/sites/klz-cables.com
- **Umami API:** http://localhost:3000/api/import
## Data Summary
### What Was Migrated
- **Source:** Independent Analytics CSV (220 unique pages)
- **Migrated:** 7,634 simulated page view events
- **Metrics:** Page views, visitor counts, average duration
- **Website ID:** `59a7db94-0100-4c7e-98ef-99f45b17f9c3`
### What Was NOT Migrated
- Individual user sessions
- Real-time data
- Geographic data
- Referrer data
- Device/browser data
- Custom events
**Note:** The CSV contains aggregated data, not raw event data. The migration creates simulated historical data for reference only.
## Verification
### After Import
1. **Check Umami dashboard:** https://analytics.infra.mintel.me
2. **Verify page view counts** match your expectations
3. **Check top pages** appear correctly
4. **Monitor for a few days** to ensure new data is being collected
### Expected Results
- ✅ 7,634 events imported
- ✅ 220 unique pages
- ✅ Historical view counts preserved
- ✅ Duration data maintained
## Troubleshooting
### Issue: "SSH connection failed"
**Solution:** Check your SSH key and ensure `deploy@alpha.mintel.me` has access
### Issue: "API import failed"
**Solution:**
1. Check if Umami API is running: `docker compose ps`
2. Verify API key in `.env`: `UMAMI_API_KEY=your-key`
3. Try database import instead
### Issue: "Database import failed"
**Solution:**
1. Ensure PostgreSQL is running: `docker compose ps`
2. Check database credentials
3. Run migrations first: `docker exec -it $(docker compose ps -q postgres) psql -U umami -d umami -c "SELECT 1;"`
### Issue: "No data appears in dashboard"
**Solution:**
1. Verify import completed successfully
2. Check Umami logs: `docker compose logs app`
3. Ensure website ID matches: `59a7db94-0100-4c7e-98ef-99f45b17f9c3`
## Next Steps
### 1. Import the Data
Choose one of the import methods above and run it.
### 2. Verify the Migration
- Check Umami dashboard
- Verify page view counts
- Confirm data appears correctly
### 3. Update Your Website
Your website is already configured with:
```bash
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
```
### 4. Monitor for a Few Days
- Ensure Umami is collecting new data
- Compare with any remaining Independent Analytics data
- Verify tracking code is working
### 5. Clean Up
- Keep the original CSV as backup: `data/pages(1).csv`
- Store migration files for future reference
- Remove old Independent Analytics plugin from WordPress
## Support Resources
- **Umami Documentation:** https://umami.is/docs
- **Umami GitHub:** https://github.com/umami-software/umami
- **Independent Analytics:** https://independentanalytics.com/
## Migration Details
**Migration Date:** 2026-01-25
**Source Plugin:** Independent Analytics v2.9.7
**Target Platform:** Umami Analytics
**Website ID:** `59a7db94-0100-4c7e-98ef-99f45b17f9c3`
**Server:** alpha.mintel.me (deploy user)
**Status:** ✅ Ready for import
---
**Quick Command Reference:**
```bash
# Deploy to server
./scripts/deploy-analytics-to-umami.sh
# Or manually:
scp data/umami-import.json deploy@alpha.mintel.me:/home/deploy/sites/klz-cables.com/data/
ssh deploy@alpha.mintel.me
cd /home/deploy/sites/klz-cables.com
docker exec -i $(docker compose ps -q postgres) psql -U umami -d umami < data/umami-import.sql
```
**Need help?** Check `scripts/README-migration.md` for detailed instructions.

View File

@@ -3,7 +3,7 @@ FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache libc6-compat curl
WORKDIR /app
# Install dependencies based on the preferred package manager
@@ -20,14 +20,20 @@ COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_TELEMETRY_DISABLED=1
# Build-time environment variables for Next.js
# These are baked into the client bundle during build
ARG NEXT_PUBLIC_BASE_URL
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
ARG NEXT_PUBLIC_SENTRY_DSN
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL
ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN
# Validate environment variables during build
RUN npx tsx scripts/validate-env.ts
RUN npm run build
@@ -35,7 +41,10 @@ RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Install curl for health checks
RUN apk add --no-cache curl
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
@@ -57,9 +66,9 @@ USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV PORT=3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
ENV HOSTNAME="0.0.0.0"
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output

272
ENV_CLEANUP_SUMMARY.md Normal file
View File

@@ -0,0 +1,272 @@
# Environment Variables Cleanup - Summary
## What Was Done
Cleaned up the fragile, overkill environment variable mess and replaced it with a simple, clean, robust **fully automated** system.
## Changes Made
### 1. Dockerfile ✅
**Before**: 4 build args including runtime-only variables (SENTRY_DSN)
**After**: 3 build args - only `NEXT_PUBLIC_*` variables that need to be baked into the client bundle
```dockerfile
# Only these build args now:
ARG NEXT_PUBLIC_BASE_URL
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
```
### 2. docker-compose.yml ✅
**Before**: 12+ individual environment variables listed
**After**: Single `env_file: .env` directive
```yaml
app:
image: registry.infra.mintel.me/mintel/klz-cables.com:latest
env_file:
- .env # All runtime vars loaded from here
```
### 3. .gitea/workflows/deploy.yml ✅
**Before**: Passing 12+ environment variables individually via SSH command (fragile!)
**After**: **Fully automated** - workflow creates `.env` file from Gitea secrets and uploads it
```yaml
# Before (FRAGILE):
ssh root@alpha.mintel.me \
"MAIL_FROM='${{ secrets.MAIL_FROM }}' \
MAIL_HOST='${{ secrets.MAIL_HOST }}' \
... (12+ variables) \
/home/deploy/deploy.sh"
# After (AUTOMATED):
# 1. Create .env from secrets
cat > /tmp/klz-cables.env << EOF
NODE_ENV=production
NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}
# ... all other vars from secrets
EOF
# 2. Upload to server
scp /tmp/klz-cables.env root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/.env
# 3. Deploy
ssh root@alpha.mintel.me "cd /home/deploy/sites/klz-cables.com && docker-compose up -d"
```
### 4. New Files Created ✅
- **`.env.production`** - Template for reference (not used in automation)
- **`docs/DEPLOYMENT.md`** - Complete deployment guide
- **`docs/SERVER_SETUP.md`** - Server setup instructions
- **`docs/ENV_MIGRATION.md`** - Migration guide from old to new system
### 5. Updated Files ✅
- **`.env.example`** - Clear documentation of all variables with build-time vs runtime notes
## Architecture
### Build Time (CI/CD)
```
Gitea Workflow
Only passes NEXT_PUBLIC_* as --build-arg
Docker Build
Validates env vars
Bakes NEXT_PUBLIC_* into client bundle
Push to Registry
```
### Runtime (Production Server) - FULLY AUTOMATED
```
Gitea Secrets
Workflow creates .env file
SCP uploads to server
Secured (chmod 600, chown deploy:deploy)
docker-compose.yml (env_file: .env)
Loads .env into container
Application runs with full config
```
## Key Benefits
### 1. Simplicity
- **Before**: 15+ Gitea secrets, variables in 3+ places
- **After**: All secrets in Gitea, automatically deployed
### 2. Clarity
- **Before**: Confusing duplication, unclear which vars go where
- **After**: Clear separation - build args vs runtime env file
### 3. Robustness
- **Before**: Fragile SSH command with 12+ inline variables
- **After**: Robust automated file generation and upload
### 4. Security
- **Before**: Secrets potentially exposed in CI logs
- **After**: Secrets masked in logs, .env auto-secured on server
### 5. Maintainability
- **Before**: Update in 3 places (Dockerfile, docker-compose.yml, deploy.yml)
- **After**: Update Gitea secrets only - deployment is automatic
### 6. **Zero Manual Steps** 🎉
- **Before**: Manual .env file creation on server (error-prone, can be forgotten)
- **After**: **Fully automated** - .env file created and uploaded on every deployment
## What You Need to Do
### Required Gitea Secrets
Ensure these secrets are configured in your Gitea repository:
**Build-Time (NEXT_PUBLIC_*):**
- `NEXT_PUBLIC_BASE_URL` - Production URL (e.g., `https://klz-cables.com`)
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - Umami analytics ID
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` - Umami script URL
**Runtime:**
- `SENTRY_DSN` - Error tracking DSN
- `MAIL_HOST` - SMTP server
- `MAIL_PORT` - SMTP port (e.g., `587`)
- `MAIL_USERNAME` - SMTP username
- `MAIL_PASSWORD` - SMTP password
- `MAIL_FROM` - Sender email
- `MAIL_RECIPIENTS` - Recipient emails (comma-separated)
**Infrastructure:**
- `REGISTRY_USER` - Docker registry username
- `REGISTRY_PASS` - Docker registry password
- `ALPHA_SSH_KEY` - SSH private key for deployment server
**Notifications:**
- `GOTIFY_URL` - Gotify notification server URL
- `GOTIFY_TOKEN` - Gotify application token
### That's It!
**No manual steps required.** Just push to main branch and the workflow will:
1. ✅ Build Docker image with NEXT_PUBLIC_* build args
2. ✅ Create .env file from all secrets
3. ✅ Upload .env to server
4. ✅ Secure .env file (600 permissions, deploy:deploy ownership)
5. ✅ Pull latest image
6. ✅ Deploy with docker-compose
## Files Changed
```
Modified:
├── Dockerfile (removed redundant build args)
├── docker-compose.yml (use env_file instead of individual vars)
├── .gitea/workflows/deploy.yml (automated .env creation & upload)
├── .env.example (clear documentation)
├── lib/services/create-services.ts (removed redundant dotenv usage)
└── scripts/migrate-*.ts (removed redundant dotenv usage)
Created:
├── .env.production (reference template)
├── docs/DEPLOYMENT.md (deployment guide)
├── docs/SERVER_SETUP.md (server setup guide)
├── docs/ENV_MIGRATION.md (migration guide)
└── ENV_CLEANUP_SUMMARY.md (this file)
```
## Deployment Flow
```
┌─────────────────────────────────────────────────────────────┐
│ Developer pushes to main branch │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Gitea Workflow Triggered │
│ │
│ 1. Build Docker image (NEXT_PUBLIC_* build args) │
│ 2. Push to registry │
│ 3. Generate .env from secrets │
│ 4. Upload .env to server via SCP │
│ 5. SSH to server and deploy │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Production Server │
│ │
│ 1. .env file secured (600, deploy:deploy) │
│ 2. Docker login to registry │
│ 3. Pull latest image │
│ 4. docker-compose down │
│ 5. docker-compose up -d (loads .env) │
│ 6. Health checks pass │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ✅ Deployment Complete - Gotify Notification Sent │
└─────────────────────────────────────────────────────────────┘
```
## Comparison: Before vs After
| Aspect | Before | After |
|--------|--------|-------|
| **Gitea Secrets** | 15+ secrets | Same secrets, better organized |
| **Build Args** | 4 vars (including runtime-only) | 3 vars (NEXT_PUBLIC_* only) |
| **Runtime Vars** | Passed via SSH command | Auto-generated .env file |
| **Manual Steps** | ❌ Manual .env creation | ✅ Fully automated |
| **Maintenance** | Update in 3 places | Update Gitea secrets only |
| **Security** | Secrets in CI logs | Secrets masked, .env secured |
| **Clarity** | Confusing duplication | Clear separation |
| **Robustness** | Fragile SSH command | Robust automation |
| **Error-Prone** | ❌ Can forget .env | ✅ Impossible to forget |
## Documentation
- **[DEPLOYMENT.md](docs/DEPLOYMENT.md)** - Complete deployment guide
- **[SERVER_SETUP.md](docs/SERVER_SETUP.md)** - Server setup instructions (mostly automated now)
- **[ENV_MIGRATION.md](docs/ENV_MIGRATION.md)** - Migration from old to new system
- **[.env.example](.env.example)** - Environment variables reference
- **[.env.production](.env.production)** - Production template (for reference)
## Troubleshooting
### Deployment Fails
1. **Check Gitea secrets** - Ensure all required secrets are set
2. **Check workflow logs** - Look for specific error messages
3. **SSH to server** - Verify .env file exists and has correct permissions
4. **Check container logs** - `docker-compose logs -f app`
### .env File Issues
The workflow automatically:
- Creates .env from secrets
- Uploads to server
- Sets 600 permissions
- Sets deploy:deploy ownership
If there are issues, check the workflow logs for the "📝 Preparing environment configuration" step.
### Missing Environment Variables
If a variable is missing:
1. Add it to Gitea secrets
2. Update `.gitea/workflows/deploy.yml` to include it in the .env generation
3. Push to trigger new deployment
---
**Result**: Environment variable management is now simple, clean, robust, and **fully automated**! 🎉
No more manual .env file creation. No more forgotten configuration. No more fragile SSH commands. Just push and deploy!

193
MIGRATION_SUMMARY.md Normal file
View File

@@ -0,0 +1,193 @@
# Analytics Migration Summary: Independent Analytics → Umami
## Overview
Successfully migrated analytics data from Independent Analytics WordPress plugin to Umami format.
## Files Created
### 1. Migration Script
- **Location:** `scripts/migrate-analytics-to-umami.py`
- **Purpose:** Converts Independent Analytics CSV data to Umami format
- **Features:**
- JSON format (for API import)
- SQL format (for direct database import)
- API payload format (for manual import)
### 2. Migration Documentation
- **Location:** `scripts/README-migration.md`
- **Purpose:** Step-by-step guide for migration
- **Contents:**
- Prerequisites
- Migration options
- Import instructions
- Troubleshooting guide
### 3. Output Files
#### JSON Import File
- **Location:** `data/umami-import.json`
- **Size:** 2.1 MB
- **Records:** 7,634 simulated page view events
- **Format:** JSON array of Umami-compatible events
- **Use Case:** Import via Umami API
#### SQL Import File
- **Location:** `data/umami-import.sql`
- **Size:** 1.8 MB
- **Records:** 5,250 SQL INSERT statements
- **Format:** PostgreSQL-compatible SQL
- **Use Case:** Direct database import
## Data Migrated
### Source Data
- **File:** `data/pages(1).csv`
- **Records:** 220 unique pages
- **Metrics:**
- Page titles
- Visitor counts
- View counts
- Average view duration
- Bounce rates
- URLs
- Page types (Page, Post, Product, Category, etc.)
### Migrated Data
- **Total Events:** 7,634 simulated page views
- **Unique Pages:** 220
- **Data Points:**
- Website ID: `klz-cables`
- Path: Page URLs
- Duration: Preserved from average view duration
- Timestamp: Current time (for historical reference)
## Migration Process
### Step 1: Run Migration Script
```bash
python3 scripts/migrate-analytics-to-umami.py \
--input data/pages\(1\).csv \
--output data/umami-import.json \
--format json \
--site-id klz-cables
```
### Step 2: Choose Import Method
#### Option A: API Import (Recommended)
```bash
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d @data/umami-import.json \
https://your-umami-instance.com/api/import
```
#### Option B: Database Import
```bash
psql -U umami -d umami -f data/umami-import.sql
```
### Step 3: Verify Migration
1. Check Umami dashboard
2. Verify page view counts
3. Confirm data appears correctly
## Important Notes
### Data Limitations
The CSV export contains **aggregated data**, not raw event data:
- ✅ Page views (total counts)
- ✅ Visitor counts
- ✅ Average view duration
- ❌ Individual user sessions
- ❌ Real-time data
- ❌ Geographic data
- ❌ Referrer data
- ❌ Device/browser data
### What Gets Imported
The migration creates **simulated historical data**:
- Each page view becomes a separate event
- Timestamps are set to current time
- Duration is preserved from average view duration
- No session tracking (each view is independent)
### Recommendations
1. **Start fresh with Umami** - Let Umami collect new data going forward
2. **Keep the original CSV** - Store as backup for future reference
3. **Update your website** - Replace Independent Analytics tracking with Umami tracking
4. **Monitor for a few days** - Verify Umami is collecting data correctly
## Verification
### Check Generated Files
```bash
# Verify JSON file
ls -lh data/umami-import.json
head -20 data/umami-import.json
# Verify SQL file
ls -lh data/umami-import.sql
head -20 data/umami-import.sql
```
### Expected Results
- ✅ JSON file: ~2.1 MB, 7,634 records
- ✅ SQL file: ~1.8 MB, 5,250 statements
- ✅ Both files contain valid data for Umami import
## Next Steps
1. **Set up Umami instance** (if not already done)
2. **Create a website** in Umami dashboard
3. **Get your Website ID** and API key
4. **Run the migration script** with your credentials
5. **Import the data** using your preferred method
6. **Verify the migration** in Umami dashboard
7. **Update your website** to use Umami tracking code
8. **Monitor for a few days** to ensure data collection works
## Troubleshooting
### Issue: "ModuleNotFoundError"
**Solution:** Ensure Python 3 is installed: `python3 --version`
### Issue: "Permission denied"
**Solution:** Make script executable: `chmod +x scripts/migrate-analytics-to-umami.py`
### Issue: API import fails
**Solution:** Check API key, website ID, and Umami instance accessibility
### Issue: SQL import fails
**Solution:** Verify database credentials and run migrations first
## Support Resources
- **Umami Documentation:** https://umami.is/docs
- **Umami GitHub:** https://github.com/umami-software/umami
- **Independent Analytics:** https://independentanalytics.com/
## Summary
**Completed:**
- Created migration script with 3 output formats
- Generated JSON import file (2.1 MB, 7,634 events)
- Generated SQL import file (1.8 MB, 5,250 statements)
- Created comprehensive documentation
📊 **Data Migrated:**
- 220 unique pages
- 7,634 simulated page view events
- Historical view counts and durations
🎯 **Ready for Import:**
- Choose API or SQL import method
- Follow instructions in `scripts/README-migration.md`
- Verify data in Umami dashboard
**Migration Date:** 2026-01-25
**Source:** Independent Analytics v2.9.7
**Target:** Umami Analytics
**Site ID:** klz-cables

View File

@@ -47,11 +47,6 @@ NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
SENTRY_DSN=https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID
NEXT_PUBLIC_SENTRY_DSN=https://PUBLIC_KEY@errors.infra.mintel.me/PROJECT_ID
# Redis (optional cache)
# Platform provides a shared redis container reachable as `redis`.
# Pick a dedicated DB index per app, e.g. redis://redis:6379/2
REDIS_URL=redis://redis:6379/2
REDIS_KEY_PREFIX=klz:
```
## 📊 Project Overview
@@ -225,31 +220,60 @@ GET /robots.txt
## 🚀 Deployment
### Vercel (Recommended)
```bash
# Install Vercel CLI
npm i -g vercel
### Automatic Deployment (Current Setup)
# Deploy
vercel --prod
The project uses **Gitea Actions** for CI/CD. Every push to `main` triggers:
1. **Build**: Docker image built for `linux/arm64`
2. **Push**: Image pushed to `registry.infra.mintel.me`
3. **Deploy**: SSH to production server, pull and restart containers
**Workflow**: `.gitea/workflows/deploy.yml`
**Required Secrets** (configure in Gitea repository settings):
- `REGISTRY_USER` - Docker registry username
- `REGISTRY_PASS` - Docker registry password
- `ALPHA_SSH_KEY` - SSH private key for deployment
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - Analytics ID
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` - Analytics script URL
- `SENTRY_DSN` - Error tracking DSN
### Manual Deployment
```bash
# SSH into production server
ssh deploy@alpha.mintel.me
# Navigate to project
cd /home/deploy/sites/klz-cables.com
# Pull latest image and restart
docker compose pull
docker compose up -d --force-recreate --remove-orphans
docker image prune -f
```
### Static Export
Or use the convenience script:
```bash
# Build and export
npm run build
npm run export
# Deploy to any static host
# Upload /out directory
bash scripts/deploy-webhook.sh
```
### Netlify
```bash
# Connect repository
# Set build command: npm run build
# Set publish directory: out
### Architecture
```
Client → Traefik (TLS) → Next.js App
```
**Domains**:
- `klz-cables.com` - Production
- `www.klz-cables.com` - Production (www)
- `staging.klz-cables.com` - Staging
**Services**:
- `app`: Next.js application (port 3000)
- `traefik`: Reverse proxy (external)
For detailed deployment documentation, see [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md).
## 📈 Performance

View File

@@ -0,0 +1,29 @@
import { ImageResponse } from 'next/og';
import { getPageBySlug } from '@/lib/pages';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'nodejs';
export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string } }) {
const pageData = await getPageBySlug(slug, locale);
if (!pageData) {
return new ImageResponse(
<div style={{ display: 'flex', width: '100%', height: '100%', backgroundColor: '#001a4d' }} />
);
}
return new ImageResponse(
(
<OGImageTemplate
title={pageData.frontmatter.title}
description={pageData.frontmatter.excerpt}
label="Information"
/>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -1,8 +1,11 @@
import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { Section, Container, Heading, Badge } from '@/components/ui';
import { Container, Badge, Heading } from '@/components/ui';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
import { getPageBySlug, getAllPages } from '@/lib/pages';
import { mdxComponents } from '@/components/blog/MDXComponents';
import { getOGImageMetadata } from '@/lib/metadata';
interface PageProps {
params: {
@@ -11,8 +14,21 @@ interface PageProps {
};
}
export async function generateStaticParams() {
const locales = ['en', 'de'];
const params = [];
for (const locale of locales) {
const pages = await getAllPages(locale);
for (const page of pages) {
params.push({ locale, slug: page.slug });
}
}
return params;
}
export async function generateMetadata({ params: { locale, slug } }: PageProps): Promise<Metadata> {
const { getPageBySlug } = await import('@/lib/pages');
const pageData = await getPageBySlug(slug, locale);
if (!pageData) return {};
@@ -32,6 +48,7 @@ export async function generateMetadata({ params: { locale, slug } }: PageProps):
title: `${pageData.frontmatter.title} | KLZ Cables`,
description: pageData.frontmatter.excerpt || '',
url: `https://klz-cables.com/${locale}/${slug}`,
images: getOGImageMetadata(slug, pageData.frontmatter.title, locale),
},
twitter: {
card: 'summary_large_image',
@@ -42,7 +59,6 @@ export async function generateMetadata({ params: { locale, slug } }: PageProps):
}
export default async function StandardPage({ params: { locale, slug } }: PageProps) {
const { getPageBySlug } = await import('@/lib/pages');
const pageData = await getPageBySlug(slug, locale);
const t = await getTranslations('StandardPage');
@@ -51,7 +67,7 @@ export default async function StandardPage({ params: { locale, slug } }: PagePro
}
return (
<div className="flex flex-col min-h-screen bg-neutral-light">
<div className="flex flex-col min-h-screen bg-white">
{/* Hero Section */}
<section className="bg-primary-dark text-white py-20 md:py-32 relative overflow-hidden">
<div className="absolute inset-0 opacity-20">
@@ -60,67 +76,44 @@ export default async function StandardPage({ params: { locale, slug } }: PagePro
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<Badge variant="accent" className="mb-4 md:mb-6">{t('badge')}</Badge>
<Heading level={1} className="text-3xl md:text-6xl lg:text-7xl xl:text-8xl text-white mb-0">
<span className="text-white">{pageData.frontmatter.title}</span>
<Heading level={1} className="text-white mb-0">
{pageData.frontmatter.title}
</Heading>
</div>
</Container>
</section>
<Section className="bg-white -mt-8 md:-mt-12 relative z-20 rounded-t-[32px] md:rounded-t-[60px] shadow-2xl py-12 md:py-28">
<Container>
<div className="sticky-narrative-container">
{/* Sticky Narrative Sidebar - Mobile Optimized */}
<div className="sticky-narrative-sidebar mb-8 lg:mb-0">
<div className="lg:sticky lg:top-32 space-y-4 md:space-y-8">
{/* Mobile-only chip/stepper feel */}
<div className="flex lg:hidden overflow-x-auto pb-4 gap-3 no-scrollbar -mx-4 px-4">
<Badge variant="primary" className="whitespace-nowrap px-4 py-2 rounded-full shadow-sm">{t('overview')}</Badge>
<Badge variant="neutral" className="whitespace-nowrap px-4 py-2 rounded-full shadow-sm opacity-60">{t('details')}</Badge>
<Badge variant="neutral" className="whitespace-nowrap px-4 py-2 rounded-full shadow-sm opacity-60">{t('support')}</Badge>
</div>
<div className="p-6 md:p-8 bg-neutral-light rounded-2xl md:rounded-3xl border border-neutral-medium shadow-sm">
<h3 className="text-lg md:text-xl font-bold text-primary mb-3 md:mb-4 flex items-center gap-2">
<span className="w-1.5 h-6 bg-accent rounded-full" />
{t('quickNavigation')}
</h3>
<nav className="space-y-3 md:space-y-4">
<p className="text-sm md:text-base text-text-secondary leading-relaxed">
{t('exploreDetails', { title: pageData.frontmatter.title })}
</p>
</nav>
</div>
<div className="p-6 md:p-8 bg-primary-dark rounded-2xl md:rounded-3xl text-white shadow-xl relative overflow-hidden group">
<div className="absolute top-0 right-0 w-24 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
<h3 className="text-lg md:text-xl font-bold mb-3 md:mb-4 relative z-10">{t('needHelp')}</h3>
<p className="text-sm md:text-base text-white/70 mb-4 md:mb-6 relative z-10">{t('supportTeamAvailable')}</p>
<a href={`/${locale}/contact`} className="inline-flex items-center text-accent font-bold hover:underline touch-target relative z-10 group/link">
{t('contactUs')}
<span className="ml-2 transition-transform group-hover/link:translate-x-1">&rarr;</span>
</a>
</div>
</div>
{/* Main Content Area */}
<div className="container mx-auto px-4 py-16 md:py-24">
<div className="max-w-4xl mx-auto">
{/* Excerpt/Lead paragraph if available */}
{pageData.frontmatter.excerpt && (
<div className="mb-16 animate-slight-fade-in-from-bottom">
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
{pageData.frontmatter.excerpt}
</p>
</div>
)}
{/* Main Content */}
<div className="sticky-narrative-content">
<article className="prose prose-sm md:prose-lg lg:prose-xl prose-primary max-w-none
prose-headings:text-primary prose-headings:font-bold prose-headings:tracking-tight
prose-p:text-text-secondary prose-p:leading-relaxed
prose-strong:text-primary prose-strong:font-extrabold
prose-a:text-primary prose-a:font-bold prose-a:no-underline hover:prose-a:underline
prose-img:rounded-2xl md:prose-img:rounded-3xl prose-img:shadow-2xl
prose-ul:list-disc prose-ul:pl-5 md:prose-ul:pl-6
prose-li:text-text-secondary
">
<MDXRemote source={pageData.content} />
</article>
{/* Main content with shared blog components */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary animate-slight-fade-in-from-bottom">
<MDXRemote source={pageData.content} components={mdxComponents} />
</div>
{/* Support Section */}
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group animate-slight-fade-in-from-bottom">
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
<div className="relative z-10 max-w-2xl">
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('needHelp')}</h3>
<p className="text-lg text-white/70 mb-8">{t('supportTeamAvailable')}</p>
<a href={`/${locale}/contact`} className="inline-flex items-center px-8 py-4 bg-accent text-primary-dark font-bold rounded-full hover:bg-white transition-all duration-300 group/link">
{t('contactUs')}
<span className="ml-2 transition-transform group-hover/link:translate-x-1">&rarr;</span>
</a>
</div>
</div>
</Container>
</Section>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,74 @@
import { ImageResponse } from 'next/og';
import { getProductBySlug } from '@/lib/mdx';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { NextRequest } from 'next/server';
export const runtime = 'nodejs';
export async function GET(
request: NextRequest,
{ params }: { params: { locale: string } }
) {
const { searchParams } = new URL(request.url);
const slug = searchParams.get('slug');
const locale = params.locale || 'en';
if (!slug) {
return new Response('Missing slug', { status: 400 });
}
const t = await getTranslations({ locale, namespace: 'Products' });
// Check if it's a category page
const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
if (categories.includes(slug)) {
const categoryKey = slug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : slug;
const categoryDesc = t.has(`categories.${categoryKey}.description`) ? t(`categories.${categoryKey}.description`) : '';
return new ImageResponse(
(
<OGImageTemplate
title={categoryTitle}
description={categoryDesc}
label="Product Category"
/>
),
{
width: 1200,
height: 630,
}
);
}
const product = await getProductBySlug(slug, locale);
if (!product) {
return new ImageResponse(
<div style={{ display: 'flex', width: '100%', height: '100%', backgroundColor: '#001a4d' }} />
);
}
const { origin } = new URL(request.url);
const featuredImage = product.frontmatter.images?.[0]
? (product.frontmatter.images[0].startsWith('http')
? product.frontmatter.images[0]
: `${origin}${product.frontmatter.images[0]}`)
: undefined;
return new ImageResponse(
(
<OGImageTemplate
title={product.frontmatter.title}
description={product.frontmatter.description}
label={product.frontmatter.categories?.[0] || 'Product'}
image={featuredImage}
/>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -1,5 +1,6 @@
import { ImageResponse } from 'next/og';
import { getPostBySlug } from '@/lib/blog';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'nodejs';
@@ -12,98 +13,20 @@ export default async function Image({ params: { locale, slug } }: { params: { lo
);
}
const featuredImage = post.frontmatter.featuredImage
? (post.frontmatter.featuredImage.startsWith('http')
? post.frontmatter.featuredImage
: `https://klz-cables.com${post.frontmatter.featuredImage}`)
: undefined;
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-end',
backgroundColor: '#001a4d',
padding: '80px',
position: 'relative',
}}
>
{/* Background Image Overlay if available */}
{post.frontmatter.featuredImage && (
<img
src={post.frontmatter.featuredImage.startsWith('http') ? post.frontmatter.featuredImage : `https://klz-cables.com${post.frontmatter.featuredImage}`}
alt=""
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
objectFit: 'cover',
opacity: 0.4,
}}
/>
)}
{/* Gradient Overlay */}
<div
style={{
position: 'absolute',
inset: 0,
background: 'linear-gradient(to top, rgba(0,26,77,1) 0%, rgba(0,26,77,0.4) 100%)',
}}
/>
<div style={{ display: 'flex', flexDirection: 'column', position: 'relative', zIndex: 10 }}>
{post.frontmatter.category && (
<div
style={{
fontSize: '20px',
fontWeight: 'bold',
color: '#00ff99',
textTransform: 'uppercase',
letterSpacing: '0.1em',
marginBottom: '16px',
backgroundColor: 'rgba(0,255,153,0.1)',
padding: '4px 12px',
borderRadius: '4px',
}}
>
{post.frontmatter.category}
</div>
)}
<div
style={{
fontSize: '64px',
fontWeight: '900',
color: 'white',
lineHeight: '1.1',
maxWidth: '900px',
marginBottom: '24px',
}}
>
{post.frontmatter.title}
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
fontSize: '24px',
color: 'rgba(255,255,255,0.6)',
fontWeight: '500',
}}
>
KLZ Cables Blog
</div>
<div style={{ width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#00ff99', margin: '0 16px' }} />
<div
style={{
fontSize: '24px',
color: 'rgba(255,255,255,0.6)',
}}
>
{new Date(post.frontmatter.date).toLocaleDateString(locale, { year: 'numeric', month: 'long', day: 'numeric' })}
</div>
</div>
</div>
</div>
<OGImageTemplate
title={post.frontmatter.title}
description={post.frontmatter.excerpt}
label={post.frontmatter.category || 'Blog'}
image={featuredImage}
/>
),
{
width: 1200,

View File

@@ -1,8 +1,17 @@
import { notFound } from 'next/navigation';
import Script from 'next/script';
import JsonLd from '@/components/JsonLd';
import { getBreadcrumbSchema, SITE_URL, LOGO_URL } from '@/lib/schema';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { getPostBySlug, getAdjacentPosts, getReadingTime, getHeadings } from '@/lib/blog';
import { Metadata } from 'next';
import Link from 'next/link';
import PostNavigation from '@/components/blog/PostNavigation';
import PowerCTA from '@/components/blog/PowerCTA';
import TableOfContents from '@/components/blog/TableOfContents';
import { mdxComponents } from '@/components/blog/MDXComponents';
import { Heading } from '@/components/ui';
import { getOGImageMetadata } from '@/lib/metadata';
interface BlogPostProps {
params: {
@@ -35,6 +44,7 @@ export async function generateMetadata({ params: { locale, slug } }: BlogPostPro
publishedTime: post.frontmatter.date,
authors: ['KLZ Cables'],
url: `https://klz-cables.com/${locale}/blog/${slug}`,
images: getOGImageMetadata(`blog/${slug}`, post.frontmatter.title, locale),
},
twitter: {
card: 'summary_large_image',
@@ -44,151 +54,6 @@ export async function generateMetadata({ params: { locale, slug } }: BlogPostPro
};
}
import Link from 'next/link';
import VisualLinkPreview from '@/components/blog/VisualLinkPreview';
import { Callout } from '@/components/ui';
import HighlightBox from '@/components/blog/HighlightBox';
import Stats from '@/components/blog/Stats';
import AnimatedImage from '@/components/blog/AnimatedImage';
import ChatBubble from '@/components/blog/ChatBubble';
import SplitHeading from '@/components/blog/SplitHeading';
import PostNavigation from '@/components/blog/PostNavigation';
import PowerCTA from '@/components/blog/PowerCTA';
import TableOfContents from '@/components/blog/TableOfContents';
import StickyNarrative from '@/components/blog/StickyNarrative';
import TechnicalGrid from '@/components/blog/TechnicalGrid';
import ComparisonGrid from '@/components/blog/ComparisonGrid';
const components = {
VisualLinkPreview,
Callout,
HighlightBox,
Stats,
AnimatedImage,
ChatBubble,
PowerCTA,
SplitHeading,
StickyNarrative,
TechnicalGrid,
ComparisonGrid,
h1: () => null,
a: ({ href, children, ...props }: any) => {
if (href?.startsWith('/')) {
return (
<Link href={href} {...props} className="text-primary font-medium hover:underline decoration-2 underline-offset-2 transition-all">
{children}
</Link>
);
}
return (
<a
href={href}
{...props}
target="_blank"
rel="noopener noreferrer"
className="text-primary font-medium hover:underline decoration-2 underline-offset-2 transition-all inline-flex items-center gap-1"
>
{children}
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
);
},
img: (props: any) => (
<AnimatedImage src={props.src} alt={props.alt} />
),
h2: ({ children, ...props }: any) => (
<SplitHeading {...props} className="mt-16 mb-6 pb-3 border-b-2 border-primary/20">
{children}
</SplitHeading>
),
h3: ({ children, ...props }: any) => (
<h3 {...props} className="text-2xl font-bold text-text-primary mt-12 mb-4">
{children}
</h3>
),
p: ({ children, ...props }: any) => (
<p {...props} className="text-lg text-text-secondary leading-relaxed mb-6">
{children}
</p>
),
ul: ({ children, ...props }: any) => (
<ul {...props} className="my-8 space-y-3">
{children}
</ul>
),
ol: ({ children, ...props }: any) => (
<ol {...props} className="my-8 space-y-3 list-decimal list-inside">
{children}
</ol>
),
li: ({ children, ...props }: any) => (
<li {...props} className="text-lg text-text-secondary flex items-start gap-3">
<span className="text-primary mt-1.5 flex-shrink-0">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
</span>
<span className="flex-1">{children}</span>
</li>
),
blockquote: ({ children, ...props }: any) => (
<blockquote {...props} className="my-8 pl-6 border-l-4 border-primary bg-neutral-light/30 py-4 pr-6 rounded-r-lg">
<div className="text-lg text-text-primary italic">
{children}
</div>
</blockquote>
),
strong: ({ children, ...props }: any) => (
<strong {...props} className="font-bold text-primary">
{children}
</strong>
),
code: ({ children, ...props }: any) => (
<code {...props} className="px-2 py-1 bg-neutral-light text-primary rounded font-mono text-sm">
{children}
</code>
),
pre: ({ children, ...props }: any) => (
<pre {...props} className="my-8 p-6 bg-neutral-dark/5 rounded-xl overflow-x-auto">
{children}
</pre>
),
table: ({ children, ...props }: any) => (
<div className="my-8 overflow-x-auto rounded-lg border border-neutral-200 shadow-sm">
<table {...props} className="w-full text-left text-sm text-text-secondary">
{children}
</table>
</div>
),
thead: ({ children, ...props }: any) => (
<thead {...props} className="bg-neutral-50 text-text-primary font-semibold border-b border-neutral-200">
{children}
</thead>
),
tbody: ({ children, ...props }: any) => (
<tbody {...props} className="divide-y divide-neutral-200 bg-white">
{children}
</tbody>
),
tr: ({ children, ...props }: any) => (
<tr {...props} className="hover:bg-neutral-50/50 transition-colors">
{children}
</tr>
),
th: ({ children, ...props }: any) => (
<th {...props} className="px-6 py-4 whitespace-nowrap">
{children}
</th>
),
td: ({ children, ...props }: any) => (
<td {...props} className="px-6 py-4">
{children}
</td>
),
};
export default async function BlogPost({ params: { locale, slug } }: BlogPostProps) {
const post = await getPostBySlug(slug, locale);
const { prev, next } = await getAdjacentPosts(slug, locale);
@@ -222,9 +87,9 @@ export default async function BlogPost({ params: { locale, slug } }: BlogPostPro
</span>
</div>
)}
<h1 className="text-4xl md:text-6xl lg:text-7xl font-bold text-white mb-8 leading-[1.1] drop-shadow-2xl animate-slight-fade-in-from-bottom [animation-delay:200ms]">
<Heading level={1} className="text-white mb-8 drop-shadow-2xl animate-slight-fade-in-from-bottom [animation-delay:200ms]">
{post.frontmatter.title}
</h1>
</Heading>
<div className="flex flex-wrap items-center gap-6 text-white/80 text-sm md:text-base font-medium animate-slight-fade-in-from-bottom [animation-delay:400ms]">
<time dateTime={post.frontmatter.date}>
{new Date(post.frontmatter.date).toLocaleDateString(locale, {
@@ -250,9 +115,9 @@ export default async function BlogPost({ params: { locale, slug } }: BlogPostPro
</span>
</div>
)}
<h1 className="text-4xl md:text-6xl font-bold text-text-primary mb-8 leading-tight">
<Heading level={1} className="mb-8">
{post.frontmatter.title}
</h1>
</Heading>
<div className="flex items-center gap-6 text-text-secondary font-medium">
<time dateTime={post.frontmatter.date}>
{new Date(post.frontmatter.date).toLocaleDateString(locale, {
@@ -284,7 +149,7 @@ export default async function BlogPost({ params: { locale, slug } }: BlogPostPro
{/* Main content with enhanced styling */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary animate-slight-fade-in-from-bottom [animation-delay:800ms]">
<MDXRemote source={post.content} components={components} />
<MDXRemote source={post.content} components={mdxComponents} />
</div>
{/* Power CTA */}
@@ -321,32 +186,59 @@ export default async function BlogPost({ params: { locale, slug } }: BlogPostPro
</div>
{/* Structured Data */}
<Script
<JsonLd
id={`jsonld-${slug}`}
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.frontmatter.title,
datePublished: post.frontmatter.date,
image: post.frontmatter.featuredImage ? `https://klz-cables.com${post.frontmatter.featuredImage}` : undefined,
author: {
'@type': 'Organization',
name: 'KLZ Cables',
url: 'https://klz-cables.com',
data={{
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.frontmatter.title,
datePublished: post.frontmatter.date,
dateModified: post.frontmatter.date,
image: post.frontmatter.featuredImage ? `https://klz-cables.com${post.frontmatter.featuredImage}` : undefined,
author: {
'@type': 'Organization',
name: 'KLZ Cables',
url: 'https://klz-cables.com',
logo: 'https://klz-cables.com/logo-blue.svg'
},
publisher: {
'@type': 'Organization',
name: 'KLZ Cables',
logo: {
'@type': 'ImageObject',
url: 'https://klz-cables.com/logo-blue.svg',
},
publisher: {
'@type': 'Organization',
name: 'KLZ Cables',
logo: {
'@type': 'ImageObject',
url: 'https://klz-cables.com/logo.png',
},
},
description: post.frontmatter.excerpt,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://klz-cables.com/${locale}/blog/${slug}`,
},
articleSection: post.frontmatter.category,
wordCount: post.content.split(/\s+/).length,
timeRequired: `PT${getReadingTime(post.content)}M`
} as any}
/>
<JsonLd
id={`breadcrumb-${slug}`}
data={{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'Blog',
item: `https://klz-cables.com/${locale}/blog`,
},
description: post.frontmatter.excerpt,
}),
}}
{
'@type': 'ListItem',
position: 2,
name: post.frontmatter.title,
item: `https://klz-cables.com/${locale}/blog/${slug}`,
},
],
} as any}
/>
</article>
);

View File

@@ -0,0 +1,25 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'nodejs';
export default async function Image({ params: { locale } }: { params: { locale: string } }) {
const t = await getTranslations({ locale, namespace: 'Blog.meta' });
const title = t('title');
const description = t('description');
return new ImageResponse(
(
<OGImageTemplate
title={title}
description={description}
label="Blog"
/>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -3,6 +3,7 @@ import { getAllPosts } from '@/lib/blog';
import { Section, Container, Heading, Card, Badge, Button } from '@/components/ui';
import Reveal from '@/components/Reveal';
import { getTranslations } from 'next-intl/server';
import { getOGImageMetadata } from '@/lib/metadata';
interface BlogIndexProps {
params: {
@@ -27,6 +28,7 @@ export async function generateMetadata({ params: { locale } }: BlogIndexProps) {
title: `${t('title')} | KLZ Cables`,
description: t('description'),
url: `https://klz-cables.com/${locale}/blog`,
images: getOGImageMetadata('blog', t('title'), locale),
},
twitter: {
card: 'summary_large_image',
@@ -51,38 +53,40 @@ export default async function BlogIndex({ params: { locale } }: BlogIndexProps)
return (
<div className="bg-neutral-light min-h-screen">
{/* Hero Section - Immersive Magazine Feel */}
<section className="relative h-[50vh] md:h-[70vh] min-h-[400px] md:min-h-[600px] flex items-center overflow-hidden bg-primary-dark">
{featuredPost && featuredPost.frontmatter.featuredImage && (
<>
<img
src={featuredPost.frontmatter.featuredImage}
alt={featuredPost.frontmatter.title}
className="absolute inset-0 w-full h-full object-cover scale-105 animate-slow-zoom opacity-40 md:opacity-60"
/>
<div className="absolute inset-0 image-overlay-gradient" />
</>
)}
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<Badge variant="saturated" className="mb-4 md:mb-6">{t('featuredPost')}</Badge>
{featuredPost && (
<>
<h1 className="text-3xl md:text-7xl font-extrabold text-white mb-4 md:mb-8 leading-[1.1] line-clamp-3 md:line-clamp-none">
{featuredPost.frontmatter.title}
</h1>
<p className="text-base md:text-2xl text-white/80 mb-6 md:mb-10 line-clamp-2 md:line-clamp-2 max-w-2xl">
{featuredPost.frontmatter.excerpt}
</p>
<Button href={`/${locale}/blog/${featuredPost.slug}`} variant="accent" size="lg" className="group w-full md:w-auto md:h-16 md:px-10 md:text-xl">
{t('readFullArticle')}
<span className="ml-3 transition-transform group-hover:translate-x-2">&rarr;</span>
</Button>
</>
)}
</div>
</Container>
</section>
<Reveal>
<section className="relative h-[50vh] md:h-[70vh] min-h-[400px] md:min-h-[600px] flex items-center overflow-hidden bg-primary-dark">
{featuredPost && featuredPost.frontmatter.featuredImage && (
<>
<img
src={featuredPost.frontmatter.featuredImage}
alt={featuredPost.frontmatter.title}
className="absolute inset-0 w-full h-full object-cover scale-105 animate-slow-zoom opacity-40 md:opacity-60"
/>
<div className="absolute inset-0 image-overlay-gradient" />
</>
)}
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<Badge variant="saturated" className="mb-4 md:mb-6">{t('featuredPost')}</Badge>
{featuredPost && (
<>
<Heading level={1} className="text-white mb-4 md:mb-8">
{featuredPost.frontmatter.title}
</Heading>
<p className="text-base md:text-xl text-white/80 mb-6 md:mb-10 line-clamp-2 md:line-clamp-2 max-w-2xl">
{featuredPost.frontmatter.excerpt}
</p>
<Button href={`/${locale}/blog/${featuredPost.slug}`} variant="accent" size="lg" className="group w-full md:w-auto md:h-16 md:px-10 md:text-xl">
{t('readFullArticle')}
<span className="ml-3 transition-transform group-hover:translate-x-2">&rarr;</span>
</Button>
</>
)}
</div>
</Container>
</section>
</Reveal>
<Section className="bg-neutral-light py-12 md:py-28">
<Container>

View File

@@ -0,0 +1,25 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'nodejs';
export default async function Image({ params: { locale } }: { params: { locale: string } }) {
const t = await getTranslations({ locale, namespace: 'Contact' });
const title = t('meta.title') || t('title');
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
(
<OGImageTemplate
title={title}
description={description}
label="Contact"
/>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -1,7 +1,21 @@
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import ContactForm from '@/components/ContactForm';
import JsonLd from '@/components/JsonLd';
import Reveal from '@/components/Reveal';
import { Container, Heading, Section } from '@/components/ui';
import { Metadata } from 'next';
import { Section, Container, Button, Heading, Card, Input, Textarea, Label } from '@/components/ui';
import { getTranslations } from 'next-intl/server';
import { SITE_URL } from '@/lib/schema';
import { getOGImageMetadata } from '@/lib/metadata';
import { Suspense } from 'react';
import dynamic from 'next/dynamic';
const LeafletMap = dynamic(() => import('@/components/LeafletMap'), {
ssr: false,
loading: () => (
<div className="h-full w-full bg-neutral-medium flex items-center justify-center">
<div className="animate-pulse text-primary font-medium">Loading Map...</div>
</div>
),
});
interface ContactPageProps {
params: {
@@ -11,51 +25,122 @@ interface ContactPageProps {
export async function generateMetadata({ params: { locale } }: ContactPageProps): Promise<Metadata> {
const t = await getTranslations({ locale, namespace: 'Contact' });
const title = t('meta.title') || t('title');
const description = t('meta.description') || t('subtitle');
return {
title: t('title'),
description: t('subtitle'),
title,
description,
alternates: {
canonical: `/${locale}/contact`,
canonical: `https://klz-cables.com/${locale}/contact`,
languages: {
'de': '/de/contact',
'en': '/en/contact',
'x-default': '/en/contact',
'de-DE': '/de/contact',
'en-US': '/en/contact',
},
},
openGraph: {
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
title: `${title} | KLZ Cables`,
description,
url: `https://klz-cables.com/${locale}/contact`,
siteName: 'KLZ Cables',
images: getOGImageMetadata('contact', title, locale),
locale: `${locale.toUpperCase()}_DE`,
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
title: `${title} | KLZ Cables`,
description,
images: [`${SITE_URL}/${locale}/contact/opengraph-image`],
},
robots: {
index: true,
follow: true,
},
};
}
export default function ContactPage() {
const t = useTranslations('Contact');
export async function generateStaticParams() {
return [{ locale: 'de' }, { locale: 'en' }];
}
export default async function ContactPage({ params }: ContactPageProps) {
const { locale } = params;
const t = await getTranslations({ locale, namespace: 'Contact' });
return (
<div className="flex flex-col min-h-screen bg-neutral-light">
<JsonLd
id="breadcrumb-contact"
data={{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: t('title'),
item: `https://klz-cables.com/${locale}/contact`,
},
],
}}
/>
<JsonLd
id="local-business-contact"
data={{
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
name: 'KLZ Cables',
image: 'https://klz-cables.com/logo.png',
'@id': 'https://klz-cables.com',
url: 'https://klz-cables.com',
address: {
'@type': 'PostalAddress',
streetAddress: 'Raiffeisenstraße 22',
addressLocality: 'Remshalden',
postalCode: '73630',
addressCountry: 'DE',
},
geo: {
'@type': 'GeoCoordinates',
latitude: 48.8144,
longitude: 9.4144,
},
openingHoursSpecification: [
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday'
],
opens: '08:00',
closes: '17:00'
}
],
sameAs: [
'https://www.linkedin.com/company/klz-cables'
]
}}
/>
{/* Hero Section */}
<section className="bg-primary-dark text-white py-20 md:py-32 relative overflow-hidden">
<div className="absolute inset-0 opacity-20">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
</div>
<Container className="relative z-10">
<div className="max-w-4xl animate-slide-up">
<Heading level={1} subtitle={t('heroSubtitle')} className="text-white mb-4 md:mb-6">
<span className="text-white">{t('title')}</span>
</Heading>
<p className="text-lg md:text-2xl text-white/70 leading-relaxed max-w-2xl">
{t('subtitle')}
</p>
<Reveal>
<section className="bg-primary-dark text-white py-20 md:py-32 relative overflow-hidden">
<div className="absolute inset-0 opacity-20">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent via-transparent to-transparent" />
</div>
</Container>
</section>
<Container className="relative z-10">
<div className="max-w-4xl">
<Heading level={1} subtitle={t('heroSubtitle')} className="text-white mb-4 md:mb-6">
<span className="text-white">{t('title')}</span>
</Heading>
<p className="text-lg md:text-xl text-white/70 leading-relaxed max-w-2xl">
{t('subtitle')}
</p>
</div>
</Container>
</section>
</Reveal>
<Section className="bg-neutral-light -mt-8 md:-mt-20 relative z-20 py-12 md:py-28">
<Container>
@@ -82,17 +167,6 @@ export default function ContactPage() {
</div>
</div>
<div className="flex items-start gap-4 md:gap-6 group">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-saturated/10 flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
<svg className="w-5 h-5 md:w-7 md:h-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
</div>
<div>
<h4 className="text-base md:text-xl font-bold text-primary mb-1 md:mb-2">{t('info.phone')}</h4>
<a href="tel:+4988192537298" className="text-sm md:text-lg text-text-secondary hover:text-primary transition-colors font-medium touch-target">+49 881 92537298</a>
</div>
</div>
<div className="flex items-start gap-4 md:gap-6 group">
<div className="w-10 h-10 md:w-14 md:h-14 rounded-xl md:rounded-2xl bg-saturated/10 flex items-center justify-center text-saturated group-hover:bg-accent group-hover:text-primary-dark transition-all duration-300 shadow-sm flex-shrink-0">
@@ -102,7 +176,7 @@ export default function ContactPage() {
</div>
<div>
<h4 className="text-base md:text-xl font-bold text-primary mb-1 md:mb-2">{t('info.email')}</h4>
<a href="mailto:info@klz-vertriebs-gmbh.com" className="text-sm md:text-lg text-text-secondary hover:text-primary transition-colors font-medium touch-target">info@klz-vertriebs-gmbh.com</a>
<a href="mailto:info@klz-cables.com" className="text-sm md:text-lg text-text-secondary hover:text-primary transition-colors font-medium touch-target">info@klz-cables.com</a>
</div>
</div>
</div>
@@ -110,7 +184,7 @@ export default function ContactPage() {
<div className="p-6 md:p-10 bg-white rounded-2xl md:rounded-3xl border border-neutral-medium shadow-sm animate-fade-in">
<Heading level={4} className="mb-4 md:mb-6">{t('hours.title')}</Heading>
<ul className="space-y-2 md:space-y-4">
<ul className="space-y-2 md:space-y-4 list-none m-0 p-0">
<li className="flex justify-between items-center pb-2 md:pb-4 border-b border-neutral-medium text-sm md:text-base">
<span className="font-bold text-primary">{t('hours.weekdays')}</span>
<span className="text-text-secondary">{t('hours.weekdaysTime')}</span>
@@ -125,71 +199,25 @@ export default function ContactPage() {
{/* Contact Form */}
<div className="lg:col-span-7">
<Card className="p-6 md:p-12 rounded-2xl md:rounded-[40px] border-none shadow-2xl animate-slide-up">
<Heading level={3} subtitle={t('form.subtitle')} className="mb-6 md:mb-10">
{t('form.title')}
</Heading>
<form className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8">
<div className="space-y-1 md:space-y-2">
<Label htmlFor="name">{t('form.name')}</Label>
<Input
type="text"
id="name"
name="name"
autoComplete="name"
enterKeyHint="next"
placeholder={t('form.namePlaceholder')}
required
/>
</div>
<div className="space-y-1 md:space-y-2">
<Label htmlFor="email">{t('form.email')}</Label>
<Input
type="email"
id="email"
name="email"
autoComplete="email"
inputMode="email"
enterKeyHint="next"
placeholder={t('form.emailPlaceholder')}
required
/>
</div>
<div className="md:col-span-2 space-y-1 md:space-y-2">
<Label htmlFor="message">{t('form.message')}</Label>
<Textarea
id="message"
name="message"
rows={4}
enterKeyHint="send"
placeholder={t('form.messagePlaceholder')}
required
/>
</div>
<div className="md:col-span-2 pt-2 md:pt-4">
<Button type="submit" variant="saturated" size="lg" className="w-full shadow-xl shadow-saturated/20 md:h-16 md:px-10 md:text-xl active:scale-[0.98] transition-transform">
{t('form.submit')}
</Button>
</div>
</form>
</Card>
<Suspense fallback={<div className="animate-pulse bg-neutral-medium h-96 rounded-2xl md:rounded-3xl"></div>}>
<ContactForm />
</Suspense>
</div>
</div>
</Container>
</Section>
{/* Map Placeholder */}
{/* Map Section */}
<section className="h-[300px] md:h-[500px] bg-neutral-medium relative overflow-hidden grayscale hover:grayscale-0 transition-all duration-1000">
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center">
<div className="w-20 h-20 bg-primary rounded-full flex items-center justify-center text-white mb-4 mx-auto shadow-2xl animate-bounce">
<svg className="w-10 h-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
</svg>
</div>
<p className="font-bold text-primary text-xl">{t('map.comingSoon')}</p>
</div>
</div>
<Suspense fallback={<div className="h-full w-full bg-neutral-medium animate-pulse flex items-center justify-center">
<div className="text-primary font-medium">Loading Map...</div>
</div>}>
<LeafletMap
address={t('info.address')}
lat={48.8144}
lng={9.4144}
/>
</Suspense>
</section>
</div>
);

View File

@@ -1,9 +1,10 @@
'use client';
import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';
import { useTranslations } from 'next-intl';
import { Container, Button } from '@/components/ui';
import { getAppServices } from '@/lib/services/create-services';
import { Container, Button, Heading } from '@/components/ui';
import Scribble from '@/components/Scribble';
export default function Error({
error,
@@ -15,18 +16,51 @@ export default function Error({
const t = useTranslations('Error');
useEffect(() => {
Sentry.captureException(error);
const services = getAppServices();
services.errors.captureException(error);
services.logger.error('Application error caught by boundary', {
message: error.message,
stack: error.stack,
digest: error.digest
});
}, [error]);
return (
<Container className="py-24 flex flex-col items-center justify-center text-center min-h-[60vh]">
<h2 className="text-3xl font-bold mb-4">{t('title')}</h2>
<p className="text-white/60 mb-8 max-w-md">
<Container className="relative py-24 flex flex-col items-center justify-center text-center min-h-[70vh] overflow-hidden">
{/* Industrial Background Element */}
<div className="absolute inset-0 -z-10 opacity-[0.03] pointer-events-none flex items-center justify-center">
<span className="text-[20rem] font-bold select-none">500</span>
</div>
<div className="relative mb-8">
<Heading level={1} className="text-6xl md:text-8xl font-bold mb-2 text-saturated">
500
</Heading>
<Scribble
variant="underline"
className="w-full h-6 -bottom-2 left-0 text-saturated/40"
/>
</div>
<Heading level={2} className="text-2xl md:text-3xl font-bold mb-4">
{t('title')}
</Heading>
<p className="text-white/60 mb-10 max-w-md text-lg">
{t('description')}
</p>
<Button onClick={() => reset()}>
{t('tryAgain')}
</Button>
<div className="flex flex-col sm:flex-row gap-4">
<Button onClick={() => reset()} variant="saturated" size="lg">
{t('tryAgain')}
</Button>
<Button href="/" variant="outline" size="lg">
{t('goHome')}
</Button>
</div>
{/* Decorative Industrial Line */}
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-px h-24 bg-gradient-to-b from-saturated/50 to-transparent" />
</Container>
);
}

View File

@@ -1,60 +1,16 @@
import {NextIntlClientProvider} from 'next-intl';
import {getMessages, getTranslations} from 'next-intl/server';
import '../../styles/globals.css';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import UmamiScript from '@/components/analytics/UmamiScript';
import Header from '@/components/Header';
import JsonLd from '@/components/JsonLd';
import AnalyticsProvider from '@/components/analytics/AnalyticsProvider';
import { Metadata, Viewport } from 'next';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import '../../styles/globals.css';
import { SITE_URL } from '@/lib/schema';
export async function generateMetadata({params: {locale}}: {params: {locale: string}}): Promise<Metadata> {
const t = await getTranslations({locale, namespace: 'Index.meta'});
return {
title: {
default: t('title'),
template: `%s | KLZ Cables`
},
description: t('description'),
metadataBase: new URL('https://klz-cables.com'),
icons: {
icon: '/favicon.ico',
apple: '/apple-touch-icon.png',
},
alternates: {
canonical: `/${locale}`,
languages: {
'de': '/de',
'en': '/en',
'x-default': '/en',
},
},
openGraph: {
type: 'website',
locale: locale === 'de' ? 'de_DE' : 'en_US',
url: `https://klz-cables.com/${locale}`,
siteName: 'KLZ Cables',
title: t('title'),
description: t('description'),
},
twitter: {
card: 'summary_large_image',
title: t('title'),
description: t('description'),
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};
}
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
};
export const viewport: Viewport = {
width: 'device-width',
@@ -80,9 +36,7 @@ export default async function LocaleLayout({
<html lang={locale} className="scroll-smooth overflow-x-hidden">
<body className="flex flex-col min-h-screen font-sans selection:bg-accent selection:text-primary-dark antialiased overflow-x-hidden">
<NextIntlClientProvider messages={messages} locale={locale}>
{/* Loads Umami only when NEXT_PUBLIC_UMAMI_WEBSITE_ID is set */}
<UmamiScript />
<JsonLd />
<Header />
<main className="flex-grow animate-fade-in overflow-visible">
{children}

View File

@@ -0,0 +1,46 @@
import { useTranslations } from 'next-intl';
import { Container, Button, Heading } from '@/components/ui';
import Scribble from '@/components/Scribble';
export default function NotFound() {
const t = useTranslations('Error.notFound');
return (
<Container className="relative py-24 flex flex-col items-center justify-center text-center min-h-[70vh] overflow-hidden">
{/* Industrial Background Element */}
<div className="absolute inset-0 -z-10 opacity-[0.03] pointer-events-none flex items-center justify-center">
<span className="text-[20rem] font-bold select-none">404</span>
</div>
<div className="relative mb-8">
<Heading level={1} className="text-6xl md:text-8xl font-bold mb-2">
404
</Heading>
<Scribble
variant="circle"
className="w-[150%] h-[150%] -top-[25%] -left-[25%] text-accent/40"
/>
</div>
<Heading level={2} className="text-2xl md:text-3xl font-bold mb-4 text-primary">
{t('title')}
</Heading>
<p className="text-white/60 mb-10 max-w-md text-lg">
{t('description')}
</p>
<div className="flex flex-col sm:flex-row gap-4">
<Button href="/" variant="accent" size="lg">
{t('cta')}
</Button>
<Button href="/contact" variant="outline" size="lg">
Contact Support
</Button>
</div>
{/* Decorative Industrial Line */}
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-px h-24 bg-gradient-to-t from-accent/50 to-transparent" />
</Container>
);
}

View File

@@ -1,88 +1,19 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'edge';
export const runtime = 'nodejs';
export default async function Image({ params: { locale } }: { params: { locale: string } }) {
const t = await getTranslations({ locale, namespace: 'Index.meta' });
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'center',
backgroundColor: '#001a4d', // Primary Blue from Styleguide
padding: '80px',
position: 'relative',
}}
>
{/* Background Pattern / Scribble placeholder */}
<div
style={{
position: 'absolute',
top: '-100px',
right: '-100px',
width: '600px',
height: '600px',
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(0,255,153,0.1) 0%, transparent 70%)',
}}
/>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div
style={{
fontSize: '24px',
fontWeight: 'bold',
color: '#00ff99', // Accent Green
textTransform: 'uppercase',
letterSpacing: '0.2em',
marginBottom: '20px',
}}
>
KLZ Cables
</div>
<div
style={{
fontSize: '72px',
fontWeight: '900',
color: 'white',
lineHeight: '1.1',
maxWidth: '800px',
marginBottom: '30px',
}}
>
{t('title')}
</div>
<div
style={{
fontSize: '32px',
color: 'rgba(255,255,255,0.7)',
maxWidth: '700px',
}}
>
{t('description')}
</div>
</div>
{/* Bottom Accent Line */}
<div
style={{
position: 'absolute',
bottom: '80px',
left: '80px',
width: '120px',
height: '8px',
backgroundColor: '#00ff99',
borderRadius: '4px',
}}
/>
</div>
<OGImageTemplate
title={t('title')}
description={t('description')}
label="Reliable Energy Infrastructure"
/>
),
{
width: 1200,

View File

@@ -1,4 +1,6 @@
import Hero from '@/components/home/Hero';
import JsonLd from '@/components/JsonLd';
import { getBreadcrumbSchema, SITE_URL } from '@/lib/schema';
import ProductCategories from '@/components/home/ProductCategories';
import WhatWeDo from '@/components/home/WhatWeDo';
import RecentPosts from '@/components/home/RecentPosts';
@@ -9,10 +11,19 @@ import GallerySection from '@/components/home/GallerySection';
import VideoSection from '@/components/home/VideoSection';
import CTA from '@/components/home/CTA';
import Reveal from '@/components/Reveal';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
import { getOGImageMetadata } from '@/lib/metadata';
export default function HomePage({ params: { locale } }: { params: { locale: string } }) {
return (
<div className="flex flex-col min-h-screen">
<JsonLd
id="breadcrumb-home"
data={getBreadcrumbSchema([
{ name: 'Home', item: `/${locale}` },
])}
/>
<Hero />
<Reveal><ProductCategories /></Reveal>
<Reveal><WhatWeDo /></Reveal>
@@ -26,3 +37,46 @@ export default function HomePage({ params: { locale } }: { params: { locale: str
</div>
);
}
export async function generateMetadata({ params: { locale } }: { params: { locale: string } }): Promise<Metadata> {
// Use translations for meta where available (namespace: Index.meta)
// Fallback to a sensible default if translation keys are missing.
let t;
try {
t = await getTranslations({ locale, namespace: 'Index.meta' });
} catch (err) {
// If translations for Index.meta are not present, try generic Index namespace
try {
t = await getTranslations({ locale, namespace: 'Index' });
} catch (e) {
t = (key: string) => '';
}
}
const title = t('title') || 'KLZ Cables';
const description = t('description') || '';
return {
title,
description,
alternates: {
canonical: `/${locale}`,
languages: {
'de': '/de',
'en': '/en',
'x-default': '/en',
},
},
openGraph: {
title: `${title} | KLZ Cables`,
description,
url: `https://klz-cables.com/${locale}`,
images: getOGImageMetadata('', title, locale),
},
twitter: {
card: 'summary_large_image',
title: `${title} | KLZ Cables`,
description,
},
};
}

View File

@@ -1,14 +1,17 @@
import Script from 'next/script';
import JsonLd from '@/components/JsonLd';
import { getBreadcrumbSchema, SITE_URL } from '@/lib/schema';
import ProductSidebar from '@/components/ProductSidebar';
import ProductTabs from '@/components/ProductTabs';
import ProductTechnicalData from '@/components/ProductTechnicalData';
import RelatedProducts from '@/components/RelatedProducts';
import { Badge, Container, Section } from '@/components/ui';
import { Badge, Container, Heading, Section } from '@/components/ui';
import { getDatasheetPath } from '@/lib/datasheets';
import { getAllProducts, getProductBySlug } from '@/lib/mdx';
import { mapFileSlugToTranslated } from '@/lib/slugs';
import { mapFileSlugToTranslated, mapSlugToFileSlug } from '@/lib/slugs';
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { getProductOGImageMetadata } from '@/lib/metadata';
import { MDXRemote } from 'next-mdx-remote/rsc';
import Image from 'next/image';
import Link from 'next/link';
@@ -28,9 +31,10 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
// Check if it's a category page
const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
if (categories.includes(productSlug)) {
const categoryKey = productSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : productSlug;
const fileSlug = await mapSlugToFileSlug(productSlug, locale);
if (categories.includes(fileSlug)) {
const categoryKey = fileSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : fileSlug;
const categoryDesc = t.has(`categories.${categoryKey}.description`) ? t(`categories.${categoryKey}.description`) : '';
return {
@@ -48,6 +52,7 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
title: `${categoryTitle} | KLZ Cables`,
description: categoryDesc,
url: `https://klz-cables.com/${locale}/products/${productSlug}`,
images: getProductOGImageMetadata(fileSlug, categoryTitle, locale),
},
twitter: {
card: 'summary_large_image',
@@ -76,6 +81,7 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
description: product.frontmatter.description,
type: 'website',
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
images: getProductOGImageMetadata(productSlug, product.frontmatter.title, locale),
},
twitter: {
card: 'summary_large_image',
@@ -91,7 +97,7 @@ const components = {
p: (props: any) => <p {...props} className="text-lg md:text-xl text-text-secondary leading-relaxed mb-8 font-medium" />,
h2: (props: any) => (
<div className="relative mb-16">
<h2 {...props} className="text-4xl md:text-5xl font-black text-primary tracking-tighter uppercase mb-6" />
<h2 {...props} className="text-3xl md:text-4xl font-black text-primary tracking-tighter uppercase mb-6" />
<div className="w-20 h-1.5 bg-accent rounded-full" />
</div>
),
@@ -116,7 +122,7 @@ const components = {
blockquote: (props: any) => (
<div className="my-20 p-10 md:p-16 bg-primary-dark rounded-[40px] relative overflow-hidden group">
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/10 rounded-full -translate-y-1/2 translate-x-1/2 blur-3xl group-hover:bg-accent/20 transition-colors duration-700" />
<div className="relative z-10 italic text-2xl md:text-4xl text-white/90 leading-relaxed font-black tracking-tight" {...props} />
<div className="relative z-10 italic text-2xl md:text-3xl text-white/90 leading-relaxed font-black tracking-tight" {...props} />
</div>
),
};
@@ -128,15 +134,17 @@ export default async function ProductPage({ params }: ProductPageProps) {
// Check if it's a category page
const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
if (categories.includes(productSlug)) {
const fileSlug = await mapSlugToFileSlug(productSlug, locale);
if (categories.includes(fileSlug)) {
const allProducts = await getAllProducts(locale);
const categoryKey = productSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : productSlug;
const categoryKey = fileSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : fileSlug;
// Filter products for this category
const filteredProducts = allProducts.filter(p =>
p.frontmatter.categories.some(cat =>
cat.toLowerCase().replace(/\s+/g, '-') === productSlug ||
cat.toLowerCase().replace(/\s+/g, '-') === fileSlug ||
cat === categoryTitle
)
);
@@ -159,9 +167,9 @@ export default async function ProductPage({ params }: ProductPageProps) {
<span className="mx-3 opacity-30">/</span>
<span className="text-white/90">{categoryTitle}</span>
</nav>
<h1 className="text-5xl md:text-7xl lg:text-8xl font-extrabold text-white mb-8 tracking-tight leading-[1.05]">
<Heading level={1} className="text-white mb-8">
{categoryTitle}
</h1>
</Heading>
<div className="h-1.5 w-24 bg-accent rounded-full" />
</div>
</Container>
@@ -228,11 +236,24 @@ export default async function ProductPage({ params }: ProductPageProps) {
notFound();
}
// Extract technical data for schema
const technicalDataMatch = product.content.match(/technicalData=\{<ProductTechnicalData data=\{(.*?)\}\s*\/>\}/s);
let technicalItems = [];
if (technicalDataMatch) {
try {
const data = JSON.parse(technicalDataMatch[1]);
technicalItems = data.technicalItems || [];
} catch (e) {
console.error('Failed to parse technical data for schema', e);
}
}
const datasheetPath = getDatasheetPath(productSlug, locale);
const isFallback = (product.frontmatter as any).isFallback;
const categorySlug = slug[0];
const categoryKey = categorySlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : categorySlug;
const categoryFileSlug = await mapSlugToFileSlug(categorySlug, locale);
const categoryKey = categoryFileSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : categoryFileSlug;
const sidebar = (
<ProductSidebar
@@ -291,9 +312,9 @@ export default async function ProductPage({ params }: ProductPageProps) {
</Badge>
))}
</div>
<h1 className="text-6xl md:text-8xl lg:text-9xl font-black text-white mb-8 tracking-tighter leading-[0.9] uppercase">
<Heading level={1} className="text-white mb-8 uppercase">
{product.frontmatter.title}
</h1>
</Heading>
<p className="text-xl md:text-2xl text-white/60 max-w-2xl leading-relaxed font-medium">
{product.frontmatter.description}
</p>
@@ -342,29 +363,37 @@ export default async function ProductPage({ params }: ProductPageProps) {
</div>
{/* Structured Data */}
<Script
<JsonLd
id={`jsonld-${product.slug}`}
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: product.frontmatter.title,
description: product.frontmatter.description,
sku: product.frontmatter.sku,
image: product.frontmatter.images?.[0] ? `https://klz-cables.com${product.frontmatter.images[0]}` : undefined,
brand: {
'@type': 'Brand',
name: 'KLZ Cables',
},
offers: {
'@type': 'Offer',
availability: 'https://schema.org/InStock',
priceCurrency: 'EUR',
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
},
}),
}}
data={{
'@context': 'https://schema.org',
'@type': 'Product',
name: product.frontmatter.title,
description: product.frontmatter.description,
sku: product.frontmatter.sku || product.slug.toUpperCase(),
image: product.frontmatter.images?.[0] ? `https://klz-cables.com${product.frontmatter.images[0]}` : undefined,
brand: {
'@type': 'Brand',
name: 'KLZ Cables',
},
offers: {
'@type': 'Offer',
availability: 'https://schema.org/InStock',
priceCurrency: 'EUR',
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
itemCondition: 'https://schema.org/NewCondition',
},
additionalProperty: technicalItems.map((item: any) => ({
'@type': 'PropertyValue',
name: item.label,
value: item.value,
})),
category: product.frontmatter.categories.join(', '),
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
},
} as any}
/>
</div>
</div>

View File

@@ -0,0 +1,83 @@
import { ImageResponse } from 'next/og';
import { getProductBySlug } from '@/lib/mdx';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'nodejs';
export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug?: string[] } }) {
const t = await getTranslations('Products');
// If no slug, it's the main products page
if (!slug || slug.length === 0) {
const title = t('meta.title') || t('title');
const description = t('meta.description') || t('subtitle');
return new ImageResponse(
(
<OGImageTemplate
title={title}
description={description}
label="Products"
/>
),
{
width: 1200,
height: 630,
}
);
}
const productSlug = slug[slug.length - 1];
// Check if it's a category page
const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
if (categories.includes(productSlug)) {
const categoryKey = productSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : productSlug;
const categoryDesc = t.has(`categories.${categoryKey}.description`) ? t(`categories.${categoryKey}.description`) : '';
return new ImageResponse(
(
<OGImageTemplate
title={categoryTitle}
description={categoryDesc}
label="Product Category"
/>
),
{
width: 1200,
height: 630,
}
);
}
const product = await getProductBySlug(productSlug, locale);
if (!product) {
return new ImageResponse(
<div style={{ display: 'flex', width: '100%', height: '100%', backgroundColor: '#001a4d' }} />
);
}
const featuredImage = product.frontmatter.images?.[0]
? (product.frontmatter.images[0].startsWith('http')
? product.frontmatter.images[0]
: `https://klz-cables.com${product.frontmatter.images[0]}`)
: undefined;
return new ImageResponse(
(
<OGImageTemplate
title={product.frontmatter.title}
description={product.frontmatter.description}
label={product.frontmatter.categories?.[0] || 'Product'}
image={featuredImage}
/>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -1,11 +1,12 @@
import Reveal from '@/components/Reveal';
import Scribble from '@/components/Scribble';
import { Badge, Button, Card, Container, Section } from '@/components/ui';
import { Badge, Button, Card, Container, Heading, Section } from '@/components/ui';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';
import { mapFileSlugToTranslated } from '@/lib/slugs';
import { getOGImageMetadata } from '@/lib/metadata';
interface ProductsPageProps {
params: {
@@ -15,9 +16,11 @@ interface ProductsPageProps {
export async function generateMetadata({ params: { locale } }: ProductsPageProps): Promise<Metadata> {
const t = await getTranslations({ locale, namespace: 'Products' });
const title = t('meta.title') || t('title');
const description = t('meta.description') || t('subtitle');
return {
title: t('title'),
description: t('subtitle'),
title,
description,
alternates: {
canonical: `/${locale}/products`,
languages: {
@@ -27,14 +30,15 @@ export async function generateMetadata({ params: { locale } }: ProductsPageProps
},
},
openGraph: {
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
title: `${title} | KLZ Cables`,
description,
url: `https://klz-cables.com/${locale}/products`,
images: getOGImageMetadata('products', title, locale),
},
twitter: {
card: 'summary_large_image',
title: `${t('title')} | KLZ Cables`,
description: t('subtitle'),
title: `${title} | KLZ Cables`,
description,
},
};
}
@@ -88,7 +92,7 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg px-3 py-1 md:px-4 md:py-1.5">
{t('heroSubtitle')}
</Badge>
<h1 className="text-4xl md:text-7xl lg:text-8xl font-extrabold text-white mb-4 md:mb-8 tracking-tight leading-[1.05]">
<Heading level={1} className="text-white mb-4 md:mb-8">
{t.rich('title', {
green: (chunks) => (
<span className="relative inline-block">
@@ -97,8 +101,8 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
</span>
)
})}
</h1>
<p className="text-lg md:text-2xl text-white/70 leading-relaxed max-w-2xl mb-8 md:mb-12 line-clamp-2 md:line-clamp-none">
</Heading>
<p className="text-lg md:text-xl text-white/70 leading-relaxed max-w-2xl mb-8 md:mb-12 line-clamp-2 md:line-clamp-none">
{t('subtitle')}
</p>
<div className="flex flex-wrap gap-4 md:gap-6">
@@ -166,25 +170,27 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
</Section>
{/* Technical Support CTA */}
<Section className="bg-white py-12 md:py-28">
<Container>
<div className="bg-primary-dark rounded-[32px] md:rounded-[64px] p-6 md:p-20 lg:p-24 relative overflow-hidden">
<div className="absolute top-0 right-0 w-1/2 h-full bg-accent/5 -skew-x-12 translate-x-1/4" />
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-6 md:gap-12">
<div className="max-w-2xl text-center lg:text-left">
<h2 className="text-2xl md:text-5xl lg:text-6xl font-bold text-white mb-4 md:mb-8 tracking-tight">{t('cta.title')}</h2>
<p className="text-base md:text-xl text-white/70 leading-relaxed">
{t('cta.description')}
</p>
<Reveal>
<Section className="bg-white py-12 md:py-28">
<Container>
<div className="bg-primary-dark rounded-[32px] md:rounded-[64px] p-6 md:p-20 lg:p-24 relative overflow-hidden">
<div className="absolute top-0 right-0 w-1/2 h-full bg-accent/5 -skew-x-12 translate-x-1/4" />
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between gap-6 md:gap-12">
<div className="max-w-2xl text-center lg:text-left">
<h2 className="text-2xl md:text-5xl lg:text-6xl font-bold text-white mb-4 md:mb-8 tracking-tight">{t('cta.title')}</h2>
<p className="text-base md:text-xl text-white/70 leading-relaxed">
{t('cta.description')}
</p>
</div>
<Button href={`/${params.locale}/contact`} variant="accent" size="lg" className="group whitespace-nowrap w-full md:w-auto md:h-16 md:px-10 md:text-xl">
{t('cta.button')}
<span className="ml-4 transition-transform group-hover:translate-x-2">&rarr;</span>
</Button>
</div>
<Button href={`/${params.locale}/contact`} variant="accent" size="lg" className="group whitespace-nowrap w-full md:w-auto md:h-16 md:px-10 md:text-xl">
{t('cta.button')}
<span className="ml-4 transition-transform group-hover:translate-x-2">&rarr;</span>
</Button>
</div>
</div>
</Container>
</Section>
</Container>
</Section>
</Reveal>
</div>
);
}

View File

@@ -0,0 +1,26 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'nodejs';
export default async function Image({ params: { locale } }: { params: { locale: string } }) {
const t = await getTranslations({ locale, namespace: 'Team' });
const title = t('meta.title') || t('hero.subtitle');
const description = t('meta.description') || t('hero.title');
return new ImageResponse(
(
<OGImageTemplate
title={title}
description={description}
label="Our Team"
image="https://klz-cables.com/uploads/2024/12/DSC07655-Large.webp"
/>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -1,9 +1,12 @@
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';
import JsonLd from '@/components/JsonLd';
import { getBreadcrumbSchema, SITE_URL } from '@/lib/schema';
import { Section, Container, Heading, Badge, Button } from '@/components/ui';
import { getOGImageMetadata } from '@/lib/metadata';
import Image from 'next/image';
import Reveal from '@/components/Reveal';
import Gallery from '@/components/team/Gallery';
interface TeamPageProps {
params: {
@@ -13,9 +16,11 @@ interface TeamPageProps {
export async function generateMetadata({ params: { locale } }: TeamPageProps): Promise<Metadata> {
const t = await getTranslations({ locale, namespace: 'Team' });
const title = t('meta.title') || t('hero.subtitle');
const description = t('meta.description') || t('hero.title');
return {
title: t('hero.subtitle'),
description: t('hero.title'),
title,
description,
alternates: {
canonical: `/${locale}/team`,
languages: {
@@ -25,46 +30,89 @@ export async function generateMetadata({ params: { locale } }: TeamPageProps): P
},
},
openGraph: {
title: `${t('hero.subtitle')} | KLZ Cables`,
description: t('hero.title'),
title: `${title} | KLZ Cables`,
description,
url: `https://klz-cables.com/${locale}/team`,
images: getOGImageMetadata('team', title, locale),
},
twitter: {
card: 'summary_large_image',
title: `${t('hero.subtitle')} | KLZ Cables`,
description: t('hero.title'),
title: `${title} | KLZ Cables`,
description,
},
};
}
export default function TeamPage() {
const t = useTranslations('Team');
export default async function TeamPage({ params: { locale } }: TeamPageProps) {
const t = await getTranslations({ locale, namespace: 'Team' });
return (
<div className="flex flex-col min-h-screen bg-neutral-light">
<JsonLd
id="breadcrumb-team"
data={getBreadcrumbSchema([
{ name: t('hero.subtitle'), item: `/team` },
])}
/>
<JsonLd
id="person-michael"
data={{
'@context': 'https://schema.org',
'@type': 'Person',
name: t('michael.name'),
jobTitle: t('michael.role'),
worksFor: {
'@type': 'Organization',
name: 'KLZ Cables',
},
sameAs: [
'https://www.linkedin.com/in/michael-bodemer-33b493122/'
],
image: `${SITE_URL}/uploads/2024/12/DSC07768-Large.webp`
}}
/>
<JsonLd
id="person-klaus"
data={{
'@context': 'https://schema.org',
'@type': 'Person',
name: t('klaus.name'),
jobTitle: t('klaus.role'),
worksFor: {
'@type': 'Organization',
name: 'KLZ Cables',
},
sameAs: [
'https://www.linkedin.com/in/klaus-mintel-b80a8b193/'
],
image: `${SITE_URL}/uploads/2024/12/DSC07963-Large.webp`
}}
/>
{/* Hero Section */}
<section className="relative flex items-center justify-center overflow-hidden bg-primary-dark pt-32 pb-24 md:pt-[14%] md:pb-[12%]">
<div className="absolute inset-0 z-0">
<Image
src="/uploads/2024/12/DSC07655-Large.webp"
alt="KLZ Team"
fill
className="object-cover scale-105 animate-slow-zoom opacity-30 md:opacity-40"
priority
/>
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
</div>
<Container className="relative z-10 text-center text-white max-w-5xl animate-slide-up">
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">{t('hero.badge')}</Badge>
<h1 className="text-3xl md:text-7xl lg:text-8xl font-extrabold tracking-tight leading-[1.1] mb-4 md:mb-8">
{t('hero.subtitle')}
</h1>
<p className="text-lg md:text-3xl text-white/70 font-medium italic">
{t('hero.title')}
</p>
</Container>
</section>
<Reveal>
<section className="relative flex items-center justify-center overflow-hidden bg-primary-dark pt-32 pb-24 md:pt-[14%] md:pb-[12%]">
<div className="absolute inset-0 z-0">
<Image
src="/uploads/2024/12/DSC07655-Large.webp"
alt="KLZ Team"
fill
className="object-cover scale-105 animate-slow-zoom opacity-30 md:opacity-40"
priority
/>
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
</div>
<Container className="relative z-10 text-center text-white max-w-5xl">
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">{t('hero.badge')}</Badge>
<Heading level={1} className="text-white mb-4 md:mb-8">
{t('hero.subtitle')}
</Heading>
<p className="text-lg md:text-2xl text-white/70 font-medium italic">
{t('hero.title')}
</p>
</Container>
</section>
</Reveal>
{/* Michael Bodemer Section - Sticky Narrative Split Layout */}
<section className="relative bg-white overflow-hidden">
@@ -73,12 +121,12 @@ export default function TeamPage() {
<div className="absolute top-0 right-0 w-32 h-full bg-accent/5 -skew-x-12 translate-x-1/2" />
<div className="relative z-10">
<Badge variant="accent" className="mb-4 md:mb-8">{t('michael.role')}</Badge>
<Heading level={2} className="text-white mb-6 md:mb-10 text-3xl md:text-6xl">
<Heading level={2} className="text-white mb-6 md:mb-10 text-3xl md:text-5xl">
<span className="text-white">{t('michael.name')}</span>
</Heading>
<div className="relative mb-6 md:mb-12">
<div className="absolute -left-4 md:-left-8 top-0 bottom-0 w-1 md:w-1.5 bg-accent rounded-full" />
<p className="text-lg md:text-3xl font-bold italic leading-relaxed pl-5 md:pl-8 text-white/90">
<p className="text-lg md:text-2xl font-bold italic leading-relaxed pl-5 md:pl-8 text-white/90">
{t('michael.quote')}
</p>
</div>
@@ -96,73 +144,75 @@ export default function TeamPage() {
</Button>
</div>
</Reveal>
<div className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1 lg:order-2">
<Image
src="/uploads/2024/12/DSC07768-Large.webp"
alt={t('michael.name')}
fill
<Reveal className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1 lg:order-2">
<Image
src="/uploads/2024/12/DSC07768-Large.webp"
alt={t('michael.name')}
fill
className="object-cover scale-105 hover:scale-100 transition-transform duration-1000"
sizes="(max-width: 1024px) 100vw, 50vw"
/>
<div className="absolute inset-0 bg-gradient-to-t from-primary-dark/60 lg:bg-gradient-to-r lg:from-primary-dark/20 to-transparent" />
</div>
</Reveal>
</div>
</section>
{/* Legacy Section - Immersive Background */}
<section className="relative py-16 md:py-48 bg-primary-dark text-white overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src="/uploads/2024/12/1694273920124-copy.webp"
alt={t('legacy.subtitle')}
fill
className="object-cover opacity-20 md:opacity-30 scale-110 animate-slow-zoom"
sizes="100vw"
/>
<div className="absolute inset-0 bg-primary-dark/60 mix-blend-multiply" />
</div>
<Container className="relative z-10">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16 items-center">
<div className="lg:col-span-6">
<Heading level={2} subtitle={t('legacy.subtitle')} className="text-white mb-6 md:mb-10">
<span className="text-white">{t('legacy.title')}</span>
</Heading>
<div className="space-y-4 md:space-y-8 text-base md:text-2xl text-white/80 leading-relaxed font-medium">
<p className="border-l-4 border-accent pl-5 md:pl-8 py-2 bg-white/5 backdrop-blur-sm rounded-r-xl md:rounded-r-2xl">
{t('legacy.p1')}
</p>
<p className="pl-6 md:pl-9 line-clamp-3 md:line-clamp-none">
{t('legacy.p2')}
</p>
</div>
</div>
<div className="lg:col-span-6 grid grid-cols-2 md:grid-cols-2 gap-3 md:gap-6">
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.expertise')}</div>
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.expertiseDesc')}</div>
</div>
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.network')}</div>
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.networkDesc')}</div>
</div>
</div>
<Reveal>
<section className="relative py-16 md:py-48 bg-primary-dark text-white overflow-hidden">
<div className="absolute inset-0 z-0">
<Image
src="/uploads/2024/12/1694273920124-copy.webp"
alt={t('legacy.subtitle')}
fill
className="object-cover opacity-20 md:opacity-30 scale-110 animate-slow-zoom"
sizes="100vw"
/>
<div className="absolute inset-0 bg-primary-dark/60 mix-blend-multiply" />
</div>
</Container>
</section>
<Container className="relative z-10">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 md:gap-16 items-center">
<div className="lg:col-span-6">
<Heading level={2} subtitle={t('legacy.subtitle')} className="text-white mb-6 md:mb-10">
<span className="text-white">{t('legacy.title')}</span>
</Heading>
<div className="space-y-4 md:space-y-8 text-base md:text-2xl text-white/80 leading-relaxed font-medium">
<p className="border-l-4 border-accent pl-5 md:pl-8 py-2 bg-white/5 backdrop-blur-sm rounded-r-xl md:rounded-r-2xl">
{t('legacy.p1')}
</p>
<p className="pl-6 md:pl-9 line-clamp-3 md:line-clamp-none">
{t('legacy.p2')}
</p>
</div>
</div>
<div className="lg:col-span-6 grid grid-cols-2 md:grid-cols-2 gap-3 md:gap-6">
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.expertise')}</div>
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.expertiseDesc')}</div>
</div>
<div className="p-4 md:p-8 bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl md:rounded-[32px] hover:bg-white/10 transition-colors">
<div className="text-xl md:text-4xl font-extrabold text-accent mb-1 md:mb-2">{t('legacy.network')}</div>
<div className="text-[10px] md:text-sm font-bold uppercase tracking-widest text-white/50">{t('legacy.networkDesc')}</div>
</div>
</div>
</div>
</Container>
</section>
</Reveal>
{/* Klaus Mintel Section - Reversed Split Layout */}
<section className="relative bg-white overflow-hidden">
<div className="flex flex-col lg:flex-row">
<div className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1">
<Image
src="/uploads/2024/12/DSC07963-Large.webp"
alt={t('klaus.name')}
fill
<Reveal className="w-full lg:w-1/2 relative min-h-[400px] md:min-h-[600px] lg:min-h-screen overflow-hidden order-1">
<Image
src="/uploads/2024/12/DSC07963-Large.webp"
alt={t('klaus.name')}
fill
className="object-cover scale-105 hover:scale-100 transition-transform duration-1000"
sizes="(max-width: 1024px) 100vw, 50vw"
/>
<div className="absolute inset-0 bg-gradient-to-t from-white/60 lg:bg-gradient-to-l lg:from-primary-dark/20 to-transparent" />
</div>
</Reveal>
<Reveal className="w-full lg:w-1/2 p-6 md:p-24 lg:p-32 flex flex-col justify-center bg-neutral-light text-saturated relative order-2">
<div className="absolute top-0 left-0 w-32 h-full bg-saturated/5 skew-x-12 -translate-x-1/2" />
<div className="relative z-10">
@@ -231,29 +281,9 @@ export default function TeamPage() {
</Container>
</Section>
{/* Gallery Section - Premium Treatment */}
<Section className="bg-primary-dark py-16 md:py-32">
<Container>
<Heading level={2} subtitle={t('gallery.subtitle')} align="center" className="text-white mb-12 md:mb-20">
<span className="text-white">{t('gallery.title')}</span>
</Heading>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-6">
{[
'/uploads/2024/12/DSC07539-Large-600x400.webp',
'/uploads/2024/12/DSC07460-Large-600x400.webp',
'/uploads/2024/12/DSC07469-Large-600x400.webp',
'/uploads/2024/12/DSC07433-Large-600x400.webp',
'/uploads/2024/12/DSC07387-Large-600x400.webp'
].map((src, idx) => (
<div key={idx} className="relative aspect-[3/4] rounded-2xl md:rounded-[32px] overflow-hidden group shadow-2xl">
<Image src={src} alt={t('gallery.title')} fill className="object-cover transition-transform duration-1000 group-hover:scale-110" />
<div className="absolute inset-0 bg-primary-dark/40 group-hover:bg-transparent transition-all duration-500" />
<div className="absolute inset-0 border-0 group-hover:border-[8px] md:group-hover:border-[12px] border-white/10 transition-all duration-500 pointer-events-none" />
</div>
))}
</div>
</Container>
</Section>
<Reveal>
<Gallery />
</Reveal>
</div>
);
}

46
app/actions/contact.ts Normal file
View File

@@ -0,0 +1,46 @@
"use server";
import { sendEmail } from "@/lib/mail/mailer";
import ContactEmail from "@/components/emails/ContactEmail";
import React from "react";
import { getServerAppServices } from "@/lib/services/create-services.server";
export async function sendContactFormAction(formData: FormData) {
const services = getServerAppServices();
const logger = services.logger.child({ action: 'sendContactFormAction' });
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const message = formData.get("message") as string;
const productName = formData.get("productName") as string | null;
if (!name || !email || !message) {
logger.warn('Missing required fields in contact form', { name: !!name, email: !!email, message: !!message });
return { success: false, error: "Missing required fields" };
}
logger.info('Sending contact form email', { email, productName });
const subject = productName
? `Product Inquiry: ${productName}`
: "New Contact Form Submission";
const result = await sendEmail({
subject,
template: React.createElement(ContactEmail, {
name,
email,
message,
productName: productName || undefined,
subject,
}),
});
if (result.success) {
logger.info('Contact form email sent successfully', { messageId: result.messageId });
} else {
logger.error('Failed to send contact form email', { error: result.error });
services.errors.captureException(result.error, { action: 'sendContactFormAction', email });
}
return result;
}

View File

@@ -1,5 +1,9 @@
import { getServerAppServices } from '@/lib/services/create-services.server';
export const dynamic = 'force-dynamic';
export async function GET() {
const services = getServerAppServices();
services.logger.debug('Health check requested');
return new Response('OK', { status: 200 });
}

View File

@@ -2,11 +2,17 @@ import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/health/'],
},
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/api/', '/health/'],
},
{
userAgent: ['GPTBot', 'ClaudeBot', 'PerplexityBot', 'Google-Extended', 'OAI-SearchBot'],
allow: '/',
}
],
sitemap: 'https://klz-cables.com/sitemap.xml',
};
}

148
components/ContactForm.tsx Normal file
View File

@@ -0,0 +1,148 @@
'use client';
import React, { useState } from 'react';
import { useTranslations } from 'next-intl';
import { Button, Heading, Card, Input, Textarea, Label } from '@/components/ui';
import { sendContactFormAction } from '@/app/actions/contact';
import { useAnalytics } from '@/components/analytics/useAnalytics';
export default function ContactForm() {
const t = useTranslations('Contact');
const { trackEvent } = useAnalytics();
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('submitting');
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
try {
const result = await sendContactFormAction(formData);
if (result.success) {
trackEvent('contact_form_submission', {
form_type: 'general',
email,
});
setStatus('success');
(e.target as HTMLFormElement).reset();
} else {
console.error('Contact form submission failed:', { email, error: result.error });
setStatus('error');
}
} catch (error) {
console.error('Contact form submission error:', { email, error });
setStatus('error');
}
}
if (status === 'success') {
return (
<Card className="p-6 md:p-12 rounded-2xl md:rounded-[40px] border-none shadow-2xl text-center">
<div className="w-20 h-20 bg-accent rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg shadow-accent/20">
<svg className="w-10 h-10 text-primary-dark" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</div>
<Heading level={3} className="mb-4">
{t('form.successTitle') || 'Message Sent!'}
</Heading>
<p className="text-text-secondary text-lg mb-8">
{t('form.successDesc') || 'Thank you for your message. We will get back to you as soon as possible.'}
</p>
<Button onClick={() => setStatus('idle')} variant="saturated">
{t('form.sendAnother') || 'Send another message'}
</Button>
</Card>
);
}
if (status === 'error') {
return (
<Card className="p-6 md:p-12 rounded-2xl md:rounded-[40px] border-destructive/20 shadow-2xl text-center bg-destructive/5 animate-slide-up">
<div className="w-20 h-20 bg-destructive rounded-full flex items-center justify-center mx-auto mb-6 shadow-lg shadow-destructive/20">
<svg className="w-10 h-10 text-destructive-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" />
<line x1="15" y1="9" x2="9" y2="15" strokeLinecap="round" strokeLinejoin="round" />
<line x1="9" y1="9" x2="15" y2="15" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<Heading level={3} className="mb-4 text-destructive font-black">
{t('form.errorTitle') || 'Submission Failed!'}
</Heading>
<p className="text-destructive/80 text-lg mb-8 leading-relaxed font-medium">
{t('form.error') || 'Something went wrong. Please check your input and try again.'}
</p>
<Button onClick={() => setStatus('idle')} variant="saturated" size="lg" className="w-full bg-destructive hover:bg-destructive/90 text-destructive-foreground border-destructive shadow-lg shadow-destructive/20">
{t('form.tryAgain') || 'Try Again'}
</Button>
</Card>
);
}
return (
<Card className="p-6 md:p-12 rounded-2xl md:rounded-[40px] border-none shadow-2xl animate-slide-up">
<Heading level={3} subtitle={t('form.subtitle')} className="mb-6 md:mb-10">
{t('form.title')}
</Heading>
<form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8">
<div className="space-y-1 md:space-y-2">
<Label htmlFor="name">{t('form.name')}</Label>
<Input
type="text"
id="name"
name="name"
autoComplete="name"
enterKeyHint="next"
placeholder={t('form.namePlaceholder')}
required
/>
</div>
<div className="space-y-1 md:space-y-2">
<Label htmlFor="email">{t('form.email')}</Label>
<Input
type="email"
id="email"
name="email"
autoComplete="email"
inputMode="email"
enterKeyHint="next"
placeholder={t('form.emailPlaceholder')}
required
/>
</div>
<div className="md:col-span-2 space-y-1 md:space-y-2">
<Label htmlFor="message">{t('form.message')}</Label>
<Textarea
id="message"
name="message"
rows={4}
enterKeyHint="send"
placeholder={t('form.messagePlaceholder')}
required
/>
</div>
<div className="md:col-span-2 pt-2 md:pt-4">
<Button
type="submit"
variant="saturated"
size="lg"
disabled={status === 'submitting'}
className="w-full shadow-xl shadow-saturated/20 md:h-16 md:px-10 md:text-xl active:scale-[0.98] transition-transform"
>
{status === 'submitting' ? (
<span className="flex items-center gap-2">
<svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
{t('form.submitting') || 'Sending...'}
</span>
) : t('form.submit')}
</Button>
</div>
</form>
</Card>
);
}

View File

@@ -27,11 +27,11 @@ export default function Footer() {
unoptimized
/>
</Link>
<p className="text-white/60 text-lg leading-relaxed max-w-sm">
<p className="text-white/60 text-base md:text-lg leading-relaxed max-w-sm">
{t('tagline')}
</p>
<div className="flex gap-4">
<a href="https://www.linkedin.com/company/klz-vertriebs-gmbh/" target="_blank" rel="noopener noreferrer" className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center hover:bg-accent hover:text-primary-dark transition-all duration-300 border border-white/10">
<a href="https://www.linkedin.com/company/klz-vertriebs-gmbh/" target="_blank" rel="noopener noreferrer" className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center text-white hover:bg-accent hover:text-primary-dark transition-all duration-300 border border-white/10">
<span className="sr-only">LinkedIn</span>
<svg className="w-5 h-5 fill-current" viewBox="0 0 24 24">
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
@@ -42,36 +42,50 @@ export default function Footer() {
{/* Links Columns */}
<div className="lg:col-span-2">
<h4 className="text-accent font-bold uppercase tracking-widest text-sm mb-8">{t('legal')}</h4>
<ul className="space-y-4 text-white/70">
<li><Link href={`/${locale}/${t('legalNoticeSlug')}`} className="hover:text-white transition-colors">{t('legalNotice')}</Link></li>
<li><Link href={`/${locale}/${t('privacyPolicySlug')}`} className="hover:text-white transition-colors">{t('privacyPolicy')}</Link></li>
<li><Link href={`/${locale}/${t('termsSlug')}`} className="hover:text-white transition-colors">{t('terms')}</Link></li>
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">{t('legal')}</h4>
<ul className="space-y-4 text-white/70 list-none m-0 p-0">
<li><Link href={`/${locale}/${t('legalNoticeSlug')}`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{t('legalNotice')}</Link></li>
<li><Link href={`/${locale}/${t('privacyPolicySlug')}`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{t('privacyPolicy')}</Link></li>
<li><Link href={`/${locale}/${t('termsSlug')}`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{t('terms')}</Link></li>
</ul>
</div>
<div className="lg:col-span-2">
<h4 className="text-accent font-bold uppercase tracking-widest text-sm mb-8">{t('company')}</h4>
<ul className="space-y-4 text-white/70">
<li><Link href={`/${locale}/team`} className="hover:text-white transition-colors">{navT('team')}</Link></li>
<li><Link href={`/${locale}/products`} className="hover:text-white transition-colors">{navT('products')}</Link></li>
<li><Link href={`/${locale}/blog`} className="hover:text-white transition-colors">{navT('blog')}</Link></li>
<li><Link href={`/${locale}/contact`} className="hover:text-white transition-colors">{navT('contact')}</Link></li>
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">{t('company')}</h4>
<ul className="space-y-4 text-white/70 list-none m-0 p-0">
<li><Link href={`/${locale}/team`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('team')}</Link></li>
<li><Link href={`/${locale}/products`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('products')}</Link></li>
<li><Link href={`/${locale}/blog`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('blog')}</Link></li>
<li><Link href={`/${locale}/contact`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('contact')}</Link></li>
</ul>
</div>
{/* Recent Posts Column */}
<div className="lg:col-span-4">
<h4 className="text-accent font-bold uppercase tracking-widest text-sm mb-8">{t('recentPosts')}</h4>
<ul className="space-y-6">
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">{t('recentPosts')}</h4>
<ul className="space-y-6 list-none m-0 p-0">
{[
"Focus on wind farm construction: three typical cable challenges",
"Why the N2XS(F)2Y is the ideal cable for your energy project"
{
title: locale === 'de'
? "Windparkbau im Fokus: drei typische Kabelherausforderungen"
: "Focus on wind farm construction: three typical cable challenges",
slug: locale === 'de'
? "windparkbau-im-fokus-drei-typische-kabelherausforderungen"
: "focus-on-wind-farm-construction-three-typical-cable-challenges"
},
{
title: locale === 'de'
? "Warum das N2XS(F)2Y das ideale Kabel für Ihr Energieprojekt ist"
: "Why the N2XS(F)2Y is the ideal cable for your energy project",
slug: locale === 'de'
? "n2xsf2y-mittelspannungskabel-energieprojekt"
: "why-the-n2xsf2y-is-the-ideal-cable-for-your-energy-project"
}
].map((post, i) => (
<li key={i}>
<Link href="#" className="group block">
<p className="text-white/80 font-bold group-hover:text-accent transition-colors leading-snug mb-2">
{post}
<Link href={`/${locale}/blog/${post.slug}`} className="group block text-white/80">
<p className="text-white/80 font-bold group-hover:text-accent transition-colors leading-snug mb-2 text-base md:text-base">
{post.title}
</p>
<span className="text-xs text-white/40 uppercase tracking-widest">{t('readArticle')} &rarr;</span>
</Link>
@@ -81,11 +95,11 @@ export default function Footer() {
</div>
</div>
<div className="pt-12 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-8 text-white/40 text-sm font-medium">
<div className="pt-12 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-8 text-white/40 text-xs md:text-sm font-medium">
<p>{t('copyright', { year: currentYear })}</p>
<div className="flex gap-8">
<Link href="/en" className="hover:text-white transition-colors">English</Link>
<Link href="/de" className="hover:text-white transition-colors">Deutsch</Link>
<Link href="/en" locale="en" className="hover:text-white transition-colors">English</Link>
<Link href="/de" locale="de" className="hover:text-white transition-colors">Deutsch</Link>
</div>
</div>
</Container>

View File

@@ -2,6 +2,7 @@
import Link from 'next/link';
import Image from 'next/image';
import { motion } from 'framer-motion';
import { useTranslations } from 'next-intl';
import { usePathname } from 'next/navigation';
import { Button } from './ui';
@@ -58,7 +59,7 @@ export default function Header() {
];
const headerClass = cn(
"fixed top-0 left-0 right-0 z-50 transition-all duration-500 safe-area-p",
"fixed top-0 left-0 right-0 z-50 transition-all duration-500 safe-area-p transform-gpu",
{
"bg-transparent py-4 md:py-8": isHomePage && !isScrolled && !isMobileMenuOpen,
"bg-primary py-3 md:py-4 shadow-2xl": !isHomePage || isScrolled || isMobileMenuOpen,
@@ -70,79 +71,168 @@ export default function Header() {
return (
<>
<header className={headerClass}>
<motion.header
className={headerClass}
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
<div className="container mx-auto px-4 md:px-12 lg:px-16 max-w-7xl flex items-center justify-between">
<Link href={`/${currentLocale}`} className="flex-shrink-0 group touch-target">
<Image
src={logoSrc}
alt={t('home')}
width={120}
height={120}
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110"
priority
unoptimized
/>
</Link>
<motion.div
className="flex-shrink-0 group touch-target"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.6, ease: "easeOut", delay: 0.1 }}
>
<Link href={`/${currentLocale}`}>
<Image
src={logoSrc}
alt={t('home')}
width={120}
height={120}
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110"
priority
unoptimized
/>
</Link>
</motion.div>
<div className="flex items-center gap-4 md:gap-12">
<nav className="hidden lg:flex items-center space-x-10">
{menuItems.map((item) => (
<Link
key={item.href}
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
className={cn(
textColorClass,
"hover:text-accent font-bold transition-all text-lg tracking-tight relative group"
)}
<motion.div
className="flex items-center gap-4 md:gap-12"
initial="hidden"
animate="visible"
variants={{
visible: {
transition: {
staggerChildren: 0.08,
delayChildren: 0.3
}
}
}}
>
<motion.nav
className="hidden lg:flex items-center space-x-10"
variants={navVariants}
>
{menuItems.map((item, idx) => (
<motion.div
key={item.href}
variants={navLinkVariants}
>
{item.label}
<span className="absolute -bottom-2 left-0 w-0 h-1 bg-accent transition-all duration-300 group-hover:w-full rounded-full" />
</Link>
<Link
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
className={cn(
textColorClass,
"hover:text-accent font-bold transition-all duration-500 text-base md:text-lg tracking-tight relative group inline-block hover:-translate-y-0.5"
)}
>
{item.label}
<span className="absolute -bottom-2 left-0 w-0 h-1 bg-accent transition-all duration-500 group-hover:w-full rounded-full shadow-[0_0_12px_rgba(130,237,32,0.6)]" />
</Link>
</motion.div>
))}
</nav>
</motion.nav>
<div className={cn("hidden lg:flex items-center space-x-8", textColorClass)}>
<div className="flex items-center space-x-4 text-sm font-extrabold tracking-widest uppercase">
<Link
href={getPathForLocale('en')}
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
>
EN
</Link>
<div className="w-px h-4 bg-current opacity-20" />
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</div>
<Button
href={`/${currentLocale}/contact`}
variant="white"
size="md"
className="px-8 shadow-xl"
<motion.div
className={cn("hidden lg:flex items-center space-x-8", textColorClass)}
variants={headerRightVariants}
>
<motion.div
className="flex items-center space-x-4 text-xs md:text-sm font-extrabold tracking-widest uppercase"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.6 }}
>
{t('contact')}
</Button>
</div>
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.65 }}
>
<Link
href={getPathForLocale('en')}
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
>
EN
</Link>
</motion.div>
<motion.div
className="w-px h-4 bg-current opacity-20"
initial={{ scaleY: 0 }}
animate={{ scaleY: 1 }}
transition={{ duration: 0.4, delay: 0.7 }}
/>
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.75 }}
>
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors flex items-center gap-2 touch-target ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</motion.div>
</motion.div>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.6, type: "spring", stiffness: 400, delay: 0.7 }}
>
<Button
href={`/${currentLocale}/contact`}
variant="white"
size="md"
className="px-8 shadow-xl"
>
{t('contact')}
</Button>
</motion.div>
</motion.div>
{/* Mobile Menu Button */}
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className={cn("lg:hidden touch-target p-2 rounded-xl bg-white/10 backdrop-blur-md border border-white/20 z-50", textColorClass)}
<motion.button
className={cn("lg:hidden touch-target p-2 rounded-xl bg-white/10 border border-white/20 z-50", textColorClass)}
aria-label={t('toggleMenu')}
initial={{ scale: 0.8, opacity: 0, rotate: -180 }}
animate={{ scale: 1, opacity: 1, rotate: 0 }}
transition={{ duration: 0.6, type: "spring", stiffness: 300, damping: 20, delay: 0.5 }}
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<motion.svg
className="w-7 h-7"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.3, delay: 0.6 }}
>
{isMobileMenuOpen ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
<motion.path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.4, delay: 0.7 }}
/>
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
<motion.path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.4, delay: 0.7 }}
/>
)}
</svg>
</button>
</div>
</motion.svg>
</motion.button>
</motion.div>
</div>
{/* Mobile Menu Overlay */}
@@ -150,58 +240,156 @@ export default function Header() {
"fixed inset-0 bg-primary z-40 lg:hidden transition-all duration-500 ease-in-out flex flex-col",
isMobileMenuOpen ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-full pointer-events-none"
)}>
<div className="flex-grow flex flex-col justify-center items-center p-8 space-y-8">
<motion.div
className="flex-grow flex flex-col justify-center items-center p-8 space-y-8"
initial="closed"
animate={isMobileMenuOpen ? "open" : "closed"}
variants={{
open: {
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
}}
>
{menuItems.map((item, idx) => (
<Link
key={item.href}
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
className={cn(
"text-3xl font-extrabold text-white hover:text-accent transition-all transform",
isMobileMenuOpen ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"
)}
style={{ transitionDelay: `${idx * 100}ms` }}
<motion.div
key={item.href}
variants={{
closed: { opacity: 0, y: 50, scale: 0.9 },
open: {
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.6,
ease: "easeOut",
delay: idx * 0.08
}
}
}}
>
{item.label}
</Link>
<Link
href={`/${currentLocale}${item.href === '/' ? '' : item.href}`}
className="text-2xl md:text-3xl font-extrabold text-white hover:text-accent transition-all transform block py-4"
>
{item.label}
</Link>
</motion.div>
))}
<div className={cn(
"pt-8 border-t border-white/10 w-full flex flex-col items-center space-y-8 transition-all duration-500 delay-500",
isMobileMenuOpen ? "translate-y-0 opacity-100" : "translate-y-10 opacity-0"
)}>
<div className="flex items-center space-x-8 text-xl font-extrabold tracking-widest uppercase text-white">
<Link
href={getPathForLocale('en')}
className={`hover:text-accent transition-colors ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
>
EN
</Link>
<div className="w-px h-6 bg-white/20" />
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</div>
<Button
href={`/${currentLocale}/contact`}
variant="accent"
size="lg"
className="w-full max-w-xs py-6 text-xl shadow-2xl"
<motion.div
className="pt-8 border-t border-white/10 w-full flex flex-col items-center space-y-8"
initial={{ opacity: 0, y: 30 }}
animate={isMobileMenuOpen ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
transition={{ duration: 0.5, delay: 0.8 }}
>
<motion.div
className="flex items-center space-x-8 text-lg md:text-xl font-extrabold tracking-widest uppercase text-white"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4, delay: 0.9 }}
>
{t('contact')}
</Button>
</div>
</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3, delay: 1.0 }}
>
<Link
href={getPathForLocale('en')}
className={`hover:text-accent transition-colors ${currentLocale === 'en' ? 'text-accent' : 'opacity-60'}`}
>
EN
</Link>
</motion.div>
<motion.div
className="w-px h-6 bg-white/20"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.4, delay: 1.05 }}
/>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3, delay: 1.1 }}
>
<Link
href={getPathForLocale('de')}
className={`hover:text-accent transition-colors ${currentLocale === 'de' ? 'text-accent' : 'opacity-60'}`}
>
DE
</Link>
</motion.div>
</motion.div>
<motion.div
initial={{ scale: 0.9, opacity: 0, y: 20 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 400, damping: 20, delay: 1.2 }}
>
<Button
href={`/${currentLocale}/contact`}
variant="accent"
size="lg"
className="w-full max-w-xs py-6 text-lg md:text-xl shadow-2xl"
>
{t('contact')}
</Button>
</motion.div>
</motion.div>
{/* Bottom Branding */}
<div className="p-12 flex justify-center opacity-20">
<Image src="/logo-white.svg" alt={t('home')} width={80} height={80} unoptimized />
</div>
<motion.div
className="p-12 flex justify-center opacity-20"
initial={{ opacity: 0, scale: 0.8 }}
animate={isMobileMenuOpen ? { opacity: 0.2, scale: 1 } : { opacity: 0, scale: 0.8 }}
transition={{ duration: 0.5, delay: 1.4 }}
>
<motion.div
initial={{ scale: 0.5 }}
animate={{ scale: 1 }}
transition={{ type: "spring", stiffness: 300, delay: 1.5 }}
>
<Image src="/logo-white.svg" alt={t('home')} width={80} height={80} unoptimized />
</motion.div>
</motion.div>
</motion.div>
</div>
</header>
</motion.header>
</>
);
}
const navVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.06,
delayChildren: 0.1
}
}
} as const;
const navLinkVariants = {
hidden: { opacity: 0, y: 20, scale: 0.9 },
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.5,
ease: "easeOut"
}
}
} as const;
const headerRightVariants = {
hidden: { opacity: 0, x: 30 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.6, ease: "easeOut" }
}
} as const;

51
components/JsonLd.tsx Normal file
View File

@@ -0,0 +1,51 @@
import { Thing, WithContext } from 'schema-dts';
interface JsonLdProps {
id?: string;
data?: WithContext<Thing> | WithContext<Thing>[];
}
export default function JsonLd({ id, data }: JsonLdProps) {
// If data is provided, use it. Otherwise, use the default Organization + WebSite schema.
const schemaData = data || [
{
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'KLZ Cables',
url: 'https://klz-cables.com',
logo: 'https://klz-cables.com/logo-blue.svg',
sameAs: [
'https://www.linkedin.com/company/klz-cables',
],
description: 'Premium Cable Solutions for Renewable Energy and Infrastructure.',
address: {
'@type': 'PostalAddress',
addressCountry: 'DE',
},
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'KLZ Cables',
url: 'https://klz-cables.com',
potentialAction: {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: 'https://klz-cables.com/search?q={search_term_string}',
},
'query-input': 'required name=search_term_string',
},
}
];
return (
<script
id={id}
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(schemaData),
}}
/>
);
}

45
components/LeafletMap.tsx Normal file
View File

@@ -0,0 +1,45 @@
'use client';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
// Fix for default marker icon in Leaflet with Next.js
const DefaultIcon = L.icon({
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
});
L.Marker.prototype.options.icon = DefaultIcon;
interface LeafletMapProps {
address: string;
lat: number;
lng: number;
}
export default function LeafletMap({ address, lat, lng }: LeafletMapProps) {
const position: [number, number] = [lat, lng];
return (
<MapContainer
center={position}
zoom={15}
scrollWheelZoom={false}
className="h-full w-full z-0"
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={position}>
<Popup>
<div className="text-primary font-bold">KLZ Cables</div>
<div className="text-sm whitespace-pre-line">{address}</div>
</Popup>
</Marker>
</MapContainer>
);
}

204
components/Lightbox.tsx Normal file
View File

@@ -0,0 +1,204 @@
'use client';
import React, { useEffect, useState, useCallback } from 'react';
import Image from 'next/image';
import { createPortal } from 'react-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { useRouter, useSearchParams, usePathname } from 'next/navigation';
interface LightboxProps {
isOpen: boolean;
images: string[];
initialIndex: number;
onClose: () => void;
}
export default function Lightbox({ isOpen, images, initialIndex, onClose }: LightboxProps) {
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const [currentIndex, setCurrentIndex] = useState(initialIndex);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
const updateUrl = useCallback((index: number | null) => {
const params = new URLSearchParams(searchParams.toString());
if (index !== null) {
params.set('photo', index.toString());
} else {
params.delete('photo');
}
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
}, [pathname, router, searchParams]);
const prevImage = useCallback(() => {
setCurrentIndex((prev) => {
const next = prev === 0 ? images.length - 1 : prev - 1;
updateUrl(next);
return next;
});
}, [images.length, updateUrl]);
const nextImage = useCallback(() => {
setCurrentIndex((prev) => {
const next = prev === images.length - 1 ? 0 : prev + 1;
updateUrl(next);
return next;
});
}, [images.length, updateUrl]);
useEffect(() => {
const photoParam = searchParams.get('photo');
if (photoParam !== null) {
const index = parseInt(photoParam, 10);
if (!isNaN(index) && index >= 0 && index < images.length) {
setCurrentIndex(index);
}
}
}, [searchParams, images.length]);
useEffect(() => {
if (isOpen) {
updateUrl(currentIndex);
}
}, [isOpen, currentIndex, updateUrl]);
useEffect(() => {
if (!isOpen) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') handleClose();
if (e.key === 'ArrowLeft') prevImage();
if (e.key === 'ArrowRight') nextImage();
};
// Lock scroll
const originalStyle = window.getComputedStyle(document.body).overflow;
document.body.style.overflow = 'hidden';
window.addEventListener('keydown', handleKeyDown);
return () => {
document.body.style.overflow = originalStyle;
window.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, prevImage, nextImage]);
if (!mounted) return null;
const handleClose = () => {
updateUrl(null);
onClose();
};
return createPortal(
<AnimatePresence>
{isOpen && (
<div className="fixed inset-0 z-[99999] flex items-center justify-center">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
className="absolute inset-0 bg-primary/95 backdrop-blur-xl"
onClick={handleClose}
/>
<motion.button
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.5 }}
transition={{ delay: 0.1, duration: 0.4 }}
onClick={handleClose}
className="absolute top-6 right-6 text-white/60 hover:text-white transition-all duration-500 z-[10000] rounded-full w-14 h-14 flex items-center justify-center hover:bg-white/5 group border border-white/10"
aria-label="Close lightbox"
>
<div className="relative w-full h-full flex items-center justify-center group-hover:rotate-90 transition-transform duration-500">
<span className="text-3xl font-extralight leading-none mb-1">×</span>
</div>
</motion.button>
<motion.button
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ delay: 0.2, duration: 0.4 }}
onClick={prevImage}
className="absolute left-6 top-1/2 -translate-y-1/2 text-white/60 hover:text-white transition-all duration-500 w-14 h-14 flex items-center justify-center hover:bg-white/5 rounded-full z-[10000] group border border-white/10"
aria-label="Previous image"
>
<span className="text-4xl font-extralight group-hover:-translate-x-1 transition-transform duration-500"></span>
</motion.button>
<motion.button
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
transition={{ delay: 0.2, duration: 0.4 }}
onClick={nextImage}
className="absolute right-6 top-1/2 -translate-y-1/2 text-white/60 hover:text-white transition-all duration-500 w-14 h-14 flex items-center justify-center hover:bg-white/5 rounded-full z-[10000] group border border-white/10"
aria-label="Next image"
>
<span className="text-4xl font-extralight group-hover:translate-x-1 transition-transform duration-500"></span>
</motion.button>
<motion.div
initial={{ opacity: 0, y: 40, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.98 }}
transition={{ duration: 0.6, ease: [0.25, 0.46, 0.45, 0.94] }}
className="relative w-full h-full max-w-6xl max-h-[85vh] flex flex-col items-center justify-center p-4 md:p-12 z-20 pointer-events-none"
>
<div className="pointer-events-auto w-full h-full flex flex-col items-center justify-center">
<div className="relative w-full h-full shadow-[0_40px_100px_-20px_rgba(0,0,0,0.6)] ring-1 ring-white/20 overflow-hidden bg-primary-dark/50 rounded-2xl flex items-center justify-center">
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={currentIndex}
initial={{ opacity: 0, scale: 1.1, filter: 'blur(10px)' }}
animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
exit={{ opacity: 0, scale: 0.9, filter: 'blur(10px)' }}
transition={{ duration: 0.7, ease: [0.25, 0.46, 0.45, 0.94] }}
className="relative w-full h-full"
>
<Image
src={images[currentIndex]}
alt={`Gallery image ${currentIndex + 1}`}
fill
className="object-cover transition-transform duration-1000 hover:scale-[1.03]"
unoptimized
/>
</motion.div>
</AnimatePresence>
{/* Technical Detail: Subtle grid overlay to reinforce industrial precision */}
<div className="absolute inset-0 pointer-events-none opacity-[0.03] bg-[url('/grid.svg')] bg-repeat z-10" />
{/* Premium Reflection: Subtle gradient to give material feel */}
<div className="absolute inset-0 pointer-events-none bg-gradient-to-tr from-white/10 via-transparent to-transparent opacity-40 z-10" />
</div>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ delay: 0.3, duration: 0.4 }}
className="mt-8 flex items-center gap-4"
>
<div className="h-px w-12 bg-white/20" />
<div className="bg-white/5 backdrop-blur-2xl text-white px-6 py-2 rounded-full border border-white/10 text-[11px] font-bold tracking-[0.2em] uppercase">
{currentIndex + 1} <span className="text-accent mx-3">/</span> {images.length}
</div>
<div className="h-px w-12 bg-white/20" />
</motion.div>
</div>
</motion.div>
</div>
)}
</AnimatePresence>,
document.body
);
}

View File

@@ -0,0 +1,175 @@
import React from 'react';
interface OGImageTemplateProps {
title: string;
description?: string;
label?: string;
image?: string;
mode?: 'dark' | 'light' | 'image';
}
export function OGImageTemplate({
title,
description,
label,
image,
mode = 'dark',
}: OGImageTemplateProps) {
const primaryBlue = '#001a4d';
const accentGreen = '#82ed20';
const saturatedBlue = '#011dff';
const containerStyle: React.CSSProperties = {
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'center',
backgroundColor: mode === 'light' ? '#ffffff' : primaryBlue,
padding: '80px',
position: 'relative',
fontFamily: 'Inter, sans-serif',
};
return (
<div style={containerStyle}>
{/* Background Image with Overlay */}
{image && (
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
}}
>
<img
src={image}
alt=""
width="1200"
height="630"
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
<div
style={{
position: 'absolute',
inset: 0,
background: 'linear-gradient(to right, rgba(0,26,77,0.9) 0%, rgba(0,26,77,0.4) 100%)',
}}
/>
</div>
)}
{/* Decorative Scribble Circle (Simplified for Satori) */}
<div
style={{
position: 'absolute',
top: '-100px',
right: '-100px',
width: '600px',
height: '600px',
borderRadius: '50%',
background: `radial-gradient(circle, ${accentGreen}1a 0%, transparent 70%)`,
display: 'flex',
}}
/>
<div style={{ display: 'flex', flexDirection: 'column', position: 'relative', zIndex: 10 }}>
{/* Label / Category */}
{label && (
<div
style={{
fontSize: '24px',
fontWeight: 'bold',
color: accentGreen,
textTransform: 'uppercase',
letterSpacing: '0.2em',
marginBottom: '24px',
display: 'flex',
}}
>
{label}
</div>
)}
{/* Title */}
<div
style={{
fontSize: '72px',
fontWeight: '900',
color: 'white',
lineHeight: '1.1',
maxWidth: '900px',
marginBottom: '32px',
display: 'flex',
}}
>
{title}
</div>
{/* Description */}
{description && (
<div
style={{
fontSize: '32px',
color: 'rgba(255,255,255,0.8)',
maxWidth: '800px',
lineHeight: '1.4',
display: 'flex',
}}
>
{description}
</div>
)}
</div>
{/* Brand Footer */}
<div
style={{
position: 'absolute',
bottom: '80px',
left: '80px',
display: 'flex',
alignItems: 'center',
}}
>
<div
style={{
width: '120px',
height: '8px',
backgroundColor: accentGreen,
borderRadius: '4px',
marginRight: '24px',
}}
/>
<div
style={{
fontSize: '24px',
fontWeight: 'bold',
color: 'white',
textTransform: 'uppercase',
letterSpacing: '0.1em',
}}
>
KLZ Cables
</div>
</div>
{/* Saturated Blue Accent */}
<div
style={{
position: 'absolute',
top: 0,
right: 0,
width: '10px',
height: '100%',
backgroundColor: saturatedBlue,
}}
/>
</div>
);
}

View File

@@ -42,7 +42,7 @@ export default function ProductSidebar({ productName, productImage, datasheetPat
<div className="relative z-10">
<div className="inline-block relative mb-2">
<h3 className="text-xl font-heading font-black m-0 tracking-tighter uppercase leading-none">
<h3 className="text-lg md:text-xl font-heading font-black m-0 tracking-tighter uppercase leading-none">
{t('requestQuote')}
</h3>
<Scribble
@@ -51,7 +51,7 @@ export default function ProductSidebar({ productName, productImage, datasheetPat
color="var(--color-accent)"
/>
</div>
<p className="text-white/60 text-xs m-0 mt-2 leading-relaxed font-medium max-w-[90%]">
<p className="text-white/60 text-xs md:text-sm m-0 mt-2 leading-relaxed font-medium max-w-[90%]">
{t('requestQuoteDesc')}
</p>
</div>
@@ -77,10 +77,10 @@ export default function ProductSidebar({ productName, productImage, datasheetPat
</svg>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-heading font-black text-neutral-dark m-0 uppercase tracking-tighter leading-tight group-hover:text-saturated transition-colors duration-300">
<h3 className="text-sm md:text-base font-heading font-black text-neutral-dark m-0 uppercase tracking-tighter leading-tight group-hover:text-saturated transition-colors duration-300">
{t('downloadDatasheet')}
</h3>
<p className="text-text-secondary text-[10px] m-0 mt-0.5 font-semibold leading-tight truncate uppercase tracking-widest opacity-60">
<p className="text-text-secondary text-[10px] md:text-xs m-0 mt-0.5 font-semibold leading-tight truncate uppercase tracking-widest opacity-60">
{t('downloadDatasheetDesc')}
</p>
</div>

View File

@@ -31,7 +31,7 @@ export default function ProductTabs({ children, technicalData, sidebar }: Produc
{technicalData && (
<div className="pt-24 border-t-2 border-neutral-dark/5">
<div className="mb-16">
<h2 className="text-4xl md:text-5xl font-black text-primary tracking-tighter uppercase mb-4">Technical Specifications</h2>
<h2 className="text-3xl md:text-4xl font-black text-primary tracking-tighter uppercase mb-4">Technical Specifications</h2>
<div className="h-1.5 w-24 bg-accent rounded-full" />
</div>
<div className="not-prose">

View File

@@ -3,6 +3,8 @@
import React, { useState } from 'react';
import { useTranslations } from 'next-intl';
import { Input, Textarea, Button } from '@/components/ui';
import { sendContactFormAction } from '@/app/actions/contact';
import { useAnalytics } from '@/components/analytics/useAnalytics';
interface RequestQuoteFormProps {
productName: string;
@@ -10,6 +12,7 @@ interface RequestQuoteFormProps {
export default function RequestQuoteForm({ productName }: RequestQuoteFormProps) {
const t = useTranslations('Products.form');
const { trackEvent } = useAnalytics();
const [email, setEmail] = useState('');
const [request, setRequest] = useState('');
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
@@ -18,15 +21,30 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
e.preventDefault();
setStatus('submitting');
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
const formData = new FormData();
formData.append('name', 'Product Inquiry'); // Default name for product inquiries
formData.append('email', email);
formData.append('message', request);
formData.append('productName', productName);
// Here you would typically send the data to your backend
console.log('Form submitted:', { productName, email, request });
setStatus('success');
setEmail('');
setRequest('');
try {
const result = await sendContactFormAction(formData);
if (result.success) {
trackEvent('contact_form_submission', {
form_type: 'product_quote',
product_name: productName,
email: email,
});
setStatus('success');
setEmail('');
setRequest('');
} else {
setStatus('error');
}
} catch (error) {
console.error('Form submission error:', error);
setStatus('error');
}
};
if (status === 'success') {
@@ -41,7 +59,7 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
<p className="text-text-secondary text-xs leading-tight mb-4 !mt-0">
{t('successDesc', { productName })}
</p>
<button
<button
onClick={() => setStatus('idle')}
className="inline-flex items-center text-[9px] font-bold uppercase tracking-[0.2em] text-primary hover:text-accent transition-colors group"
>
@@ -53,6 +71,32 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps)
);
}
if (status === 'error') {
return (
<div className="bg-destructive/5 border border-destructive/20 text-destructive p-4 rounded-xl text-center animate-fade-in !mt-0">
<div className="w-10 h-10 bg-destructive rounded-full flex items-center justify-center mx-auto mb-3 shadow-lg shadow-destructive/20">
<svg className="w-5 h-5 text-destructive-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="3">
<circle cx="12" cy="12" r="10" />
<line x1="15" y1="9" x2="9" y2="15" />
<line x1="9" y1="9" x2="15" y2="15" />
</svg>
</div>
<h3 className="text-base font-extrabold mb-1 tracking-tight !mt-0 text-destructive">{t('errorTitle') || 'Submission Failed'}</h3>
<p className="text-destructive/80 text-xs leading-tight mb-4 !mt-0">
{t('errorDesc') || 'Something went wrong. Please try again.'}
</p>
<button
onClick={() => setStatus('idle')}
className="inline-flex items-center text-[9px] font-bold uppercase tracking-[0.2em] text-destructive hover:text-destructive/80 transition-colors group"
>
<span className="border-b-2 border-destructive/10 group-hover:border-destructive/30 transition-colors pb-1">
{t('tryAgain') || 'Try Again'}
</span>
</button>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="space-y-3 !mt-0">
<div className="space-y-2 !mt-0">

View File

@@ -3,19 +3,57 @@
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
import { getAppServices } from '@/lib/services/create-services';
import Script from 'next/script';
// Minimal client-side hook that sends Umami pageviews on route changes.
/**
* AnalyticsProvider Component
*
* Automatically tracks pageviews on client-side route changes.
* This component should be placed inside your layout to handle navigation events.
*
* @example
* ```tsx
* // In your layout.tsx
* <NextIntlClientProvider messages={messages} locale={locale}>
* <UmamiScript />
* <Header />
* <main>{children}</main>
* <Footer />
* <AnalyticsProvider />
* </NextIntlClientProvider>
* ```
*/
export default function AnalyticsProvider() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (!pathname) return;
const services = getAppServices();
const url = `${pathname}${searchParams?.size ? `?${searchParams.toString()}` : ''}`;
// Track pageview with the full URL
services.analytics.trackPageview(url);
if (process.env.NODE_ENV === 'development') {
console.log('[Umami] Tracked pageview:', url);
}
}, [pathname, searchParams]);
return null;
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
if (!websiteId) return null;
return (
<Script
id="umami-analytics"
src="/stats/script.js"
data-website-id={websiteId}
data-host-url="/stats"
strategy="afterInteractive"
data-domains="klz-cables.com"
defer
/>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,166 @@
# Umami Analytics Quick Reference
## Setup Checklist
- [ ] Add `NEXT_PUBLIC_UMAMI_WEBSITE_ID` to `.env` file
- [ ] Verify `UmamiScript` is in your layout
- [ ] Verify `AnalyticsProvider` is in your layout
- [ ] Test in development mode
- [ ] Check Umami dashboard for data
## Environment Variables
```bash
# Required
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
# Optional (defaults to https://analytics.infra.mintel.me/script.js)
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
```
## Quick Usage Examples
### Track an Event
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function MyComponent() {
const { trackEvent } = useAnalytics();
const handleClick = () => {
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
button_id: 'my-button',
page: 'homepage',
});
};
return <button onClick={handleClick}>Click Me</button>;
}
```
### Track a Pageview
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
function MyComponent() {
const { trackPageview } = useAnalytics();
const navigate = () => {
trackPageview('/custom-path');
// Navigate...
};
return <button onClick={navigate}>Go to Page</button>;
}
```
### Track E-commerce Events
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function ProductCard({ product }) {
const { trackEvent } = useAnalytics();
const addToCart = () => {
trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
product_id: product.id,
product_name: product.name,
price: product.price,
});
};
return <button onClick={addToCart}>Add to Cart</button>;
}
```
## Common Events
| Event | When to Use | Example Properties |
|-------|-------------|-------------------|
| `pageview` | Page loads | `{ url: '/products/123' }` |
| `button_click` | Button clicked | `{ button_id: 'cta', page: 'homepage' }` |
| `form_submit` | Form submitted | `{ form_id: 'contact', form_name: 'Contact Us' }` |
| `product_view` | Product page viewed | `{ product_id: '123', price: 99.99 }` |
| `product_add_to_cart` | Add to cart | `{ product_id: '123', quantity: 1 }` |
| `search` | Search performed | `{ search_query: 'cable', results: 42 }` |
| `user_login` | User logged in | `{ user_email: 'user@example.com' }` |
| `error` | Error occurred | `{ error_message: 'Something went wrong' }` |
## Testing
### Development Mode
In development, you'll see console logs:
```
[Umami] Tracked event: button_click { button_id: 'my-button' }
[Umami] Tracked pageview: /products/123
```
### Disable Analytics (Development)
```bash
# .env.local
# NEXT_PUBLIC_UMAMI_WEBSITE_ID=
```
## Troubleshooting
### Analytics Not Working?
1. **Check environment variables:**
```bash
echo $NEXT_PUBLIC_UMAMI_WEBSITE_ID
```
2. **Verify script is loading:**
- Open DevTools → Network tab
- Look for `script.js` request
- Check Console for errors
3. **Check Umami dashboard:**
- Log into Umami
- Verify website ID matches
- Check if data is being received
### Common Issues
| Issue | Solution |
|-------|----------|
| No data in Umami | Check website ID and script URL |
| Events not tracking | Verify `useAnalytics` hook is used |
| Script not loading | Check network connection, CORS |
| Wrong data | Verify event properties are correct |
## Performance Tips
1. **Use `useCallback` for event handlers** to prevent unnecessary re-renders
2. **Debounce high-frequency events** (like search input)
3. **Don't track every interaction** - focus on meaningful events
4. **Use environment variables** to disable analytics in development
## Privacy & Compliance
- ✅ Don't track PII (personally identifiable information)
- ✅ Don't track sensitive data (passwords, credit cards)
- ✅ Follow GDPR and other privacy regulations
- ✅ Use anonymized IDs where possible
- ✅ Provide cookie consent if required
## Next Steps
1. Read [`README.md`](README.md) for detailed documentation
2. Check [`EXAMPLES.md`](EXAMPLES.md) for more use cases
3. Review [`analytics-events.ts`](analytics-events.ts) for event definitions
4. Explore [`useAnalytics.ts`](useAnalytics.ts) for the hook implementation

View File

@@ -0,0 +1,443 @@
# Umami Analytics Integration
This project uses [Umami Analytics](https://umami.is/) for privacy-focused website analytics. The implementation is modern, clean, and follows Next.js best practices.
## Overview
The analytics system consists of:
1. **`UmamiScript`** - Loads the Umami tracking script
2. **`AnalyticsProvider`** - Tracks pageviews on route changes
3. **`useAnalytics`** - Custom hook for tracking custom events
4. **`analytics-events.ts`** - Centralized event definitions
5. **`UmamiAnalyticsService`** - Service layer for analytics operations
## Setup
### Environment Variables
Add these to your `.env` file:
```bash
# Required: Your Umami website ID
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
# Optional: Custom Umami script URL (defaults to https://analytics.infra.mintel.me/script.js)
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
```
### Docker Compose
The `docker-compose.yml` already includes the environment variables:
```yaml
environment:
- NEXT_PUBLIC_UMAMI_WEBSITE_ID=${NEXT_PUBLIC_UMAMI_WEBSITE_ID}
- NEXT_PUBLIC_UMAMI_SCRIPT_URL=${NEXT_PUBLIC_UMAMI_SCRIPT_URL:-https://analytics.infra.mintel.me/script.js}
```
## Usage
### 1. Automatic Pageview Tracking
The `AnalyticsProvider` component automatically tracks pageviews on client-side route changes. It's already included in your layout:
```tsx
// app/[locale]/layout.tsx
<NextIntlClientProvider messages={messages} locale={locale}>
<UmamiScript />
<Header />
<main>{children}</main>
<Footer />
<AnalyticsProvider />
</NextIntlClientProvider>
```
### 2. Tracking Custom Events
Use the `useAnalytics` hook to track custom events:
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function ProductCard({ product }) {
const { trackEvent } = useAnalytics();
const handleAddToCart = () => {
trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
product_id: product.id,
product_name: product.name,
product_category: product.category,
price: product.price,
});
};
return (
<button onClick={handleAddToCart}>
Add to Cart
</button>
);
}
```
### 3. Tracking Pageviews Manually
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
function CustomNavigation() {
const { trackPageview } = useAnalytics();
const navigateToCustomPage = () => {
// Track a custom pageview
trackPageview('/custom-path?param=value');
// Then perform navigation
window.location.href = '/custom-path?param=value';
};
return <button onClick={navigateToCustomPage}>Go to Custom Page</button>;
}
```
### 4. Using Predefined Events
The `analytics-events.ts` file provides a centralized list of events:
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents, AnalyticsEventProperties } from '@/components/analytics/analytics-events';
function ContactForm() {
const { trackEvent } = useAnalytics();
const handleSubmit = (formData: FormData) => {
// Track form submission
trackEvent(AnalyticsEvents.CONTACT_FORM_SUBMIT, {
form_id: 'contact-form',
form_name: 'Contact Us',
form_fields: {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
},
});
};
return <form onSubmit={handleSubmit}>{/* form fields */}</form>;
}
```
### 5. E-commerce Tracking Example
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function ProductPage({ product }) {
const { trackEvent } = useAnalytics();
// Track product view on page load
useEffect(() => {
trackEvent(AnalyticsEvents.PRODUCT_VIEW, {
product_id: product.id,
product_name: product.name,
product_category: product.category,
price: product.price,
});
}, [product]);
const handleAddToCart = () => {
trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
product_id: product.id,
product_name: product.name,
product_category: product.category,
price: product.price,
quantity: 1,
});
};
const handlePurchase = () => {
trackEvent(AnalyticsEvents.PRODUCT_PURCHASE, {
product_id: product.id,
product_name: product.name,
product_category: product.category,
price: product.price,
transaction_id: 'TXN-12345',
currency: 'EUR',
});
};
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<button onClick={handleAddToCart}>Add to Cart</button>
<button onClick={handlePurchase}>Buy Now</button>
</div>
);
}
```
### 6. Search & Filter Tracking
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function ProductFilter() {
const { trackEvent } = useAnalytics();
const handleFilterChange = (filters: Record<string, unknown>) => {
trackEvent(AnalyticsEvents.FILTER_APPLY, {
filter_type: 'category',
filter_value: filters.category,
filter_count: Object.keys(filters).length,
});
};
const handleSearch = (query: string) => {
trackEvent(AnalyticsEvents.SEARCH, {
search_query: query,
search_results_count: 42, // You'd get this from your search results
});
};
return (
<div>
<input onChange={(e) => handleSearch(e.target.value)} />
<select onChange={(e) => handleFilterChange({ category: e.target.value })}>
{/* filter options */}
</select>
</div>
);
}
```
### 7. User Account Events
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function LoginForm() {
const { trackEvent } = useAnalytics();
const handleLogin = (email: string) => {
trackEvent(AnalyticsEvents.USER_LOGIN, {
user_email: email,
login_method: 'email',
});
};
const handleLogout = () => {
trackEvent(AnalyticsEvents.USER_LOGOUT, {
user_id: 'user-123',
});
};
return (
<div>
<button onClick={() => handleLogin('user@example.com')}>Login</button>
<button onClick={handleLogout}>Logout</button>
</div>
);
}
```
### 8. Error Tracking
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function ErrorBoundary({ children }) {
const { trackEvent } = useAnalytics();
const handleError = (error: Error, errorInfo: React.ErrorInfo) => {
trackEvent(AnalyticsEvents.ERROR, {
error_message: error.message,
error_stack: error.stack,
error_component: errorInfo.componentStack,
});
};
return (
<ErrorBoundary onError={handleError}>
{children}
</ErrorBoundary>
);
}
```
## Event Reference
### Common Events
| Event Name | Description | Example Properties |
|------------|-------------|-------------------|
| `pageview` | Page view | `{ url: '/products/123' }` |
| `button_click` | Button clicked | `{ button_id: 'cta-primary', page: 'homepage' }` |
| `link_click` | Link clicked | `{ link_url: '/products', link_text: 'View Products' }` |
| `form_submit` | Form submitted | `{ form_id: 'contact-form', form_name: 'Contact Us' }` |
| `product_view` | Product page viewed | `{ product_id: '123', product_name: 'Cable', price: 99.99 }` |
| `product_add_to_cart` | Product added to cart | `{ product_id: '123', quantity: 1 }` |
| `product_purchase` | Product purchased | `{ product_id: '123', transaction_id: 'TXN-123' }` |
| `search` | Search performed | `{ search_query: 'cable', search_results_count: 42 }` |
| `filter_apply` | Filter applied | `{ filter_type: 'category', filter_value: 'high-voltage' }` |
| `user_login` | User logged in | `{ user_email: 'user@example.com' }` |
| `user_signup` | User signed up | `{ user_email: 'user@example.com' }` |
| `error` | Error occurred | `{ error_message: 'Something went wrong' }` |
### Custom Events
You can create any custom event by passing a string name:
```tsx
trackEvent('custom_event_name', {
custom_property: 'value',
another_property: 123,
});
```
## Best Practices
### 1. Use Centralized Event Definitions
Always use the `AnalyticsEvents` constant for consistency:
```tsx
// ✅ Good
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, { ... });
// ❌ Avoid
trackEvent('product_add_to_cart', { ... }); // Typo risk!
```
### 2. Include Relevant Context
Add context to your events to make them more useful:
```tsx
// ✅ Good
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
button_id: 'cta-primary',
page: 'homepage',
section: 'hero',
user_type: 'guest',
});
// ❌ Less useful
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
button_id: 'cta-primary',
});
```
### 3. Track Meaningful Events
Focus on business-critical events:
- ✅ Product views, add to cart, purchases
- ✅ Form submissions (contact, newsletter, quote requests)
- ✅ Search queries and filter usage
- ✅ User authentication events
- ✅ Error occurrences
- ❌ Every mouse move
- ❌ Every scroll event (unless specifically needed)
- ❌ Every hover state change
### 4. Respect Privacy
- Don't track personally identifiable information (PII)
- Don't track sensitive data (passwords, credit cards, etc.)
- Use anonymized IDs where possible
- Follow GDPR and other privacy regulations
### 5. Test in Development
The analytics system includes development mode logging:
```bash
# In development, you'll see console logs:
[Umami] Tracked event: product_add_to_cart { product_id: '123' }
[Umami] Tracked pageview: /products/123
```
## Troubleshooting
### Analytics Not Working
1. **Check environment variables:**
```bash
echo $NEXT_PUBLIC_UMAMI_WEBSITE_ID
```
2. **Verify the script is loading:**
- Open browser DevTools
- Check Network tab for `script.js` request
- Check Console for any errors
3. **Check Umami dashboard:**
- Log into your Umami instance
- Verify the website ID matches
- Check if data is being received
### Development Mode
In development mode, you'll see console logs for all tracked events. This helps you verify that events are being tracked correctly without affecting your production analytics.
### Disabling Analytics
To disable analytics (e.g., for local development), simply remove the `NEXT_PUBLIC_UMAMI_WEBSITE_ID` environment variable:
```bash
# .env.local (not committed to git)
# NEXT_PUBLIC_UMAMI_WEBSITE_ID=
```
## Performance
The analytics implementation is optimized for performance:
- ✅ Uses Next.js `Script` component with `afterInteractive` strategy
- ✅ Script loads after page is interactive
- ✅ No blocking of critical rendering path
- ✅ Minimal JavaScript bundle size
- ✅ Automatic cleanup on route changes
## Security
- ✅ Environment variables are not exposed to the client (except `NEXT_PUBLIC_` prefixed ones)
- ✅ Script URL can be customized for self-hosted Umami instances
- ✅ Error handling prevents analytics from breaking your app
- ✅ Type-safe event tracking with TypeScript
## Additional Resources
- [Umami Documentation](https://umami.is/docs)
- [Next.js Script Component](https://nextjs.org/docs/app/api-reference/components/script)
- [Analytics Best Practices](https://umami.is/docs/best-practices)
## Support
For issues or questions about the analytics implementation, check:
1. This README for usage examples
2. The component source code for implementation details
3. The Umami documentation for platform-specific questions

View File

@@ -0,0 +1,268 @@
# Umami Analytics Implementation Summary
## ✅ Implementation Status: COMPLETE
Your project now has a **modern, clean, and comprehensive** Umami analytics implementation.
## What Was Already Implemented
The project already had a solid foundation:
1. **`UmamiScript.tsx`** - Next.js Script component for loading analytics
2. **`AnalyticsProvider.tsx`** - Automatic pageview tracking on route changes
3. **`UmamiAnalyticsService.ts`** - Service layer for event tracking
4. **Environment variables** in `docker-compose.yml`
## What Was Enhanced
### 1. **Enhanced UmamiScript** (`components/analytics/UmamiScript.tsx`)
- ✅ Added TypeScript props interface for customization
- ✅ Added JSDoc documentation with usage examples
- ✅ Added error handling for script loading failures
- ✅ Added development mode warnings
- ✅ Improved type safety and comments
### 2. **Enhanced AnalyticsProvider** (`components/analytics/AnalyticsProvider.tsx`)
- ✅ Added comprehensive JSDoc documentation
- ✅ Added development mode logging
- ✅ Improved code comments
### 3. **New Custom Hook** (`components/analytics/useAnalytics.ts`)
- ✅ Type-safe `useAnalytics` hook for easy event tracking
-`trackEvent()` method for custom events
-`trackPageview()` method for manual pageview tracking
-`useCallback` optimization for performance
- ✅ Development mode logging
### 4. **Event Definitions** (`components/analytics/analytics-events.ts`)
- ✅ Centralized event constants for consistency
- ✅ Type-safe event names
- ✅ Helper functions for common event properties
- ✅ 30+ predefined events for various use cases
### 5. **Comprehensive Documentation**
-**README.md** - Full documentation with setup, usage, and best practices
-**EXAMPLES.md** - 20+ practical examples for different scenarios
-**QUICK_REFERENCE.md** - Quick start guide and troubleshooting
-**SUMMARY.md** - This file
## File Structure
```
components/analytics/
├── UmamiScript.tsx # Script loader component
├── AnalyticsProvider.tsx # Route change tracker
├── useAnalytics.ts # Custom hook for event tracking
├── analytics-events.ts # Event definitions and helpers
├── README.md # Full documentation
├── EXAMPLES.md # Practical examples
├── QUICK_REFERENCE.md # Quick start guide
└── SUMMARY.md # This summary
```
## Key Features
### 🚀 Modern Implementation
- Uses Next.js `Script` component (not old-school `<script>` tags)
- TypeScript for type safety
- React hooks for clean API
- Environment variable configuration
### 📊 Comprehensive Tracking
- Automatic pageview tracking on route changes
- Custom event tracking with properties
- E-commerce events (products, cart, purchases)
- User authentication events
- Search and filter tracking
- Error and performance tracking
### 🎯 Developer Experience
- Type-safe event tracking
- Centralized event definitions
- Development mode logging
- Comprehensive documentation
- 20+ practical examples
### 🔒 Privacy & Performance
- No PII tracking by default
- Script loads after page is interactive
- Minimal performance impact
- Easy to disable in development
## Environment Variables
The project is already configured in `docker-compose.yml`:
```yaml
environment:
- NEXT_PUBLIC_UMAMI_WEBSITE_ID=${NEXT_PUBLIC_UMAMI_WEBSITE_ID}
- NEXT_PUBLIC_UMAMI_SCRIPT_URL=${NEXT_PUBLIC_UMAMI_SCRIPT_URL:-https://analytics.infra.mintel.me/script.js}
```
### Required Setup
Add to your `.env` file:
```bash
NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3
```
## Usage Examples
### Basic Event Tracking
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function MyComponent() {
const { trackEvent } = useAnalytics();
const handleClick = () => {
trackEvent(AnalyticsEvents.BUTTON_CLICK, {
button_id: 'my-button',
page: 'homepage',
});
};
return <button onClick={handleClick}>Click Me</button>;
}
```
### E-commerce Tracking
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
import { AnalyticsEvents } from '@/components/analytics/analytics-events';
function ProductCard({ product }) {
const { trackEvent } = useAnalytics();
const addToCart = () => {
trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
product_id: product.id,
product_name: product.name,
price: product.price,
});
};
return <button onClick={addToCart}>Add to Cart</button>;
}
```
### Custom Pageview Tracking
```tsx
'use client';
import { useAnalytics } from '@/components/analytics/useAnalytics';
function CustomNavigation() {
const { trackPageview } = useAnalytics();
const navigate = () => {
trackPageview('/custom-path');
// Navigate...
};
return <button onClick={navigate}>Go to Page</button>;
}
```
## Testing & Development
### Development Mode
In development, you'll see console logs:
```
[Umami] Tracked event: button_click { button_id: 'my-button' }
[Umami] Tracked pageview: /products/123
```
### Disable Analytics (Development)
```bash
# .env.local
# NEXT_PUBLIC_UMAMI_WEBSITE_ID=
```
## Troubleshooting
### Analytics Not Working?
1. **Check environment variables:**
```bash
echo $NEXT_PUBLIC_UMAMI_WEBSITE_ID
```
2. **Verify script is loading:**
- Open DevTools → Network tab
- Look for `script.js` request
- Check Console for errors
3. **Check Umami dashboard:**
- Log into Umami
- Verify website ID matches
- Check if data is being received
### Common Issues
| Issue | Solution |
|-------|----------|
| No data in Umami | Check website ID and script URL |
| Events not tracking | Verify `useAnalytics` hook is used |
| Script not loading | Check network connection, CORS |
| Wrong data | Verify event properties are correct |
## Performance Tips
1. **Use `useCallback`** - The hook is already optimized
2. **Debounce high-frequency events** - See EXAMPLES.md
3. **Don't track every interaction** - Focus on meaningful events
4. **Use environment variables** - Disable in development
## Privacy & Compliance
- ✅ Don't track PII (personally identifiable information)
- ✅ Don't track sensitive data (passwords, credit cards)
- ✅ Follow GDPR and other privacy regulations
- ✅ Use anonymized IDs where possible
- ✅ Provide cookie consent if required
## Next Steps
1. ✅ **Setup complete** - All files are in place
2. ✅ **Documentation complete** - README, EXAMPLES, QUICK_REFERENCE
3. ✅ **Code enhanced** - Better TypeScript, error handling, docs
4. 📝 **Add to .env** - Set `NEXT_PUBLIC_UMAMI_WEBSITE_ID`
5. 🧪 **Test in development** - Verify events are tracked
6. 🚀 **Deploy** - Analytics will work in production
## Quick Start Checklist
- [ ] Add `NEXT_PUBLIC_UMAMI_WEBSITE_ID` to `.env` file
- [ ] Verify `UmamiScript` is in `app/[locale]/layout.tsx`
- [ ] Verify `AnalyticsProvider` is in `app/[locale]/layout.tsx`
- [ ] Test in development mode (check console logs)
- [ ] Check Umami dashboard for data
- [ ] Review EXAMPLES.md for specific use cases
- [ ] Start tracking custom events with `useAnalytics`
## Summary
Your Umami analytics implementation is now **production-ready** with:
-**Modern Next.js approach** (Script component, not old-school tags)
-**Type-safe API** (TypeScript throughout)
-**Comprehensive tracking** (pageviews, events, e-commerce, errors)
-**Excellent documentation** (README, examples, quick reference)
-**Developer-friendly** (hooks, helpers, development mode)
-**Performance optimized** (async loading, minimal impact)
-**Privacy conscious** (no PII, easy to disable)
The implementation is clean, maintainable, and follows Next.js best practices. You can now track any user interaction or business event with just a few lines of code.

View File

@@ -1,19 +0,0 @@
import Script from 'next/script';
export default function UmamiScript() {
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
if (!websiteId) return null;
const src =
process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL ??
'https://analytics.infra.mintel.me/script.js';
return (
<Script
src={src}
data-website-id={websiteId}
strategy="afterInteractive"
/>
);
}

View File

@@ -0,0 +1,130 @@
/**
* Analytics Events Utility
*
* Centralized definitions for common analytics events and their properties.
* This helps maintain consistency across the application and makes it easier
* to track meaningful events.
*
* @example
* ```tsx
* import { useAnalytics } from '@/components/analytics/useAnalytics';
* import { AnalyticsEvents } from '@/components/analytics/analytics-events';
*
* function ProductPage() {
* const { trackEvent } = useAnalytics();
*
* const handleAddToCart = (productId: string, productName: string) => {
* trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
* product_id: productId,
* product_name: productName,
* page: 'product-detail'
* });
* };
*
* return <button onClick={() => handleAddToCart('123', 'Cable')}>Add to Cart</button>;
* }
* ```
*/
export const AnalyticsEvents = {
// Page & Navigation Events
PAGE_VIEW: 'pageview',
PAGE_SCROLL: 'page_scroll',
PAGE_EXIT: 'page_exit',
// User Interaction Events
BUTTON_CLICK: 'button_click',
LINK_CLICK: 'link_click',
FORM_SUBMIT: 'form_submit',
FORM_START: 'form_start',
FORM_ERROR: 'form_error',
// E-commerce Events
PRODUCT_VIEW: 'product_view',
PRODUCT_ADD_TO_CART: 'product_add_to_cart',
PRODUCT_REMOVE_FROM_CART: 'product_remove_from_cart',
PRODUCT_PURCHASE: 'product_purchase',
PRODUCT_WISHLIST_ADD: 'product_wishlist_add',
PRODUCT_WISHLIST_REMOVE: 'product_wishlist_remove',
// Search & Filter Events
SEARCH: 'search',
FILTER_APPLY: 'filter_apply',
FILTER_CLEAR: 'filter_clear',
// User Account Events
USER_SIGNUP: 'user_signup',
USER_LOGIN: 'user_login',
USER_LOGOUT: 'user_logout',
USER_PROFILE_UPDATE: 'user_profile_update',
// Content Events
BLOG_POST_VIEW: 'blog_post_view',
VIDEO_PLAY: 'video_play',
VIDEO_PAUSE: 'video_pause',
VIDEO_COMPLETE: 'video_complete',
DOWNLOAD: 'download',
// UI Interaction Events
MODAL_OPEN: 'modal_open',
MODAL_CLOSE: 'modal_close',
TOGGLE_SWITCH: 'toggle_switch',
ACCORDION_TOGGLE: 'accordion_toggle',
TAB_SWITCH: 'tab_switch',
// Error & Performance Events
ERROR: 'error',
PERFORMANCE: 'performance',
API_ERROR: 'api_error',
API_SUCCESS: 'api_success',
// Custom Business Events
QUOTE_REQUEST: 'quote_request',
CONTACT_FORM_SUBMIT: 'contact_form_submit',
NEWSLETTER_SUBSCRIBE: 'newsletter_subscribe',
BROCHURE_DOWNLOAD: 'brochure_download',
} as const;
/**
* Type-safe event properties for common events
*/
export type AnalyticsEventName = (typeof AnalyticsEvents)[keyof typeof AnalyticsEvents];
/**
* Common event property helpers
*/
export const AnalyticsEventProperties = {
/**
* Create properties for a product event
*/
product: (productId: string, productName: string, category?: string) => ({
product_id: productId,
product_name: productName,
product_category: category,
}),
/**
* Create properties for a form event
*/
form: (formId: string, formName: string, fields?: Record<string, unknown>) => ({
form_id: formId,
form_name: formName,
form_fields: fields,
}),
/**
* Create properties for a search event
*/
search: (query: string, filters?: Record<string, unknown>) => ({
search_query: query,
search_filters: filters,
}),
/**
* Create properties for a navigation event
*/
navigation: (from: string, to: string) => ({
from_page: from,
to_page: to,
}),
} as const;

View File

@@ -0,0 +1,77 @@
'use client';
import { useCallback } from 'react';
import { getAppServices } from '@/lib/services/create-services';
import type { AnalyticsEventProperties } from '@/lib/services/analytics/analytics-service';
/**
* Custom hook for tracking analytics events with Umami.
*
* Provides a convenient way to track custom events throughout your application.
*
* @example
* ```tsx
* import { useAnalytics } from '@/components/analytics/useAnalytics';
*
* function MyComponent() {
* const { trackEvent, trackPageview } = useAnalytics();
*
* const handleButtonClick = () => {
* trackEvent('button_click', {
* button_id: 'cta-primary',
* page: 'homepage'
* });
* };
*
* return <button onClick={handleButtonClick}>Click me</button>;
* }
* ```
*
* @example
* ```tsx
* // Track a custom pageview
* const { trackPageview } = useAnalytics();
* trackPageview('/custom-path?param=value');
* ```
*/
export function useAnalytics() {
const services = getAppServices();
/**
* Track a custom event with optional properties.
*
* @param eventName - The name of the event to track
* @param properties - Optional event properties (metadata)
*/
const trackEvent = useCallback(
(eventName: string, properties?: AnalyticsEventProperties) => {
services.analytics.track(eventName, properties);
if (process.env.NODE_ENV === 'development') {
console.log('[Umami] Tracked event:', eventName, properties);
}
},
[services]
);
/**
* Track a pageview (useful for custom navigation or SPA routing).
*
* @param url - The URL to track (defaults to current location)
*/
const trackPageview = useCallback(
(url?: string) => {
services.analytics.trackPageview(url);
if (process.env.NODE_ENV === 'development') {
console.log('[Umami] Tracked pageview:', url ?? 'current location');
}
},
[services]
);
return {
trackEvent,
trackPageview,
};
}

View File

@@ -41,8 +41,8 @@ export default function ChatBubble({
{/* Message Bubble */}
<div className={`flex flex-col max-w-[85%] ${isRight ? 'items-end' : 'items-start'}`}>
<div className="flex items-baseline gap-2 mb-1">
<span className="text-sm font-bold text-text-primary">{author}</span>
{role && <span className="text-xs text-text-secondary">{role}</span>}
<span className="text-sm md:text-base font-bold text-text-primary">{author}</span>
{role && <span className="text-xs md:text-sm text-text-secondary">{role}</span>}
</div>
<div className={`
@@ -52,7 +52,7 @@ export default function ChatBubble({
: 'bg-white text-text-primary rounded-tl-none border-neutral-200'
}
`}>
<div className={`prose prose-lg max-w-none ${isRight ? 'prose-invert' : ''}`}>
<div className={`prose prose-base md:prose-lg max-w-none ${isRight ? 'prose-invert' : ''}`}>
{children}
</div>

View File

@@ -20,7 +20,7 @@ export default function HighlightBox({ title, children, color = 'primary' }: Hig
<div className="absolute top-0 right-0 w-16 h-16 bg-primary/5 -mr-8 -mt-8 rotate-45 transition-transform group-hover:scale-110" />
{title && (
<h3 className="text-2xl font-bold text-text-primary mb-6 flex items-center gap-4 relative">
<h3 className="text-xl md:text-2xl font-bold text-text-primary mb-6 flex items-center gap-4 relative">
<span className="relative">
{title}
{color === 'accent' && (
@@ -32,7 +32,7 @@ export default function HighlightBox({ title, children, color = 'primary' }: Hig
</span>
</h3>
)}
<div className="prose prose-lg max-w-none relative z-10">
<div className="prose prose-base md:prose-lg max-w-none relative z-10">
{children}
</div>
</div>

View File

@@ -0,0 +1,171 @@
import Link from 'next/link';
import VisualLinkPreview from '@/components/blog/VisualLinkPreview';
import { Callout } from '@/components/ui';
import HighlightBox from '@/components/blog/HighlightBox';
import Stats from '@/components/blog/Stats';
import AnimatedImage from '@/components/blog/AnimatedImage';
import ChatBubble from '@/components/blog/ChatBubble';
import SplitHeading from '@/components/blog/SplitHeading';
import PowerCTA from '@/components/blog/PowerCTA';
import StickyNarrative from '@/components/blog/StickyNarrative';
import TechnicalGrid from '@/components/blog/TechnicalGrid';
import ComparisonGrid from '@/components/blog/ComparisonGrid';
export const mdxComponents = {
VisualLinkPreview,
Callout,
HighlightBox,
Stats,
AnimatedImage,
ChatBubble,
PowerCTA,
SplitHeading,
StickyNarrative,
TechnicalGrid,
ComparisonGrid,
h1: () => null,
a: ({ href, children, ...props }: any) => {
// Special handling for PDF downloads to make them prominent
if (href?.endsWith('.pdf')) {
return (
<a
href={href}
{...props}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-3 px-6 py-3 bg-primary text-white font-bold rounded-xl hover:bg-accent hover:text-primary-dark transition-all duration-300 no-underline my-8 group shadow-lg hover:shadow-xl hover:-translate-y-1"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span>{children}</span>
<span className="text-xs opacity-50 font-normal group-hover:opacity-100 transition-opacity">(PDF)</span>
</a>
);
}
if (href?.startsWith('/')) {
return (
<Link href={href} {...props} className="text-primary font-medium hover:underline decoration-2 underline-offset-2 transition-all">
{children}
</Link>
);
}
return (
<a
href={href}
{...props}
target="_blank"
rel="noopener noreferrer"
className="text-primary font-medium hover:underline decoration-2 underline-offset-2 transition-all inline-flex items-center gap-1"
>
{children}
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
);
},
img: (props: any) => (
<AnimatedImage src={props.src} alt={props.alt} />
),
h2: ({ children, ...props }: any) => {
const id = typeof children === 'string'
? children.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
: props.id;
return (
<SplitHeading {...props} id={id} className="mt-16 mb-6 pb-3 border-b-2 border-primary/20">
{children}
</SplitHeading>
);
},
h3: ({ children, ...props }: any) => {
const id = typeof children === 'string'
? children.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
: props.id;
return (
<h3 {...props} id={id} className="text-2xl font-bold text-text-primary mt-12 mb-4">
{children}
</h3>
);
},
p: ({ children, ...props }: any) => (
<p {...props} className="text-lg text-text-secondary leading-relaxed mb-6">
{children}
</p>
),
ul: ({ children, ...props }: any) => (
<ul {...props} className="my-8 space-y-3">
{children}
</ul>
),
ol: ({ children, ...props }: any) => (
<ol {...props} className="my-8 space-y-3 list-decimal list-inside">
{children}
</ol>
),
li: ({ children, ...props }: any) => (
<li {...props} className="text-lg text-text-secondary flex items-start gap-3">
<span className="text-primary mt-1.5 flex-shrink-0">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
</span>
<span className="flex-1">{children}</span>
</li>
),
blockquote: ({ children, ...props }: any) => (
<blockquote {...props} className="my-8 pl-6 border-l-4 border-primary bg-neutral-light/30 py-4 pr-6 rounded-r-lg">
<div className="text-lg text-text-primary italic">
{children}
</div>
</blockquote>
),
strong: ({ children, ...props }: any) => (
<strong {...props} className="font-bold text-primary">
{children}
</strong>
),
code: ({ children, ...props }: any) => (
<code {...props} className="px-2 py-1 bg-neutral-light text-primary rounded font-mono text-sm">
{children}
</code>
),
pre: ({ children, ...props }: any) => (
<pre {...props} className="my-8 p-6 bg-neutral-dark/5 rounded-xl overflow-x-auto">
{children}
</pre>
),
table: ({ children, ...props }: any) => (
<div className="my-8 overflow-x-auto rounded-lg border border-neutral-200 shadow-sm">
<table {...props} className="w-full text-left text-sm text-text-secondary">
{children}
</table>
</div>
),
thead: ({ children, ...props }: any) => (
<thead {...props} className="bg-neutral-50 text-text-primary font-semibold border-b border-neutral-200">
{children}
</thead>
),
tbody: ({ children, ...props }: any) => (
<tbody {...props} className="divide-y divide-neutral-200 bg-white">
{children}
</tbody>
),
tr: ({ children, ...props }: any) => (
<tr {...props} className="hover:bg-neutral-50/50 transition-colors">
{children}
</tr>
),
th: ({ children, ...props }: any) => (
<th {...props} className="px-6 py-4 whitespace-nowrap">
{children}
</th>
),
td: ({ children, ...props }: any) => (
<td {...props} className="px-6 py-4">
{children}
</td>
),
};

View File

@@ -23,7 +23,7 @@ export default function PowerCTA({ locale }: PowerCTAProps) {
{isDe ? 'Lösungen' : 'Solutions'}
</div>
<h3 className="text-3xl md:text-5xl font-bold text-white mb-8 leading-tight">
<h3 className="text-2xl md:text-4xl font-bold text-white mb-8 leading-tight">
{isDe ? 'Bereit für die' : 'Ready for the'}
<span className="text-accent block">{isDe ? 'Energiewende?' : 'Energy Transition?'}</span>
</h3>

View File

@@ -14,7 +14,7 @@ export default function SplitHeading({ children, className = '', id }: SplitHead
id={id}
className={className}
>
<h2 className="text-2xl md:text-3xl font-bold leading-tight text-text-primary">
<h2 className="text-xl md:text-2xl font-bold leading-tight text-text-primary">
{children}
</h2>
</div>

View File

@@ -14,7 +14,7 @@ export default function StickyNarrative({ title, items }: StickyNarrativeProps)
return (
<div className="my-24 grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20 items-start not-prose">
<div className="lg:col-span-4 lg:sticky lg:top-32">
<h3 className="text-3xl md:text-4xl font-bold text-primary leading-tight">
<h3 className="text-2xl md:text-3xl font-bold text-primary leading-tight">
{title}
</h3>
<div className="w-16 h-1.5 bg-accent mt-8 rounded-full" />

View File

@@ -19,7 +19,7 @@ export default function TableOfContents({ headings, locale }: TableOfContentsPro
useEffect(() => {
const observerOptions = {
rootMargin: '-100px 0% -80% 0%',
rootMargin: '-10% 0% -70% 0%',
threshold: 0
};
@@ -31,12 +31,18 @@ export default function TableOfContents({ headings, locale }: TableOfContentsPro
});
}, observerOptions);
const elements = headings.map((h) => document.getElementById(h.id));
elements.forEach((el) => {
if (el) observer.observe(el);
});
// Use a small delay to ensure MDX content is rendered and IDs are present
const timer = setTimeout(() => {
const elements = headings.map((h) => document.getElementById(h.id));
elements.forEach((el) => {
if (el) observer.observe(el);
});
}, 500);
return () => observer.disconnect();
return () => {
clearTimeout(timer);
observer.disconnect();
};
}, [headings]);
if (headings.length === 0) return null;
@@ -44,7 +50,7 @@ export default function TableOfContents({ headings, locale }: TableOfContentsPro
return (
<nav className="hidden lg:block w-full ml-12">
<div className="relative pl-6 border-l border-neutral-200">
<h4 className="text-xs font-bold uppercase tracking-[0.2em] text-text-primary/50 mb-6">
<h4 className="text-xs md:text-sm font-bold uppercase tracking-[0.2em] text-text-primary/50 mb-6">
{locale === 'de' ? 'Inhalt' : 'Table of Contents'}
</h4>
<ul className="space-y-4">
@@ -60,7 +66,7 @@ export default function TableOfContents({ headings, locale }: TableOfContentsPro
<a
href={`#${heading.id}`}
className={cn(
"text-sm transition-all duration-300 hover:text-primary block leading-snug",
"text-sm md:text-base transition-all duration-300 hover:text-primary block leading-snug",
activeId === heading.id
? "text-primary font-bold translate-x-1"
: "text-text-secondary font-medium hover:translate-x-1"
@@ -75,7 +81,7 @@ export default function TableOfContents({ headings, locale }: TableOfContentsPro
}
}}
>
{heading.text}
{heading.text.replace(/\*\*(.*?)\*\*/g, '$1')}
</a>
</li>
))}

View File

@@ -0,0 +1,115 @@
import {
Body,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Section,
Text,
} from "@react-email/components";
import * as React from "react";
interface ContactEmailProps {
name: string;
email: string;
message: string;
subject?: string;
productName?: string;
}
export const ContactEmail = ({
name,
email,
message,
subject = "New Contact Form Submission",
productName,
}: ContactEmailProps) => (
<Html>
<Head />
<Preview>{subject}</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>{subject}</Heading>
{productName && (
<Text style={text}>
<strong>Product Inquiry:</strong> {productName}
</Text>
)}
<Section style={section}>
<Text style={text}>
<strong>Name:</strong> {name}
</Text>
<Text style={text}>
<strong>Email:</strong> {email}
</Text>
<Hr style={hr} />
<Text style={text}>
<strong>Message:</strong>
</Text>
<Text style={messageText}>{message}</Text>
</Section>
<Hr style={hr} />
<Text style={footer}>
This email was sent from the contact form on klz-cables.com
</Text>
</Container>
</Body>
</Html>
);
export default ContactEmail;
const main = {
backgroundColor: "#f6f9fc",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
};
const container = {
backgroundColor: "#ffffff",
margin: "0 auto",
padding: "20px 0 48px",
marginBottom: "64px",
};
const section = {
padding: "0 48px",
};
const h1 = {
color: "#333",
fontSize: "24px",
fontWeight: "bold",
padding: "0 48px",
margin: "30px 0",
};
const text = {
color: "#333",
fontSize: "16px",
lineHeight: "24px",
textAlign: "left" as const,
};
const messageText = {
...text,
backgroundColor: "#f4f4f4",
padding: "15px",
borderRadius: "4px",
whiteSpace: "pre-wrap" as const,
};
const hr = {
borderColor: "#e6ebf1",
margin: "20px 0",
};
const footer = {
color: "#8898aa",
fontSize: "12px",
lineHeight: "16px",
textAlign: "center" as const,
marginTop: "20px",
};

View File

@@ -17,7 +17,7 @@ export default function CTA() {
<Heading level={2} subtitle={t('subtitle')} className="text-white mb-6">
<span className="text-white">{t('title')}</span>
</Heading>
<p className="text-xl text-white/70 leading-relaxed">
<p className="text-lg md:text-xl text-white/70 leading-relaxed">
{t('description')}
</p>
</div>

View File

@@ -25,7 +25,7 @@ export default function Experience() {
<Heading level={2} subtitle={t('subtitle')} className="text-white">
<span className="text-white">{t('title')}</span>
</Heading>
<div className="space-y-8 text-xl text-white/90 leading-relaxed font-medium">
<div className="space-y-8 text-lg md:text-xl text-white/90 leading-relaxed font-medium">
<p className="border-l-4 border-accent pl-8 py-2 bg-white/5 backdrop-blur-sm rounded-r-xl">
{t('p1')}
</p>
@@ -36,12 +36,12 @@ export default function Experience() {
<div className="mt-16 grid grid-cols-1 md:grid-cols-2 gap-12">
<div className="animate-fade-in">
<div className="text-4xl font-extrabold text-accent mb-4">{t('certifiedQuality')}</div>
<div className="text-lg font-bold uppercase tracking-widest text-white/60">{t('vdeApproved')}</div>
<div className="text-2xl md:text-3xl font-extrabold text-accent mb-4">{t('certifiedQuality')}</div>
<div className="text-base md:text-lg font-bold uppercase tracking-widest text-white/60">{t('vdeApproved')}</div>
</div>
<div className="animate-fade-in" style={{ animationDelay: '100ms' }}>
<div className="text-4xl font-extrabold text-accent mb-4">{t('fullSpectrum')}</div>
<div className="text-lg font-bold uppercase tracking-widest text-white/60">{t('solutionsRange')}</div>
<div className="text-2xl md:text-3xl font-extrabold text-accent mb-4">{t('fullSpectrum')}</div>
<div className="text-base md:text-lg font-bold uppercase tracking-widest text-white/60">{t('solutionsRange')}</div>
</div>
</div>
</div>

View File

@@ -1,10 +1,15 @@
import React from 'react';
'use client';
import React, { useState, useEffect } from 'react';
import Image from 'next/image';
import { useTranslations } from 'next-intl';
import { Section, Container, Heading } from '../../components/ui';
import Lightbox from '../Lightbox';
import { useSearchParams } from 'next/navigation';
export default function GallerySection() {
const t = useTranslations('Home.gallery');
const searchParams = useSearchParams();
const images = [
'/uploads/2024/12/DSC07433-Large-600x400.webp',
'/uploads/2024/12/DSC07460-Large-600x400.webp',
@@ -14,8 +19,22 @@ export default function GallerySection() {
'/uploads/2024/12/DSC07768-Large.webp',
];
const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxIndex, setLightboxIndex] = useState(0);
useEffect(() => {
const photoParam = searchParams.get('photo');
if (photoParam !== null) {
const index = parseInt(photoParam, 10);
if (!isNaN(index) && index >= 0 && index < images.length) {
setLightboxIndex(index);
setLightboxOpen(true);
}
}
}, [searchParams, images.length]);
return (
<Section className="bg-white text-neutral-dark py-32">
<Section className="bg-white text-white py-32">
<Container>
<Heading level={2} subtitle={t('subtitle')} align="center">
{t('title')}
@@ -23,9 +42,16 @@ export default function GallerySection() {
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{images.map((src, idx) => (
<div key={idx} className="relative aspect-[4/3] overflow-hidden rounded-3xl group shadow-lg hover:shadow-2xl transition-all duration-700">
<Image
src={src}
<button
key={idx}
onClick={() => {
setLightboxIndex(idx);
setLightboxOpen(true);
}}
className="relative aspect-[4/3] overflow-hidden rounded-3xl group shadow-lg hover:shadow-2xl transition-all duration-700 cursor-pointer"
>
<Image
src={src}
alt={`${t('alt')} ${idx + 1}`}
fill
className="object-cover transition-transform duration-1000 group-hover:scale-110"
@@ -33,10 +59,17 @@ export default function GallerySection() {
/>
<div className="absolute inset-0 bg-primary-dark/20 group-hover:bg-transparent transition-all duration-500" />
<div className="absolute inset-0 border-0 group-hover:border-[16px] border-white/10 transition-all duration-500 pointer-events-none" />
</div>
</button>
))}
</div>
</Container>
<Lightbox
isOpen={lightboxOpen}
images={images}
initialIndex={lightboxIndex}
onClose={() => setLightboxOpen(false)}
/>
</Section>
);
}

View File

@@ -1,48 +1,168 @@
import React from 'react';
import { useTranslations } from 'next-intl';
import { Container, Button, Section, Heading } from '@/components/ui';
'use client';
import Scribble from '@/components/Scribble';
import { Button, Container, Heading, Section } from '@/components/ui';
import { motion } from 'framer-motion';
import { useTranslations } from 'next-intl';
import HeroIllustration from './HeroIllustration';
export default function Hero() {
const t = useTranslations('Home.hero');
return (
<Section className="relative h-[70vh] md:h-[90vh] flex items-center justify-center overflow-hidden bg-primary py-0 md:py-0 lg:py-0">
<HeroIllustration />
<Container className="relative z-10 text-left text-white w-full">
<div className="max-w-5xl animate-slide-up">
<Heading level={1} className="mb-4 md:mb-8 tracking-tight leading-[1.05] max-w-[15ch] md:max-w-none text-white">
{t.rich('title', {
green: (chunks) => (
<span className="relative inline-block">
<span className="relative z-10 text-accent italic">{chunks}</span>
<Scribble variant="circle" className="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block" />
</span>
)
})}
</Heading>
<p className="text-lg md:text-2xl text-white leading-relaxed max-w-2xl mb-8 md:mb-12 line-clamp-2 md:line-clamp-none">
{t('subtitle')}
</p>
<div className="flex flex-col md:flex-row gap-3 md:gap-6">
<Button href="/contact" variant="accent" size="lg" className="group w-full md:w-auto md:h-16 md:px-10 md:text-xl">
{t('cta')}
<span className="ml-3 transition-transform group-hover:translate-x-1">&rarr;</span>
</Button>
<Button href="/products" variant="ghost" size="lg" className="group w-full md:w-auto text-white hover:bg-white/10 md:bg-white md:text-saturated md:hover:bg-neutral-light md:h-16 md:px-10 md:text-xl">
{t('exploreProducts')}
</Button>
</div>
</div>
<Section className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
<motion.div
className="max-w-5xl mx-auto md:mx-0"
initial="hidden"
animate="visible"
variants={containerVariants}
>
<motion.div variants={headingVariants}>
<Heading level={1} className="text-center md:text-left mb-6 md:mb-8 md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl font-extrabold [text-shadow:_-2px_-2px_0_#002b49,_2px_-2px_0_#002b49,_-2px_2px_0_#002b49,_2px_2px_0_#002b49,_-2px_0_0_#002b49,_2px_0_0_#002b49,_0_-2px_0_#002b49,_0_2px_0_#002b49]">
{t.rich('title', {
green: (chunks) => (
<span className="relative inline-block">
<motion.span
className="relative z-10 text-accent italic"
variants={accentVariants}
>
{chunks}
</motion.span>
<motion.div
variants={scribbleVariants}
className="w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute -z-10"
>
<Scribble variant="circle" />
</motion.div>
</span>
)
})}
</Heading>
</motion.div>
<motion.div variants={subtitleVariants}>
<p className="text-lg md:text-xl text-white/90 leading-relaxed max-w-2xl mb-10 md:mb-12">
{t('subtitle')}
</p>
</motion.div>
<motion.div
className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6"
variants={buttonContainerVariants}
>
<motion.div variants={buttonVariants}>
<Button href="/contact" variant="accent" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg">
{t('cta')}
<span className="transition-transform group-hover/btn:translate-x-1">&rarr;</span>
</Button>
</motion.div>
<motion.div variants={buttonVariants}>
<Button href="/products" variant="white" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg md:bg-white md:text-primary md:hover:bg-neutral-light md:border-none">
{t('exploreProducts')}
</Button>
</motion.div>
</motion.div>
</motion.div>
</Container>
<motion.div
className="relative md:absolute inset-0 z-0 w-full h-[40vh] md:h-full order-1 md:order-none mb-[-80px] md:mb-0 mt-[80px] md:mt-0 overflow-visible pointer-events-none"
initial={{ opacity: 0, scale: 0.95, filter: 'blur(20px)' }}
animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}
transition={{ duration: 2.2, ease: 'easeOut', delay: 0.05 }}
>
<HeroIllustration />
</motion.div>
<div className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 animate-bounce hidden sm:block">
<motion.div
className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 hidden sm:block"
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1, ease: "easeOut", delay: 3 }}
>
<div className="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center p-1">
<div className="w-1 h-2 bg-white rounded-full" />
<motion.div
className="w-1 h-2 bg-white rounded-full"
animate={{ y: [0, -10, 0] }}
transition={{
duration: 1.5,
repeat: Infinity,
ease: "easeInOut"
}}
/>
</div>
</div>
</motion.div>
</Section>
);
}
const containerVariants = {
hidden: { opacity: 1 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.12,
delayChildren: 0.4
}
}
} as const;
const headingVariants = {
hidden: { opacity: 0, y: 60, scale: 0.85 },
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: { duration: 1.2, ease: [0.25, 0.46, 0.45, 0.94] }
}
} as const;
const accentVariants = {
hidden: { opacity: 0, scale: 0.9, rotate: -5 },
visible: {
opacity: 1,
scale: 1,
rotate: 0,
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }
}
} as const;
const scribbleVariants = {
hidden: { opacity: 0, scale: 0, rotate: 180 },
visible: {
opacity: 1,
scale: 1,
rotate: 0,
transition: { duration: 1, type: "spring", stiffness: 300, damping: 20 }
}
} as const;
const subtitleVariants = {
hidden: { opacity: 0, y: 40, scale: 0.95 },
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: { duration: 1, ease: [0.25, 0.46, 0.45, 0.94] }
}
} as const;
const buttonContainerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.4
}
}
} as const;
const buttonVariants = {
hidden: { opacity: 0, y: 30, scale: 0.9 },
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: { type: "spring", stiffness: 400, damping: 20 }
}
} as const;

View File

@@ -163,11 +163,589 @@ const POWER_LINES = [
export default function HeroIllustration() {
return (
<div className="absolute inset-0 z-0 overflow-hidden bg-primary">
<div className="absolute md:inset-0 z-0 overflow-visible bg-primary w-full h-full">
<svg
viewBox="400 0 1000 1100"
className="w-full h-full opacity-60 md:opacity-100 scale-[1.44] md:scale-100 translate-x-0 overflow-visible md:hidden"
preserveAspectRatio="xMidYMid meet"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
{/* Electric energy flow gradient */}
<linearGradient id="energy-pulse" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#82ed20" stopOpacity="0" />
<stop offset="30%" stopColor="#82ed20" stopOpacity="0.6" />
<stop offset="50%" stopColor="#9bf14d" stopOpacity="1" />
<stop offset="70%" stopColor="#82ed20" stopOpacity="0.6" />
<stop offset="100%" stopColor="#82ed20" stopOpacity="0" />
</linearGradient>
{/* Wind flow gradient */}
<linearGradient id="wind-flow" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="white" stopOpacity="0" />
<stop offset="30%" stopColor="white" stopOpacity="0.4" />
<stop offset="50%" stopColor="white" stopOpacity="0.6" />
<stop offset="70%" stopColor="white" stopOpacity="0.4" />
<stop offset="100%" stopColor="white" stopOpacity="0" />
</linearGradient>
{/* Sun ray gradient */}
<linearGradient id="sun-ray" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#FFD700" stopOpacity="0.6" />
<stop offset="50%" stopColor="#FFD700" stopOpacity="0.3" />
<stop offset="100%" stopColor="#82ed20" stopOpacity="0.1" />
</linearGradient>
{/* Glow filter */}
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
{/* Soft glow for nodes */}
<filter id="soft-glow" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="2" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
{/* Sun glow filter */}
<filter id="sun-glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
{/* Main scene container - positioned to the right */}
<g transform="translate(900, 100)">
{/* === ISOMETRIC GRID === */}
<g opacity="0.15">
{/* Horizontal grid lines (going from top-left to bottom-right) */}
{[...Array(GRID.rows + 1)].map((_, row) => {
const start = gridToScreen(0, row);
const end = gridToScreen(GRID.cols, row);
return (
<line
key={`h-${row}`}
x1={start.x}
y1={start.y}
x2={end.x}
y2={end.y}
stroke="white"
strokeWidth="1"
/>
);
})}
{/* Vertical grid lines (going from top-right to bottom-left) */}
{[...Array(GRID.cols + 1)].map((_, col) => {
const start = gridToScreen(col, 0);
const end = gridToScreen(col, GRID.rows);
return (
<line
key={`v-${col}`}
x1={start.x}
y1={start.y}
x2={end.x}
y2={end.y}
stroke="white"
strokeWidth="1"
/>
);
})}
</g>
{/* Grid intersection nodes */}
<g opacity="0.2">
{[...Array(GRID.cols + 1)].map((_, col) =>
[...Array(GRID.rows + 1)].map((_, row) => {
const pos = gridToScreen(col, row);
return (
<circle
key={`node-${col}-${row}`}
cx={pos.x}
cy={pos.y}
r="2"
fill="white"
/>
);
})
)}
</g>
{/* === POWER LINES (Base cables) === */}
<g stroke="white" strokeWidth="2" strokeOpacity="0.25">
{POWER_LINES.map((line, i) => {
const from = gridToScreen(line.from.col, line.from.row);
const to = gridToScreen(line.to.col, line.to.row);
return (
<line
key={`cable-${i}`}
x1={from.x}
y1={from.y}
x2={to.x}
y2={to.y}
/>
);
})}
</g>
{/* === ANIMATED ENERGY FLOW === */}
<g filter="url(#glow)">
{POWER_LINES.map((line, i) => {
const from = gridToScreen(line.from.col, line.from.row);
const to = gridToScreen(line.to.col, line.to.row);
const length = Math.sqrt(
Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2)
);
return (
<line
key={`flow-${i}`}
x1={from.x}
y1={from.y}
x2={to.x}
y2={to.y}
stroke="url(#energy-pulse)"
strokeWidth="3"
strokeLinecap="round"
strokeDasharray={`${length * 0.2} ${length * 0.8}`}
>
<animate
attributeName="stroke-dashoffset"
from={length}
to={0}
dur={`${1.5 + (i % 3) * 0.5}s`}
repeatCount="indefinite"
/>
</line>
);
})}
</g>
{/* === SOLAR PANELS === */}
{INFRASTRUCTURE.solar.map((panel, i) => {
const pos = gridToScreen(panel.col, panel.row);
return (
<g key={`solar-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Panel base */}
<path
d="M -20 0 L 0 -10 L 20 0 L 0 10 Z"
fill="white"
fillOpacity="0.1"
stroke="white"
strokeWidth="1"
strokeOpacity="0.4"
/>
{/* Panel surface (tilted) */}
<path
d="M -15 -5 L 0 -15 L 15 -5 L 0 5 Z"
fill="white"
fillOpacity="0.15"
stroke="white"
strokeWidth="1"
strokeOpacity="0.5"
/>
{/* Panel grid lines */}
<line x1="-7" y1="-10" x2="7" y2="0" stroke="white" strokeWidth="0.5" strokeOpacity="0.3" />
<line x1="0" y1="-15" x2="0" y2="5" stroke="white" strokeWidth="0.5" strokeOpacity="0.3" />
{/* Connection glow */}
<circle r="4" fill="#82ed20" fillOpacity="0.4" filter="url(#soft-glow)">
<animate attributeName="fillOpacity" values="0.3;0.6;0.3" dur="2s" repeatCount="indefinite" />
</circle>
</g>
);
})}
{/* === WIND TURBINES === */}
{INFRASTRUCTURE.wind.map((turbine, i) => {
const pos = gridToScreen(turbine.col, turbine.row);
return (
<g key={`wind-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Base */}
<ellipse cx="0" cy="0" rx="10" ry="5" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="1" strokeOpacity="0.3" />
{/* Tower */}
<line x1="0" y1="0" x2="0" y2="-60" stroke="white" strokeWidth="2" strokeOpacity="0.5" />
{/* Nacelle */}
<ellipse cx="0" cy="-60" rx="6" ry="3" fill="white" fillOpacity="0.3" stroke="white" strokeWidth="1" />
{/* Blades */}
<g transform="translate(0, -60)">
{[0, 120, 240].map((angle, j) => (
<line
key={`blade-${i}-${j}`}
x1="0"
y1="0"
x2="0"
y2="-30"
stroke="white"
strokeWidth="1.5"
strokeOpacity="0.6"
transform={`rotate(${angle})`}
>
<animateTransform
attributeName="transform"
type="rotate"
from={`${angle} 0 0`}
to={`${angle + 360} 0 0`}
dur={`${3 + i}s`}
repeatCount="indefinite"
/>
</line>
))}
<circle r="3" fill="white" fillOpacity="0.4" />
</g>
{/* Connection glow */}
<circle r="5" fill="#82ed20" fillOpacity="0.4" filter="url(#soft-glow)">
<animate attributeName="fillOpacity" values="0.3;0.6;0.3" dur="2.5s" repeatCount="indefinite" />
</circle>
</g>
);
})}
{/* === SUBSTATIONS === */}
{INFRASTRUCTURE.substations.map((sub, i) => {
const pos = gridToScreen(sub.col, sub.row);
const isCollection = sub.type === 'collection';
return (
<g key={`substation-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Base platform */}
<path
d="M -25 0 L 0 -12 L 25 0 L 0 12 Z"
fill="white"
fillOpacity="0.1"
stroke="white"
strokeWidth="1"
strokeOpacity="0.4"
/>
{/* Building */}
<path
d={isCollection
? "M -18 0 L -18 -20 L 0 -32 L 18 -20 L 18 0"
: "M -22 0 L -22 -25 L 0 -37 L 22 -25 L 22 0"
}
fill="white"
fillOpacity="0.08"
stroke="white"
strokeWidth="1"
strokeOpacity="0.5"
/>
{/* Equipment */}
<rect x="-10" y="-12" width="6" height="8" fill="white" fillOpacity="0.2" stroke="white" strokeWidth="0.5" />
<rect x="4" y="-12" width="6" height="8" fill="white" fillOpacity="0.2" stroke="white" strokeWidth="0.5" />
{/* Insulators */}
<line x1="-7" y1="-12" x2="-7" y2="-22" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
<line x1="7" y1="-12" x2="7" y2="-22" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
<circle cx="-7" cy="-22" r="2" fill="white" fillOpacity="0.4" />
<circle cx="7" cy="-22" r="2" fill="white" fillOpacity="0.4" />
{/* Connection glow */}
<circle r="8" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
<animate attributeName="r" values="6;10;6" dur="3s" repeatCount="indefinite" />
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur="3s" repeatCount="indefinite" />
</circle>
</g>
);
})}
{/* === TRANSMISSION TOWERS === */}
{INFRASTRUCTURE.towers.map((tower, i) => {
const pos = gridToScreen(tower.col, tower.row);
return (
<g key={`tower-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Base */}
<ellipse cx="0" cy="0" rx="8" ry="4" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="1" strokeOpacity="0.3" />
{/* Tower legs */}
<path d="M -6 0 L -3 -45 M 6 0 L 3 -45" stroke="white" strokeWidth="1.5" strokeOpacity="0.5" />
{/* Cross braces */}
<path d="M -5 -10 L 5 -10 M -4 -20 L 4 -20 M -3 -30 L 3 -30 M -3 -45 L 3 -45" stroke="white" strokeWidth="1" strokeOpacity="0.3" />
{/* Cross arms */}
<line x1="-12" y1="-40" x2="12" y2="-40" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
<line x1="-10" y1="-32" x2="10" y2="-32" stroke="white" strokeWidth="1" strokeOpacity="0.4" />
{/* Insulators */}
<circle cx="-10" cy="-40" r="1.5" fill="white" fillOpacity="0.4" />
<circle cx="10" cy="-40" r="1.5" fill="white" fillOpacity="0.4" />
{/* Connection glow */}
<circle r="5" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur="2s" repeatCount="indefinite" />
</circle>
</g>
);
})}
{/* === CITY BUILDINGS === */}
{INFRASTRUCTURE.city.map((building, i) => {
const pos = gridToScreen(building.col, building.row);
const heights = { tall: 70, medium: 45, small: 30 };
const height = heights[building.type as keyof typeof heights];
return (
<g key={`building-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Base */}
<path
d="M -12 0 L 0 -6 L 12 0 L 0 6 Z"
fill="white"
fillOpacity="0.1"
stroke="white"
strokeWidth="1"
strokeOpacity="0.3"
/>
{/* Building front */}
<path
d={`M -12 0 L -12 -${height} L 0 -${height + 6} L 0 -6 Z`}
fill="white"
fillOpacity="0.1"
stroke="white"
strokeWidth="1"
strokeOpacity="0.4"
/>
{/* Building side */}
<path
d={`M 0 -6 L 0 -${height + 6} L 12 -${height} L 12 0 Z`}
fill="white"
fillOpacity="0.05"
stroke="white"
strokeWidth="1"
strokeOpacity="0.3"
/>
{/* Windows */}
{[...Array(Math.floor(height / 15))].map((_, w) => (
<g key={`window-${i}-${w}`}>
<rect x="-9" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
<rect x="-4" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
<rect x="3" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
<rect x="7" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
</g>
))}
{/* Connection glow */}
<circle r="4" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur={`${2 + i * 0.3}s`} repeatCount="indefinite" />
</circle>
</g>
);
})}
{/* === CITY 2 BUILDINGS (bottom-left) === */}
{INFRASTRUCTURE.city2.map((building, i) => {
const pos = gridToScreen(building.col, building.row);
const heights = { tall: 70, medium: 45, small: 30 };
const height = heights[building.type as keyof typeof heights];
return (
<g key={`building2-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Base */}
<path
d="M -12 0 L 0 -6 L 12 0 L 0 6 Z"
fill="white"
fillOpacity="0.1"
stroke="white"
strokeWidth="1"
strokeOpacity="0.3"
/>
{/* Building front */}
<path
d={`M -12 0 L -12 -${height} L 0 -${height + 6} L 0 -6 Z`}
fill="white"
fillOpacity="0.1"
stroke="white"
strokeWidth="1"
strokeOpacity="0.4"
/>
{/* Building side */}
<path
d={`M 0 -6 L 0 -${height + 6} L 12 -${height} L 12 0 Z`}
fill="white"
fillOpacity="0.05"
stroke="white"
strokeWidth="1"
strokeOpacity="0.3"
/>
{/* Windows */}
{[...Array(Math.floor(height / 15))].map((_, w) => (
<g key={`window2-${i}-${w}`}>
<rect x="-9" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
<rect x="-4" y={-12 - w * 15} width="3" height="4" fill="white" fillOpacity="0.2" />
<rect x="3" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
<rect x="7" y={-15 - w * 15} width="3" height="4" fill="white" fillOpacity="0.15" />
</g>
))}
{/* Connection glow */}
<circle r="4" fill="#82ed20" fillOpacity="0.3" filter="url(#soft-glow)">
<animate attributeName="fillOpacity" values="0.2;0.5;0.2" dur={`${2.5 + i * 0.3}s`} repeatCount="indefinite" />
</circle>
</g>
);
})}
{/* === TREES === */}
{INFRASTRUCTURE.trees.map((tree, i) => {
const pos = gridToScreen(tree.col, tree.row);
return (
<g key={`tree-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Trunk */}
<line x1="0" y1="0" x2="0" y2="-15" stroke="white" strokeWidth="2" strokeOpacity="0.3" />
{/* Foliage - layered circles for tree crown */}
<ellipse cx="0" cy="-22" rx="10" ry="8" fill="white" fillOpacity="0.12" stroke="white" strokeWidth="0.5" strokeOpacity="0.2" />
<ellipse cx="-5" cy="-26" rx="7" ry="6" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="0.5" strokeOpacity="0.15" />
<ellipse cx="5" cy="-26" rx="7" ry="6" fill="white" fillOpacity="0.1" stroke="white" strokeWidth="0.5" strokeOpacity="0.15" />
<ellipse cx="0" cy="-30" rx="6" ry="5" fill="white" fillOpacity="0.08" stroke="white" strokeWidth="0.5" strokeOpacity="0.1" />
</g>
);
})}
{/* === ABSTRACT WIND EFFECTS === */}
{INFRASTRUCTURE.wind.map((turbine, i) => {
const pos = gridToScreen(turbine.col, turbine.row);
return (
<g key={`wind-effect-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Wind swoosh lines - curved paths flowing toward turbine */}
{[0, 1, 2].map((j) => (
<path
key={`wind-line-${i}-${j}`}
d={`M ${-80 - j * 15} ${-70 - j * 8} Q ${-50 - j * 10} ${-65 - j * 5} ${-20} ${-60}`}
stroke="url(#wind-flow)"
strokeWidth="2"
fill="none"
strokeLinecap="round"
opacity="0"
>
<animate
attributeName="opacity"
values="0;0.6;0"
dur={`${2 + j * 0.5}s`}
begin={`${j * 0.7 + i * 0.3}s`}
repeatCount="indefinite"
/>
<animate
attributeName="stroke-dashoffset"
from="100"
to="0"
dur={`${2 + j * 0.5}s`}
begin={`${j * 0.7 + i * 0.3}s`}
repeatCount="indefinite"
/>
</path>
))}
{/* Additional wind particles */}
{[0, 1, 2, 3].map((j) => (
<circle
key={`wind-particle-${i}-${j}`}
r="1.5"
fill="white"
opacity="0"
>
<animate
attributeName="cx"
values={`${-70 - j * 10};${-10}`}
dur={`${1.5 + j * 0.3}s`}
begin={`${j * 0.4 + i * 0.2}s`}
repeatCount="indefinite"
/>
<animate
attributeName="cy"
values={`${-75 - j * 5};${-60}`}
dur={`${1.5 + j * 0.3}s`}
begin={`${j * 0.4 + i * 0.2}s`}
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0;0.5;0"
dur={`${1.5 + j * 0.3}s`}
begin={`${j * 0.4 + i * 0.2}s`}
repeatCount="indefinite"
/>
</circle>
))}
</g>
);
})}
{/* === SCHEMATIC SUN RAYS === */}
{/* Simple downward rays above each solar panel */}
{INFRASTRUCTURE.solar.map((panel, i) => {
const pos = gridToScreen(panel.col, panel.row);
return (
<g key={`sun-ray-${i}`} transform={`translate(${pos.x}, ${pos.y})`}>
{/* Three short schematic rays coming down to panel */}
{[-8, 0, 8].map((offset, j) => (
<line
key={`ray-${i}-${j}`}
x1={offset}
y1={-45}
x2={offset * 0.3}
y2={-18}
stroke="#FFD700"
strokeWidth="1.5"
strokeOpacity="0.4"
strokeLinecap="round"
strokeDasharray="4 6"
>
<animate
attributeName="strokeOpacity"
values="0.2;0.5;0.2"
dur={`${2 + j * 0.3}s`}
begin={`${i * 0.2}s`}
repeatCount="indefinite"
/>
<animate
attributeName="stroke-dashoffset"
from="10"
to="0"
dur="1.5s"
repeatCount="indefinite"
/>
</line>
))}
</g>
);
})}
{/* === ENERGY PARTICLES === */}
{POWER_LINES.map((line, i) => {
const from = gridToScreen(line.from.col, line.from.row);
const to = gridToScreen(line.to.col, line.to.row);
return (
<circle
key={`particle-${i}`}
r="3"
fill="#82ed20"
filter="url(#soft-glow)"
>
<animate
attributeName="cx"
values={`${from.x};${to.x}`}
dur={`${1 + (i % 4) * 0.3}s`}
repeatCount="indefinite"
/>
<animate
attributeName="cy"
values={`${from.y};${to.y}`}
dur={`${1 + (i % 4) * 0.3}s`}
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0;0.8;0"
dur={`${1 + (i % 4) * 0.3}s`}
repeatCount="indefinite"
/>
</circle>
);
})}
</g>
</svg>
{/* Desktop SVG - Original */}
<svg
viewBox="-400 -200 1800 1100"
className="w-full h-full opacity-40 md:opacity-100 scale-150 md:scale-100 translate-x-1/4 md:translate-x-0"
preserveAspectRatio="xMidYMid slice"
className="w-full h-full opacity-100 scale-100 translate-x-0 hidden md:block"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
@@ -741,7 +1319,7 @@ export default function HeroIllustration() {
</g>
</svg>
<div className="absolute inset-0 bg-gradient-to-b from-primary/10 via-transparent to-primary/90" />
<div className="absolute inset-0 bg-gradient-to-b from-primary/10 via-transparent to-primary/90 pointer-events-none" />
</div>
);
}

View File

@@ -30,7 +30,7 @@ export default function MeetTheTeam() {
<div className="relative mb-12">
<div className="absolute -left-8 top-0 bottom-0 w-1 bg-accent rounded-full" />
<p className="text-2xl md:text-3xl leading-relaxed font-medium italic text-white/90 pl-8">
<p className="text-xl md:text-2xl leading-relaxed font-medium italic text-white/90 pl-8">
"{t('description')}"
</p>
</div>
@@ -50,7 +50,7 @@ export default function MeetTheTeam() {
<Image src="/uploads/2024/12/DSC07963-Large.webp" alt={teamT('klaus.name')} fill className="object-cover" />
</div>
</div>
<span className="text-white/60 font-bold text-sm uppercase tracking-widest">
<span className="text-white/60 font-bold text-xs md:text-sm uppercase tracking-widest">
{t('andNetwork')}
</span>
</div>

View File

@@ -58,12 +58,12 @@ export default function ProductCategories() {
<div className="w-12 h-12 md:w-16 md:h-16 bg-white/10 backdrop-blur-md rounded-xl flex items-center justify-center mb-4 md:mb-6 border border-white/20">
<Image src={category.icon} alt="" width={40} height={40} className="w-8 h-8 md:w-10 md:h-10 brightness-0 invert" unoptimized />
</div>
<h3 className="text-2xl md:text-3xl font-bold mb-2 md:mb-4 leading-tight">{category.title}</h3>
<p className="text-white/80 text-base md:text-lg line-clamp-3 opacity-100 md:opacity-0 group-hover:opacity-100 transition-all duration-500 max-h-24 md:max-h-0 group-hover:max-h-32">
<h3 className="text-xl md:text-2xl font-bold mb-2 md:mb-4 leading-tight">{category.title}</h3>
<p className="text-white/80 text-sm md:text-base line-clamp-3 opacity-100 md:opacity-0 group-hover:opacity-100 transition-all duration-500 max-h-24 md:max-h-0 group-hover:max-h-32">
{category.desc}
</p>
</div>
<div className="flex items-center text-accent font-bold tracking-wider uppercase text-xs md:text-sm opacity-100 md:opacity-0 group-hover:opacity-100 transition-all duration-500 delay-100">
<div className="flex items-center text-accent font-bold tracking-wider uppercase text-xs md:text-xs opacity-100 md:opacity-0 group-hover:opacity-100 transition-all duration-500 delay-100">
{t('exploreCategory')} <span className="ml-2 transition-transform group-hover:translate-x-2">&rarr;</span>
</div>
</div>

View File

@@ -19,10 +19,10 @@ export default async function RecentPosts({ locale }: RecentPostsProps) {
<Section className="bg-neutral py-16 md:py-24">
<Container>
<div className="flex flex-col md:flex-row items-start md:items-end justify-between mb-12 md:mb-16 gap-6">
<Heading level={2} subtitle={t('latestNews')} className="mb-0">
<Heading level={2} subtitle={t('latestNews')} className="mb-0 text-primary">
{t('allArticles')}
</Heading>
<Link href={`/${locale}/blog`} className="group flex items-center text-primary font-bold text-lg touch-target">
<Link href={`/${locale}/blog`} className="group flex items-center text-primary font-bold text-base md:text-lg touch-target">
{t('allArticles')}
<span className="ml-2 transition-transform group-hover:translate-x-2">&rarr;</span>
</Link>
@@ -56,7 +56,7 @@ export default async function RecentPosts({ locale }: RecentPostsProps) {
day: 'numeric'
})}
</div>
<h3 className="text-xl md:text-2xl font-bold text-primary group-hover:text-accent-dark transition-colors line-clamp-2 mb-4 md:mb-6 leading-tight">
<h3 className="text-lg md:text-xl font-bold text-primary group-hover:text-accent-dark transition-colors line-clamp-2 mb-4 md:mb-6 leading-tight">
{post.frontmatter.title}
</h3>
<div className="mt-auto flex items-center text-primary font-bold group-hover:underline decoration-2 underline-offset-4">

View File

@@ -17,7 +17,7 @@ export default function VideoSection() {
</video>
<div className="absolute inset-0 bg-gradient-to-b from-primary/60 via-transparent to-primary/60 flex items-center justify-center">
<div className="max-w-5xl px-6 text-center animate-slide-up">
<h2 className="text-4xl md:text-6xl lg:text-7xl font-extrabold text-white leading-[1.1]">
<h2 className="text-3xl md:text-4xl lg:text-5xl font-extrabold text-white leading-[1.1]">
{t.rich('title', {
future: (chunks) => (
<span className="relative inline-block mx-2">

View File

@@ -6,19 +6,19 @@ export default function WhatWeDo() {
const t = useTranslations('Home.whatWeDo');
return (
<Section className="bg-white text-neutral-dark">
<Section className="bg-white">
<Container>
<div className="sticky-narrative-container">
<div className="sticky-narrative-sidebar">
<div className="lg:sticky lg:top-32">
<Heading level={2} subtitle={t('expertise')}>
<Heading level={2} subtitle={t('expertise')} className="text-primary-dark">
{t('title')}
</Heading>
<p className="text-lg md:text-xl text-text-secondary leading-relaxed">
<p className="text-base md:text-lg text-text-secondary leading-relaxed">
{t('subtitle')}
</p>
<div className="mt-8 md:mt-12 p-6 md:p-8 bg-saturated/10 rounded-2xl border border-saturated/10">
<p className="text-saturated font-bold text-base md:text-lg italic">
<p className="text-saturated font-bold text-base md:text-base italic">
"{t('quote')}"
</p>
</div>
@@ -33,8 +33,8 @@ export default function WhatWeDo() {
</span>
<div className="h-px flex-grow bg-neutral-medium" />
</div>
<h3 className="text-xl md:text-2xl font-bold mb-3 md:mb-4 text-saturated group-hover:text-accent-dark transition-colors">{t(`items.${idx}.title`)}</h3>
<p className="text-text-secondary text-base md:text-lg leading-relaxed">{t(`items.${idx}.description`)}</p>
<h3 className="text-lg md:text-xl font-bold mb-3 md:mb-4 text-primary-dark group-hover:text-accent-dark transition-colors">{t(`items.${idx}.title`)}</h3>
<p className="text-text-secondary text-base md:text-base leading-relaxed">{t(`items.${idx}.description`)}</p>
</div>
))}
</div>

View File

@@ -6,15 +6,15 @@ export default function WhyChooseUs() {
const t = useTranslations('Home.whyChooseUs');
return (
<Section className="bg-neutral-light text-neutral-dark">
<Section className="bg-neutral-light">
<Container>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 lg:gap-24">
<div className="lg:col-span-4 order-1 lg:order-2">
<div className="sticky top-32">
<Heading level={2} subtitle={t('whyKlz')}>
<Heading level={2} subtitle={t('whyKlz')} className="text-primary-dark">
{t('title')}
</Heading>
<p className="text-xl text-text-secondary leading-relaxed">
<p className="text-base md:text-lg text-text-secondary leading-relaxed">
{t('subtitle')}
</p>
@@ -26,7 +26,7 @@ export default function WhyChooseUs() {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
</div>
<span className="font-bold text-saturated">{t(`features.${i}`)}</span>
<span className="font-bold text-primary-dark text-base md:text-base">{t(`features.${i}`)}</span>
</div>
))}
</div>
@@ -36,10 +36,10 @@ export default function WhyChooseUs() {
{[0, 1, 2, 3].map((idx) => (
<div key={idx} className="p-10 bg-white rounded-3xl border border-neutral-medium hover:border-accent transition-all duration-500 hover:shadow-xl group">
<div className="w-14 h-14 bg-saturated/10 rounded-2xl flex items-center justify-center mb-8 group-hover:bg-accent transition-colors duration-500">
<span className="text-saturated font-bold text-xl group-hover:text-primary-dark">0{idx + 1}</span>
<span className="text-white font-bold text-lg group-hover:text-primary-dark">0{idx + 1}</span>
</div>
<h3 className="text-2xl font-bold mb-4 text-saturated">{t(`items.${idx}.title`)}</h3>
<p className="text-text-secondary text-lg leading-relaxed">{t(`items.${idx}.description`)}</p>
<h3 className="text-xl font-bold mb-4 text-primary-dark">{t(`items.${idx}.title`)}</h3>
<p className="text-text-secondary text-base md:text-base leading-relaxed">{t(`items.${idx}.description`)}</p>
</div>
))}
</div>

View File

@@ -0,0 +1,55 @@
'use client';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import Image from 'next/image';
import Lightbox from '@/components/Lightbox';
import { Section, Container, Heading } from '@/components/ui';
export default function Gallery() {
const t = useTranslations('Team');
const [lightboxOpen, setLightboxOpen] = useState(false);
const [lightboxIndex, setLightboxIndex] = useState(0);
const teamGalleryImages = [
'/uploads/2024/12/DSC07539-Large-600x400.webp',
'/uploads/2024/12/DSC07460-Large-600x400.webp',
'/uploads/2024/12/DSC07469-Large-600x400.webp',
'/uploads/2024/12/DSC07433-Large-600x400.webp',
'/uploads/2024/12/DSC07387-Large-600x400.webp'
];
return (
<Section className="bg-primary-dark py-16 md:py-32">
<Container>
<Heading level={2} subtitle={t('gallery.subtitle')} align="center" className="text-white mb-12 md:mb-20">
<span className="text-white">{t('gallery.title')}</span>
</Heading>
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 md:gap-6">
{teamGalleryImages.map((src, idx) => (
<button
key={idx}
onClick={() => {
setLightboxIndex(idx);
setLightboxOpen(true);
}}
className="relative aspect-[3/4] rounded-2xl md:rounded-[32px] overflow-hidden group shadow-2xl cursor-pointer"
>
<Image src={src} alt={t('gallery.title')} fill className="object-cover transition-transform duration-1000 group-hover:scale-110" />
<div className="absolute inset-0 bg-primary-dark/40 group-hover:bg-transparent transition-all duration-500" />
<div className="absolute inset-0 border-0 group-hover:border-[8px] md:group-hover:border-[12px] border-white/10 transition-all duration-500 pointer-events-none" />
</button>
))}
</div>
</Container>
<Lightbox
isOpen={lightboxOpen}
images={teamGalleryImages}
initialIndex={lightboxIndex}
onClose={() => setLightboxOpen(false)}
/>
</Section>
);
}

View File

@@ -11,36 +11,63 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
}
export function Button({ className, variant = 'primary', size = 'md', href, ...props }: ButtonProps) {
const baseStyles = 'inline-flex items-center justify-center rounded-full font-semibold transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none active:scale-95';
const baseStyles = 'inline-flex items-center justify-center rounded-full font-semibold transition-all duration-500 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none active:scale-95 hover:-translate-y-1 hover:scale-[1.02] relative overflow-hidden group/btn isolate';
const variants = {
primary: 'bg-primary text-white hover:bg-primary-dark shadow-md hover:shadow-lg',
secondary: 'bg-secondary text-white hover:bg-secondary-light shadow-md hover:shadow-lg',
accent: 'bg-accent text-primary-dark hover:bg-accent-dark shadow-md hover:shadow-lg',
saturated: 'bg-saturated text-white hover:bg-primary shadow-md hover:shadow-lg',
outline: 'border-2 border-primary bg-transparent hover:bg-primary hover:text-white text-primary',
ghost: 'hover:bg-primary-light text-primary',
white: 'bg-white text-primary hover:bg-neutral-light shadow-md hover:shadow-lg',
primary: 'bg-primary text-white shadow-md hover:shadow-primary/30 hover:shadow-2xl',
secondary: 'bg-secondary text-white shadow-md hover:shadow-secondary/30 hover:shadow-2xl',
accent: 'bg-accent text-primary-dark shadow-md hover:shadow-accent/30 hover:shadow-2xl',
saturated: 'bg-saturated text-white shadow-md hover:shadow-primary/30 hover:shadow-2xl',
outline: 'border-2 border-primary bg-transparent text-primary hover:text-white hover:shadow-primary/20 hover:shadow-xl',
ghost: 'text-primary hover:shadow-lg',
white: 'bg-white text-primary shadow-md hover:shadow-primary/30 hover:shadow-2xl hover:text-white',
};
const sizes = {
sm: 'h-9 px-4 text-sm',
md: 'h-11 px-6 text-base',
lg: 'h-14 px-8 text-lg',
xl: 'h-16 px-10 text-xl',
sm: 'h-9 px-4 text-sm md:text-base',
md: 'h-11 px-6 text-base md:text-lg',
lg: 'h-14 px-8 text-base md:text-lg',
xl: 'h-16 px-10 text-lg md:text-xl',
};
const styles = cn(baseStyles, variants[variant], sizes[size], className);
const overlayColors = {
primary: 'bg-primary-dark',
secondary: 'bg-secondary-light',
accent: 'bg-accent-dark',
saturated: 'bg-primary',
outline: 'bg-primary',
ghost: 'bg-primary-light/10',
white: 'bg-primary-light',
};
const content = (
<>
<span className={cn(
"relative z-10 flex items-center justify-center gap-2 transition-colors duration-500",
variant === 'white' ? "group-hover/btn:text-primary-dark" : ""
)}>
{props.children}
</span>
<span className={cn(
"absolute inset-0 translate-y-[101%] group-hover/btn:translate-y-0 transition-transform duration-500 ease-out",
overlayColors[variant]
)} />
</>
);
if (href) {
return (
<Link href={href} className={styles}>
{props.children}
{content}
</Link>
);
}
return (
<button className={styles} {...props} />
<button className={styles} {...props}>
{content}
</button>
);
}

View File

@@ -54,9 +54,9 @@ export function Callout({ type = 'info', title, children, className }: CalloutPr
</div>
<div className="flex-1">
{title && (
<h4 className="font-bold text-lg mb-2">{title}</h4>
<h4 className="font-bold text-base md:text-lg mb-2">{title}</h4>
)}
<div className="prose prose-sm max-w-none">
<div className="prose prose-sm md:prose-base max-w-none">
{children}
</div>
</div>

View File

@@ -1,15 +1,15 @@
import React from 'react';
import { cn } from './utils';
export function Heading({
level = 2,
children,
className,
export function Heading({
level = 2,
children,
className,
subtitle,
align = 'left'
}: {
level?: 1 | 2 | 3 | 4;
children: React.ReactNode;
}: {
level?: 1 | 2 | 3 | 4 | 5 | 6;
children: React.ReactNode;
className?: string;
subtitle?: string;
align?: 'left' | 'center' | 'right';
@@ -17,10 +17,12 @@ export function Heading({
const Tag = `h${level}` as any;
const sizes = {
1: 'text-4xl md:text-6xl lg:text-7xl xl:text-8xl font-extrabold leading-[1.1]',
2: 'text-3xl md:text-5xl lg:text-6xl font-bold leading-tight',
3: 'text-2xl md:text-3xl lg:text-4xl font-bold',
4: 'text-xl md:text-2xl font-bold',
1: 'text-2xl md:text-4xl lg:text-5xl font-bold leading-[1.1] tracking-tight',
2: 'text-xl md:text-3xl lg:text-4xl font-bold leading-[1.2] tracking-tight',
3: 'text-lg md:text-2xl lg:text-3xl font-bold leading-[1.3] tracking-tight',
4: 'text-lg md:text-xl lg:text-2xl font-bold leading-[1.4]',
5: 'text-base md:text-lg font-bold leading-[1.5]',
6: 'text-base md:text-lg font-semibold leading-[1.6]',
};
const alignments = {
@@ -30,7 +32,7 @@ export function Heading({
};
return (
<div className={cn('mb-8 md:mb-16 text-primary', alignments[align], className)}>
<div className={cn('mb-6 md:mb-12 text-primary', alignments[align], className)}>
{subtitle && (
<span className="inline-block text-accent font-bold tracking-widest uppercase text-xs md:text-sm mb-3 md:mb-4 animate-fade-in">
{subtitle}

View File

@@ -1,6 +1,6 @@
---
title: 'Windparkbau im Fokus: drei typische Kabelherausforderungen'
date: '2025-11-05T10:16:10'
date: '2025-01-05T10:16:10'
featuredImage: /uploads/2025/04/image_fx_-2025-02-20T193520.620.webp
locale: de
category: Kabel Technologie
@@ -81,11 +81,13 @@ title="Wann klagt der NABU gegen Windkraftprojekte?"
summary="45 Klagen wurden wegen Fehlplanungen bei Windenergie zwischen 2010 und 2019 vom NABU auf den Weg gebracht. Nicht weil der Windenergieausbau aufgehalten werden soll, sondern weil immer wieder Vorhaben und Planungen eklatant gegen Naturschutzrecht verstoßen."
image="https://www.nabu.de/imperia/md/nabu/images/umwelt/energie/energietraeger/windkraft/161125-nabu-windrad-allgaeu-heidrun-burchard.jpeg"
/>
<AnimatedImage src="/uploads/2025/02/image_fx_-7.webp" alt="Windpark Baustelle" width={1408} height={768} />
## Qualität und Nachhaltigkeit als Erfolgsfaktor
Neben Zeit und Logistik spielt auch die [**Kabelqualität**](https://www.windkraft-journal.de/2025/07/14/planungsempfehlung-bei-der-verkabelung-von-windparks-durch-wind-turbine-com/214028) eine entscheidende Rolle für die langfristige Performance eines **Windparks**. Schließlich sollen die installierten **[Mittelspannungs](/de/stromkabel/mittelspannungskabel/) und [Hochspannungskabel](/de/stromkabel/hochspannungskabel/)** über Jahrzehnte zuverlässig Energie übertragen selbst unter extremen Witterungsbedingungen und wechselnden Lastzyklen.
Ein hochwertiges **Kabelsystem für Windkraftanlagen** zeichnet sich durch mehrere Faktoren aus:
- **Materialqualität:** VPE-isolierte Kabel wie [**NA2XS(F)2Y** ](/de/products/medium-voltage-cables/na2xsf2y/)oder [**N2XS(F)2Y**](/de/products/medium-voltage-cables/n2xsf2y/) bieten hohe elektrische Festigkeit und exzellenten Langzeitschutz.
- **[Normkonformität](https://www.zvei.org/fileadmin/user_upload/Presse_und_Medien/Publikationen/2017/September/ZVEI_Leitfaden_Kabel_und_Leitungen_in_Windkraftanlagen/ZVEI-Leitfaden-Kabel-und-Leitungen-in-Windkraftanlagen-September-2017.pdf):** Alle eingesetzten Komponenten sollten den einschlägigen Normen wie **DIN VDE 0276**, **VDE 0298** oder **IEC 60502** entsprechen.
- **Normkonformität (PDF):** [Alle eingesetzten Komponenten sollten den einschlägigen Normen](https://www.zvei.org/fileadmin/user_upload/Presse_und_Medien/Publikationen/2017/September/ZVEI_Leitfaden_Kabel_und_Leitungen_in_Windkraftanlagen/ZVEI-Leitfaden-Kabel-und-Leitungen-in-Windkraftanlagen-September-2017.pdf) wie **DIN VDE 0276**, **VDE 0298** oder **IEC 60502** entsprechen.
- **Montagefreundlichkeit:** Die Kabelkonstruktion muss so ausgelegt sein, dass sie sich effizient und sicher verlegen lässt auch bei schwierigen Bodenbedingungen.
- **Umweltaspekte:** Recyclingfähige Materialien und die [Wiederverwendung von Trommeln oder Leitermaterialien](/de/recycling-von-kabeltrommeln-nachhaltigkeit-im-windkraftprojekt/) reduzieren den ökologischen Fußabdruck.
@@ -93,6 +95,8 @@ Immer mehr Projektentwickler legen Wert auf **nachhaltige Kabelsysteme**, die En
Die Kombination aus **technischer Qualität**, **ökologischer Verantwortung** und **effizienter Logistik** macht moderne **Windparkverkabelung** zu einem zentralen Erfolgsfaktor im Netzausbau. Wer hier auf durchdachte Lösungen setzt, schafft die Basis für einen stabilen und nachhaltigen Energiefluss heute und in Zukunft.
[Welche Kabel Sie für Ihr Windparkprojekt brauchen und welche Unterschiede es gibt, erklären wir Ihnen hier.](/de/welche-kabel-fuer-windkraft-unterschiede-von-nieder-bis-hoechstspannung-erklaert/)
<AnimatedImage src="/uploads/2025/01/offshore-wind-power-and-energy-farm-with-many-wind-2023-11-27-04-51-29-utc-scaled.webp" alt="Windpark Landschaft" width={2560} height={1707} />
## Fazit: Erfolgreich ans Netz
Die Verkabelung ist das Rückgrat jedes **Windparks** und gleichzeitig einer der sensibelsten Projektbereiche. Enge Zeitpläne, komplexe Logistik und spontane Änderungen sind dabei keine Ausnahme, sondern Alltag. Wer diese Herausforderungen frühzeitig erkennt und gezielt plant, verhindert Stillstand, Kostensteigerungen und Terminverschiebungen.
Erfolgreiche **Windpark-Kabelprojekte** zeichnen sich durch drei Dinge aus:
@@ -106,4 +110,4 @@ Ob [**Mittelspannung**](/de/stromkabel/mittelspannungskabel/), **Erdkabel** oder
Unser Vorteil liegt in der **Praxisnähe**: Wir wissen, wie eng Bauzeiten im Windparkbau sind, welche Kabelsysteme sich bewährt haben und worauf es bei der Logistik wirklich ankommt. Durch unsere **Lagerkapazitäten in der Mitte Deutschlands** reagieren wir schnell auf Änderungen und halten Lieferketten stabil auch wenn Projekte dynamisch verlaufen.
Mit unserem Netzwerk, unserer Marktkenntnis und unserer Leidenschaft für erneuerbare Energien sorgen wir dafür, dass Ihr **Windkraftprojekt** pünktlich und reibungslos ans Netz geht.
➡️ **Planen Sie ein neues Windparkprojekt oder benötigen Unterstützung bei der Kabelauswahl?**Dann sprechen Sie uns an wir liefern die **Kabel, Lösungen und Erfahrung**, die Ihr Projekt erfolgreich machen.
[Jetzt Kontakt aufnehmen](/de/contact/)
[Jetzt Kontakt aufnehmen](/de/kontakt/)

View File

@@ -1,6 +1,6 @@
---
title: 'Focus on wind farm construction: three typical cable challenges'
date: '2025-11-05T10:16:15'
date: '2025-01-05T10:16:15'
featuredImage: /uploads/2025/04/image_fx_-2025-02-20T193520.620.webp
locale: en
category: Kabel Technologie
@@ -28,21 +28,14 @@ In this article, we look at the** 3 biggest challenges in wind farm construction
Find out why onshore wind farms are a crucial pillar of the energy transition here:
<VisualLinkPreview
url="
<VisualLinkPreview
<VisualLinkPreview
url="https://www.enbw.com/unternehmen/themen/windkraft/onshore-wind-pfeiler-der-energiewende.html"
title="Onshore-Windenergie als Pfeiler der Energiewende | EnBW"
summary="Viele Faktoren haben den Bau von Windenergieanlagen in den letzten Jahren gebremst. Lesen Sie hier die Gründe!"
image="https://www.enbw.com/media/image-proxy/1600x914,q70,focus50x49,zoom1.0/https://www.enbw.com/media/presse/images/newsroom/onshore-windpark-langenburg-7zu4_1701415033580.jpg"
/>
"
title="Onshore-Windenergie als Pfeiler der Energiewende | EnBW"
summary="Viele Faktoren haben den Bau von Windenergieanlagen in den letzten Jahren gebremst. Lesen Sie hier die Gründe!"
image="https://www.enbw.com/media/image-proxy/1600x914,q70,focus50x49,zoom1.0/https://www.enbw.com/media/presse/images/newsroom/onshore-windpark-langenburg-7zu4_1701415033580.jpg"
/>
<AnimatedImage src="/uploads/2025/04/image_fx_-7.webp" alt="Wind farm construction site" width={1408} height={768} />
<AnimatedImage src="/uploads/2025/02/image_fx_-7.webp" alt="Wind farm construction site" width={1408} height={768} />
## Challenge 1: Tight construction timelines and fixed deadlines
@@ -70,19 +63,12 @@ With precise [**cable capacity**](https://www.a-eberle.de/infobrief/infobrief-20
Want to know which cable types are used in wind farms? Check out this article:
<VisualLinkPreview
url="
<VisualLinkPreview
<VisualLinkPreview
url="https://wind-turbine.com/magazin/ratgeber/250713/welche-arten-von-kabeln-benoetigt-man-fuer-den-bau-eines-windparks.html"
title="Welche Arten von Kabeln benötigt man für den Bau eines Windparks?"
summary="Die Verkabelung ist ein zentrales Element jeder Windkraftanlage und beeinflusst maßgeblich die Effizienz, Sicherheit und Wirtschaftlichkeit eines Windparks.…"
image="https://wind-turbine.com/i/53689/68738caa5e58ffdf06031cf2/2/1200/630/68738c85497af_KabelfreinenWindparkpng.png"
/>
"
title="Welche Arten von Kabeln benötigt man für den Bau eines Windparks?"
summary="Die Verkabelung ist ein zentrales Element jeder Windkraftanlage und beeinflusst maßgeblich die Effizienz, Sicherheit und Wirtschaftlichkeit eines Windparks.…"
image="https://wind-turbine.com/i/53689/68738caa5e58ffdf06031cf2/2/1200/630/68738c85497af_KabelfreinenWindparkpng.png"
/>
## Challenge 2: Large delivery volumes and specialized packaging
@@ -109,7 +95,7 @@ A clear [**cable logistics strategy**](https://logistik-heute.de/galerien/mammut
Anyone who integrates **packaging, storage and labeling** early into the planning process ensures that the **wind farm cables** arrive exactly where they&#8217;re needed with no time lost and no disruption to the construction flow.
<AnimatedImage src="/uploads/2025/08/NA2XSF2Y_3x1x300_RM-25_12-20kV-0.webp" alt="NA2XSF2Y Cable" width={1920} height={1080} />
<AnimatedImage src="/uploads/2025/08/NA2XSF2X_3x1x300_RM-25_12-20kV-3.webp" alt="NA2XSF2Y Cable" width={1920} height={1080} />
## Challenge 3: Last-minute project changes
@@ -132,19 +118,12 @@ Short-term changes arent the exception theyre part of everyday life in
Avoid delays or issues during your wind power project by understanding early on why NABU may file objections to certain sites:
<VisualLinkPreview
url="
<VisualLinkPreview
<VisualLinkPreview
url="https://www.nabu.de/umwelt-und-ressourcen/energie/erneuerbare-energien-energiewende/windenergie/26913.html"
title="Wann klagt der NABU gegen Windkraftprojekte?"
summary="45 Klagen wurden wegen Fehlplanungen bei Windenergie zwischen 2010 und 2019 vom NABU auf den Weg gebracht. Nicht weil der Windenergieausbau aufgehalten werden soll, sondern weil immer wieder Vorhaben und Planungen eklatant gegen Naturschutzrecht verstoßen."
image="https://www.nabu.de/imperia/md/nabu/images/umwelt/energie/energietraeger/windkraft/161125-nabu-windrad-allgaeu-heidrun-burchard.jpeg"
/>
"
title="Wann klagt der NABU gegen Windkraftprojekte?"
summary="45 Klagen wurden wegen Fehlplanungen bei Windenergie zwischen 2010 und 2019 vom NABU auf den Weg gebracht. Nicht weil der Windenergieausbau aufgehalten werden soll, sondern weil immer wieder Vorhaben und Planungen eklatant gegen Naturschutzrecht verstoßen."
image="https://www.nabu.de/imperia/md/nabu/images/umwelt/energie/energietraeger/windkraft/161125-nabu-windrad-allgaeu-heidrun-burchard.jpeg"
/>
## Quality and sustainability as success factors
@@ -152,7 +131,7 @@ In addition to time and logistics, [**cable quality**](https://www.windkraft-jou
A high-quality **cable system for wind power** stands out due to several factors:
- **Material quality:** XLPE-insulated cables like [**NA2XS(F)2Y**](/en/products/medium-voltage-cables/na2xsf2y/) or [**N2XS(F)2Y**](/en/products/medium-voltage-cables/n2xsf2y/) provide high dielectric strength and excellent long-term protection.
- **[Standards compliance](https://www.zvei.org/fileadmin/user_upload/Presse_und_Medien/Publikationen/2017/September/ZVEI_Leitfaden_Kabel_und_Leitungen_in_Windkraftanlagen/ZVEI-Leitfaden-Kabel-und-Leitungen-in-Windkraftanlagen-September-2017.pdf):** All components used should meet key standards such as **DIN VDE 0276**, **VDE 0298**, or **IEC 60502**.
- **Standards compliance (PDF):** [All components used should meet key standards](https://www.zvei.org/fileadmin/user_upload/Presse_und_Medien/Publikationen/2017/September/ZVEI_Leitfaden_Kabel_und_Leitungen_in_Windkraftanlagen/ZVEI-Leitfaden-Kabel-und-Leitungen-in-Windkraftanlagen-September-2017.pdf) such as **DIN VDE 0276**, **VDE 0298**, or **IEC 60502**.
- **Ease of installation:** Cable design must allow for efficient and safe installation even under difficult ground conditions.
- **Environmental aspects:** Recyclable materials and the [reuse of drums or conductor materials](/de/recycling-von-kabeltrommeln-nachhaltigkeit-im-windkraftprojekt/) help reduce ecological footprint.
@@ -162,7 +141,7 @@ The combination of **technical quality**, **ecological responsibility**, and **e
[Find out here which cables are suitable for your wind farm project and what makes the difference between low and high voltage options.](/de/welche-kabel-fuer-windkraft-unterschiede-von-nieder-bis-hoechstspannung-erklaert/)
<AnimatedImage src="/uploads/2025/01/green-alternative-eco-friendly-energy-windmill-tu-2023-11-27-05-10-51-utc-scaled.webp" alt="Wind farm landscape" width={2560} height={1707} />
<AnimatedImage src="/uploads/2025/01/offshore-wind-power-and-energy-farm-with-many-wind-2023-11-27-04-51-29-utc-scaled.webp" alt="Wind farm landscape" width={2560} height={1707} />
## Conclusion: Successfully connected to the grid

Binary file not shown.

220
data/pages(1).csv Normal file
View File

@@ -0,0 +1,220 @@
Title,Visitors,Views,"View Duration","Bounce Rate",URL,"Page Type"
"Home &#8211; English",2643,4037,0:57,52.6996,/,Page
"Home &#8211; Deutsch",1483,2444,0:47,20.2907,/de/start/,Page
"Team &#8211; Deutsch",944,1352,1:13,11.8547,/de/team/,Page
"Legal Notice &#8211; Deutsch",521,653,1:05,5.8615,/de/impressum/,Page
"Team &#8211; English",441,569,1:11,8.0745,/team/,Page
"Kabelabkürzungen entschlüsselt der Schlüssel zur richtigen Kabelwahl",397,458,2:57,84.3521,/de/kabelabkuerzungen-entschluesselt-der-schluessel-zur-richtigen-kabelwahl/,Post
"Contact &#8211; English",318,487,2:01,12.7841,/contact/,Page
"Contact &#8211; Deutsch",312,424,1:35,11.3372,/de/kontakt/,Page
"Medium Voltage Cables",309,447,0:39,14.121,/power-cables/medium-voltage-cables/,Category
N2XS(FL)2Y,304,397,3:02,59.2375,/de/produkte/stromkabel/hochspannungskabel/n2xsfl2y/,Product
Mittelspannungskabel,284,405,0:33,6.25,/de/stromkabel/mittelspannungskabel/,Category
"Low Voltage Cables",255,348,0:53,3.3898,/power-cables/low-voltage-cables/,Category
NA2XS(FL)2Y,235,302,2:59,55.0781,/de/produkte/stromkabel/hochspannungskabel/na2xsfl2y/,Product
"Blog &#8211; English",229,424,0:37,8.1395,/blog/,Blog
NA2XS(F)2Y,213,316,1:47,44.9219,/de/produkte/stromkabel/mittelspannungskabel/na2xsf2y-2/,Product
"Sicherheit bei Kabeltrommeln: Unfallfrei und effizient arbeiten",206,244,2:24,80.543,/de/sicherheit-bei-kabeltrommeln-unfallfrei-und-effizient-arbeiten/,Post
Stromkabel,199,269,0:20,4.5045,/de/stromkabel/,Category
Hochspannungskabel,194,245,0:36,4.3478,/de/stromkabel/hochspannungskabel/,Category
Niederspannungskabel,184,252,0:34,6.9124,/de/stromkabel/niederspannungskabel/,Category
"Power Cables",184,231,0:20,3.9409,/power-cables/,Category
"High Voltage Cables",171,238,0:18,8.8398,/power-cables/high-voltage-cables/,Category
"Solar Cables",154,213,0:46,7.0175,/solar-cables/,Category
"Welche Kabel für Windkraft? Unterschiede von Nieder- bis Höchstspannung erklärt",133,146,1:27,57.8571,/de/welche-kabel-fuer-windkraft-unterschiede-von-nieder-bis-hoechstspannung-erklaert/,Post
NA2XS(FL)2Y,122,138,0:45,57.6,/de/produkte/stromkabel/mittelspannungskabel/na2xsfl2y-2/,Product
Solarkabel,118,134,0:18,4.0984,/de/solarkabel/,Category
"Copper or aluminum cable? Cost comparison for underground cable and grid connection",108,131,3:02,84.1667,/cost-comparison-copper-vs-aluminum-cables-in-wind-farms-which-is-worthwhile-in-the-long-term/,Post
"Kupfer oder Aluminiumkabel im Windpark? Kostenvergleich für Erdkabel und Netzanschluss",108,120,4:08,85.0877,/de/kostenvergleich-kupfer-vs-aluminiumkabel-in-windparks-was-lohnt-sich-langfristig/,Post
"KLZ wächst weiter neue Stärke im Bereich Financial &amp; Sales",105,127,1:59,9.9099,/de/klz-waechst-weiter-neue-staerke-im-bereich-financial-sales/,Post
N2X2Y,105,143,4:01,64.7059,/de/produkte/stromkabel/niederspannungskabel/n2x2y-2/,Product
N2XS(F)2Y,104,135,0:53,38.843,/de/produkte/stromkabel/mittelspannungskabel/n2xsf2y-2/,Product
"Cable drum safety: Ensuring smooth operations and accident-free environments",102,114,3:02,79.2453,/cable-drum-safety-ensuring-smooth-operations-and-accident-free-environments/,Post
"Which cables for wind power? Differences from low to extra-high voltage explained",98,117,5:29,81.982,/which-cables-for-wind-power-differences-from-low-to-extra-high-voltage-explained-2/,Post
H1Z2Z2-K,89,111,2:43,48.9362,/de/produkte/solarkabel/h1z2z2-k/,Product
N2XS(FL)2Y,87,110,1:39,58.6957,/de/produkte/stromkabel/mittelspannungskabel/n2xsfl2y-2/,Product
"Engpass bei NA2XSF2Y? Wir haben das Dreileiter-Mittelspannungskabel",86,119,2:05,33.6842,/de/na2xsf2y-dreileiter-mittelspannungskabel-lieferbar/,Post
N2XS(FL)2Y,86,96,2:53,46.6667,/products/power-cables/high-voltage-cables/n2xsfl2y/,Product
Shop,81,97,0:26,16.4706,/products/,Shop
NYCWY,78,97,1:47,78.8235,/de/produkte/stromkabel/niederspannungskabel/nycwy-2/,Product
"Cable abbreviations decoded the key to choosing the right cable",74,85,2:00,84.3373,/cable-abbreviations-decoded-the-key-to-choosing-the-right-cable/,Post
NA2XS(FL)2Y,73,86,1:07,53.2468,/products/power-cables/high-voltage-cables/na2xsfl2y-3/,Product
H1Z2Z2-K,69,82,3:35,44.5946,/products/solar-cables/h1z2z2-k/,Product
"KLZ Neuigkeiten",68,75,0:56,8.8235,/de/klz-neuigkeiten/,Category
"Zukunft sichern mit H1Z2Z2-K: Unser Solarkabel zur Intersolar 2025",66,75,2:18,63.6364,/de/zukunft-sichern-mit-h1z2z2-k-unser-solarkabel-zur-intersolar-2025/,Post
N2X2Y,65,75,3:02,54.1667,/products/power-cables/low-voltage-cables/n2x2y/,Product
"Terms &#8211; English",63,86,0:33,37.3134,/terms/,Page
"Kabel Technologie",61,71,0:47,3.125,/de/kabel-technologie/,Category
"Recycling of cable drums: sustainability in wind power projects",60,77,1:30,66.1972,/recycling-of-cable-drums-sustainability-in-wind-power-projects/,Post
"Legal Notice &#8211; English",59,66,1:15,7.8125,/legal-notice/,Page
"Terms &#8211; Deutsch",57,73,1:40,16.9231,/de/agbs/,Page
"Recycling von Kabeltrommeln: Nachhaltigkeit im Windkraftprojekt",54,62,2:47,63.1579,/de/recycling-von-kabeltrommeln-nachhaltigkeit-im-windkraftprojekt/,Post
"Welcome to the future of KLZ: our new website is live!",54,66,1:14,46.7742,/welcome-to-the-future-of-klz-our-new-website-is-live/,Post
N2XSY,48,52,3:03,54.902,/products/power-cables/medium-voltage-cables/n2xsy/,Product
N2XY,48,60,0:32,67.7966,/products/power-cables/low-voltage-cables/n2xy/,Product
NA2XS2Y,48,60,2:12,54.1667,/products/power-cables/medium-voltage-cables/na2xs2y/,Product
"Die besten Erdkabel für Windkraft und Solar jetzt bei uns bestellen",47,54,1:53,41.6667,/de/die-besten-erdkabel-fuer-windkraft-und-solar-jetzt-bei-uns-bestellen/,Post
"The art of cable logistics: moving the backbone of modern energy networks",47,51,2:27,86,/the-art-of-cable-logistics-moving-the-backbone-of-modern-energy-networks/,Post
N2XS(F)2Y,46,60,1:11,22.449,/products/power-cables/medium-voltage-cables/n2xsf2y/,Product
N2XY,46,52,1:10,67.3469,/de/produkte/stromkabel/niederspannungskabel/n2xy-2/,Product
NA2XY,46,65,3:03,48.2759,/products/power-cables/low-voltage-cables/na2xy/,Product
2025-01,45,55,0:19,4.4444,/2025/01/,"Date Archive (Month)"
"Cable Logistics",43,48,0:44,15.9091,/cable-logistics/,Category
Kabel-Logistik,43,50,0:22,2.2727,/de/kabel-logistik/,Category
"Shortage of NA2XSF2Y? We have the three-core medium-voltage cable",40,42,1:01,64.2857,/na2xsf2y-three-conductor-medium-voltage-cable-available/,Post
"Milliarden-Paket für Infrastruktur: Der Kabel-Boom steht bevor",38,43,3:05,38.4615,/de/milliarden-paket-fuer-infrastruktur-der-kabel-boom-steht-bevor/,Post
NA2XS(F)2Y,38,55,2:38,35.5556,/products/power-cables/medium-voltage-cables/na2xsf2y/,Product
"Warum das NA2XS(F)2Y das ideale Kabel für Ihr Energieprojekt ist",38,51,3:11,18.1818,/de/n2xsf2y-mittelspannungskabel-energieprojekt/,Post
2025-08,37,37,0:20,2.7027,/2025/08/,"Date Archive (Month)"
"Cable Technology",37,41,0:16,8.1081,/cable-technology/,Category
"KLZ News",33,37,0:47,8.8235,/klz-news/,Category
2025-11,32,35,0:11,3.125,/2025/11/,"Date Archive (Month)"
NA2XSY,31,37,2:45,38.7097,/de/produkte/stromkabel/mittelspannungskabel/na2xsy-2/,Product
"Securing the future with H1Z2Z2-K: Our solar cable for Intersolar 2025",31,35,3:29,41.9355,/securing-the-future-with-h1z2z2-k-our-solar-cable-for-intersolar-2025/,Post
NA2XY,30,36,0:27,53.125,/de/produkte/stromkabel/niederspannungskabel/na2xy-2/,Product
"Windparkbau im Fokus: drei typische Kabelherausforderungen",30,33,1:29,32.2581,/de/windparkbau-im-fokus-drei-typische-kabelherausforderungen/,Post
N2XS2Y,29,49,0:30,24.3902,/de/produkte/stromkabel/mittelspannungskabel/n2xs2y-2/,Product
NAYCWY,29,31,0:59,70,/de/produkte/stromkabel/niederspannungskabel/naycwy-2/,Product
2025-06,28,28,0:45,7.1429,/2025/06/,"Date Archive (Month)"
NA2XS(FL)2Y,28,31,2:34,53.3333,/products/power-cables/medium-voltage-cables/na2xsfl2y/,Product
"Green energy starts underground &#8211; and with a plan",27,36,1:40,66.6667,/green-energy-starts-underground-and-with-a-plan/,Post
N2XSY,27,32,2:22,40.7407,/de/produkte/stromkabel/mittelspannungskabel/n2xsy-2/,Product
"Privacy Policy &#8211; English",27,29,0:15,14.8148,/privacy-policy/,Page
"So wählen Sie das richtige Kabel für Ihr nächstes Projekt aus",27,112,5:10,56.0976,/de/so-waehlen-sie-das-richtige-kabel-fuer-ihr-naechstes-projekt-aus/,Post
"Erneuerbare Energien",26,35,0:22,15.3846,/de/erneuerbare-energien/,Category
"Focus on wind farm construction: three typical cable challenges",26,32,4:43,77.7778,/focus-on-wind-farm-construction-three-typical-cable-challenges/,Post
NAY2Y,26,32,0:37,64.2857,/de/produkte/stromkabel/niederspannungskabel/nay2y-2/,Product
N2XS(FL)2Y,25,29,4:54,22.2222,/products/power-cables/medium-voltage-cables/n2xsfl2y-3/,Product
NA2X2Y,25,28,7:00,34.6154,/products/power-cables/low-voltage-cables/na2x2y/,Product
NYY,25,30,3:05,21.4286,/de/produkte/stromkabel/niederspannungskabel/nyy-2/,Product
"Privacy Policy &#8211; Deutsch",25,28,0:13,11.5385,/de/datenschutz/,Page
"Renewable Energy",25,31,0:17,16,/reneweable-energy/,Category
"KLZ im Adressbuch der Windenergie 2025",24,32,2:39,64.5161,/de/klz-im-adressbuch-der-windenergie-2025/,Post
NA2X2Y,23,29,1:35,38.4615,/de/produkte/stromkabel/niederspannungskabel/na2x2y-2/,Product
2025-09,22,22,0:08,0,/2025/09/,"Date Archive (Month)"
"Die perfekte Kabelanfrage: So sparen Sie sich unnötige Rückfragen",22,28,4:01,11.5385,/de/die-perfekte-kabelanfrage-so-sparen-sie-sich-unnoetige-rueckfragen/,Post
"Kabeltrommelqualität: Die Grundlage der Kabelzuverlässigkeit",22,25,0:41,17.3913,/de/kabeltrommelqualitaet-die-grundlage-der-kabelzuverlaessigkeit/,Post
"Why the N2XS(F)2Y is the ideal cable for your energy project",22,22,0:30,77.2727,/why-the-n2xsf2y-is-the-ideal-cable-for-your-energy-project/,Post
2025-02,21,23,0:12,0,/2025/02/,"Date Archive (Month)"
2025-10,21,26,1:00,0,/2025/10/,"Date Archive (Month)"
"Die Kunst der Kabellogistik: Der Transport des Fundamentes moderner Energienetze",21,22,2:34,14.2857,/de/die-kunst-der-kabellogistik-der-transport-des-fundamentes-moderner-energienetze/,Post
H1Z2Z2-K,21,26,2:17,13.6364,/products/solar-cables/h1z2z2-k/,Product
NAYY,21,34,1:57,18.1818,/de/produkte/stromkabel/niederspannungskabel/nayy-2/,Product
NY2Y,21,26,0:54,31.8182,/de/produkte/stromkabel/niederspannungskabel/ny2y-2/,Product
NYY,21,26,0:51,30.4348,/products/power-cables/low-voltage-cables/nyy/,Product
"The best underground cables for wind power and solar &#8211; order from us now",21,22,1:34,52.381,/the-best-underground-cables-for-wind-power-and-solar-order-from-us-now/,Post
"Warum die richtigen Kabel der geheime Held der grünen Energie sind",21,22,1:27,36.3636,/de/warum-die-richtigen-kabel-der-geheime-held-der-gruenen-energie-sind/,Post
NA2XS2Y,20,24,2:20,10,/de/produkte/stromkabel/mittelspannungskabel/na2xs2y-2/,Product
"Netzausbau 2025: Warum jede neue Leitung ein Schritt zur Energiewende ist",20,20,1:10,70,/de/netzausbau-2025-warum-jede-neue-leitung-ein-schritt-zur-energiewende-ist/,Post
NYCWY,20,23,1:58,55,/products/power-cables/low-voltage-cables/nycwy/,Product
404,19,19,-,100,/de/produkte/stromkabel/mittelspannungskabel,404
H1Z2Z2-K,19,23,4:37,10,/de/produkte/solarkabel/h1z2z2-k-2/,Product
"The perfect cable inquiry: How to save yourself unnecessary queries",19,19,0:32,47.3684,/the-perfect-cable-inquiry-how-to-save-yourself-unnecessary-queries/,Post
"Von smart bis nachhaltig: So sieht die Energiewirtschaft in naher Zukunft aus",19,20,1:24,57.8947,/de/von-smart-bis-nachhaltig-so-sieht-die-energiewirtschaft-in-naher-zukunft-aus/,Post
"Reicht Windenergie wirklich nicht? Ein Blick hinter die Schlagzeilen",18,19,0:58,27.7778,/de/reicht-windenergie-wirklich-nicht-ein-blick-hinter-die-schlagzeilen/,Post
2025-05,17,20,1:06,0,/2025/05/,"Date Archive (Month)"
N2XS2Y,17,20,6:33,52.9412,/products/power-cables/medium-voltage-cables/n2xs2y/,Product
"Willkommen in der Zukunft von KLZ: Unsere neue Website ist online!",17,19,0:45,11.7647,/de/willkommen-in-der-zukunft-von-klz-unsere-neue-website-ist-online/,Post
"100% renewable energy? Only with the right cable infrastructure!",16,16,1:55,56.25,/100-renewable-energy-only-with-the-right-cable-infrastructure/,Post
NA2XSY,16,19,4:57,22.2222,/products/power-cables/medium-voltage-cables/na2xsy/,Product
NAY2Y,16,17,3:33,64.7059,/products/power-cables/low-voltage-cables/nay2y/,Product
"Was macht ein erstklassiges Kabel aus? Finden Sie es hier heraus!",16,20,0:56,23.5294,/de/was-macht-ein-erstklassiges-kabel-aus-finden-sie-es-hier-heraus/,Post
"Wie die Kabelbranche Nachhaltigkeit und erneuerbare Energien vorantreibt",16,19,4:12,50,/de/wie-die-kabelbranche-nachhaltigkeit-und-erneuerbare-energien-vorantreibt/,Post
2025-03,15,16,0:13,6.25,/2025/03/,"Date Archive (Month)"
2025-04,15,15,0:45,0,/2025/04/,"Date Archive (Month)"
NAYCWY,15,19,3:47,26.6667,/products/power-cables/low-voltage-cables/naycwy/,Product
"What makes a first-class cable? Find out here!",15,15,0:43,20,/what-makes-a-first-class-cable-find-out-here/,Post
NY2Y,14,15,0:13,64.2857,/products/power-cables/low-voltage-cables/ny2y/,Product
"Why wind farm grid connection cables must withstand extreme loads",14,14,0:31,50,/why-wind-farm-grid-connection-cables-must-withstand-extreme-loads/,Post
"100 % erneuerbare Energie? Nur mit der richtigen Kabelinfrastruktur!",13,23,1:46,42.8571,/de/100-erneuerbare-energie-nur-mit-der-richtigen-kabelinfrastruktur/,Post
"Cable drum quality: the foundation of cable reliability",13,15,2:36,23.0769,/cable-drum-quality-the-foundation-of-cable-reliability/,Post
"Erkenntnisse über die grüne Energiewende: Herausforderungen und Chancen",13,13,0:23,23.0769,/de/erkenntnisse-ueber-die-gruene-energiewende-herausforderungen-und-chancen/,Post
"How to choose the right cable for your next project",13,16,0:22,20,/how-to-choose-the-right-cable-for-your-next-project/,Post
NAYY,13,13,1:17,38.4615,/products/power-cables/low-voltage-cables/nayy/,Product
"Billion-euro package for infrastructure: The cable boom is coming",12,15,0:01,40,/billion-euro-package-for-infrastructure-the-cable-boom-is-coming/,Post
"Das müssen Sie über erneuerbare Energien im Jahr 2025 wissen",12,13,0:20,25,/de/das-muessen-sie-ueber-erneuerbare-energien-im-jahr-2025-wissen/,Post
"Expanding the grid by 2025: Building the foundation for a successful energy transition",12,12,0:04,66.6667,/expanding-the-grid-by-2025-building-the-foundation-for-a-successful-energy-transition/,Post
"KLZ in the Directory of Wind Energy 2025",12,15,5:34,23.0769,/klz-in-the-directory-of-wind-energy-2025/,Post
Home,11,121,2:30,30,/,Page
"This what you need to know about renewable energies in 2025",11,13,0:44,45.4545,/this-what-you-need-to-know-about-renewable-energies-in-2025/,Post
2019-09,10,10,0:09,20,/2019/09/,"Date Archive (Month)"
2024-11,10,10,1:32,30,/2024/11/,"Date Archive (Month)"
404,10,20,0:35,0,/terms/):,404
"How the cable industry is driving sustainability and renewable energies forward",10,11,0:54,36.3636,/how-the-cable-industry-is-driving-sustainability-and-renewable-energies-forward/,Post
"Klimaneutral bis 2050? Was wir tun müssen, um das Ziel zu erreichen",10,10,0:35,20,/de/klimaneutral-bis-2050-was-wir-tun-muessen-um-das-ziel-zu-erreichen/,Post
2024-12,9,9,0:06,22.2222,/2024/12/,"Date Archive (Month)"
404,9,9,-,100,/checkout,404
404,8,10,0:08,0,/de/stromkabel/mittelspannungskabel/,404
404,8,13,0:56,0,/de/stromkabel/niederspannungskabel/,404
"Eye-opening realities of green energy transformation",8,12,0:34,37.5,/eye-opening-realities-of-green-energy-transformation/,Post
"How the right cables quietly power the green energy revolution",8,8,0:11,37.5,/how-the-right-cables-quietly-power-the-green-energy-revolution/,Post
"Grüne Energie beginnt unter der Erde und zwar mit Plan",7,10,0:34,22.2222,/de/gruene-energie-beginnt-unter-der-erde-und-zwar-mit-plan/,Post
"Is wind energy really enough? A deeper dive behind the headlines",7,7,3:26,42.8571,/is-wind-energy-really-enough-a-deeper-dive-behind-the-headlines/,Post
"Warum Windpark-Netzanschlusskabel extremen Belastungen standhalten müssen",7,7,1:33,28.5714,/de/warum-windpark-netzanschlusskabel-extremen-belastungen-standhalten-muessen/,Post
"From smart to sustainable: this is what the energy industry will look like in the near future",6,7,0:02,42.8571,/from-smart-to-sustainable-this-is-what-the-energy-industry-will-look-like-in-the-near-future/,Post
"Thanks &#8211; English",6,6,0:13,0,/thanks/,Page
404,5,7,0:05,0,/de/stromkabel/hochspannungskabel/,404
404,5,10,0:03,0,/de/stromkabel/,404
"Climate neutral by 2050? What we need to do to achieve this goal",5,5,0:06,40,/climate-neutral-by-2050-what-we-need-to-do-to-achieve-this-goal/,Post
"Thanks &#8211; Deutsch",5,5,0:20,20,/de/danke/,Page
"Blog &#8211; English",4,11,2:16,0,/blog/,Page
,3,4,0:13,0,0,-
404,3,3,0:09,33.3333,/de/produkte/solarkabel/h1z2z2-k-2/,404
404,3,4,0:03,0,/de/solarkabel/,404
"Marisa Archive",3,3,-,100,/author/70dd118b8358b039/,"Author Archive"
404,2,2,0:04,0,/de/solarkabel,404
404,2,2,0:02,0,/product-category/power-cables/medium-voltage-cables/,404
404,2,2,-,100,/wp-content/cache/breeze-minification/js/breeze_9805ec35ddc3b51dd052fc959c92b9c608e8c126b294bf6dff0059060fe0ba2075085254f24ff8659260baab9516eefc917b92bfac0673df7ab111c3c541429c.js,404
N2XS(F)2Y,2,4,0:15,0,/products/power-cables/medium-voltage-cables/n2xsf2y/,Product
N2XS(F)2Y,2,2,0:24,0,/products/power-cables/medium-voltage-cables/n2xsf2y/,Product
N2XS(F)2Y,2,3,0:04,0,/de/produkte/stromkabel/mittelspannungskabel/n2xsf2y/,Product
404,1,1,-,100,/products/power-cables/high-voltage-cables/n2xfk2y-high-voltage-cables/,404
404,1,2,0:00,0,/power-cables/high-voltage-cables/,404
404,1,2,0:11,0,/de/recycling-von-kabeltrommeln-nachhaltigkeit-im-windkraftprojekt/,404
404,1,1,0:01,0,/de/solarkabel/?orderby=popularity&amp;paged=1,404
404,1,1,0:03,0,/de/produkte/solarkabel/h1z2z2-k-2/bil,404
404,1,1,-,100,/wp-content/cache/breeze-minification/js/breeze_dcb838df562c4231e788912419a3f72faa5dd65dcdd419b7685048f4b1620f0c7a9e43bc80543ba26f26bac4a084ece9033b401a705fe0a49a1f3d7e192af842.js,404
404,1,1,0:01,0,/de/start/%20north%20dataklz%20cables%20north%20data,404
404,1,1,0:19,0,/de/stromkabel/mittelspannungskabel/n2xsy/,404
404,1,1,0:14,0,/stromkabel/niederspannungskabel/,404
404,1,2,0:26,0,/de/?p=46951&amp;preview=true,404
404,1,2,-,50,/product-category/power-cables/,404
404,1,1,-,100,/products/power-cables/high-voltage-cables/n2xsfl2y/beltrager,404
404,1,1,-,100,/de/stromkabel/mittelspannungskabel/?gad_source=2&amp;gclid=Cj0KCQjwmK_CBhCEARIsAMKwcD7ZMki42JRsyIqLhuIsjoD4EMyMuqZ7LTNVE1CQZz_g6OyC8AIYWPQaArMuEALw_wcB,404
404,1,1,0:15,0,/wp-content/uploads/2024/02/agbs.pdf,404
404,1,2,0:07,0,/power-cables/low-voltage-cables/,404
404,1,2,0:01,0,/power-cables/medium-voltage-cables/,404
404,1,1,-,100,/wp-content/cache/breeze-minification/js/breeze_8bc685a40ea644b02847846593300af35e19f3770486376bb3ef1bbe052f4385eef81507e0500134f2fe29ed003a03e22bfdecee306723fdee84216490592a2b.js,404
404,1,1,-,100,/wp-content/uploads/2024/12/Medium-Voltage-Cables-&ndash;-KLZ-Cables-12-30-2024_05_20_PM-477x1024.webp,404
404,1,1,-,100,/de/stromkabel/mittelspannungskabel/?gad_source=2&amp;gclid=Cj0KCQjwmK_CBhCEARIsAMKwcD5W7eUP8XTHo4k7XUuTjgMg0jTMJOyr6uNEk5Qz_HJTF1y8YXIz1q0aAm4qEALw_wcB,404
404,1,1,14:18,0,/stromkabel/mittelspannungskabel/,404
404,1,1,0:14,0,/de/start/%20north%20data,404
404,1,2,0:06,0,/de/?p=45906,404
404,1,1,-,100,/de/stromkabel/mittelspannungskabel/?gad_source=2&amp;wbraid=CloKCQjw9anCBhCfARJJAI67itmi53OghAGllVRruDcGaerAvNNjyLaeHscS7YPwfaDa15EOby39EIEP8xG-k11jnu8ulV4kj14DXeHVdUjMVjz2iF-jSBoCvMk,404
404,1,1,-,100,/de/stromkabel/mittelspannungskabel/?gad_source=2&amp;wbraid=CloKCQjw9anCBhCfARJJAI67itlMLr_lFFlTEuKOndhXM09OWwK_g5VQ7hvsZiACe06Tjh3mV5TaGtSc-4UZDex8xYI5jTrZFZM4nSaRkpzvELSCCNPNzxoCMTg,404
404,1,1,0:06,0,/cost-comparison-copper-vs-aluminum-cables-in-wind-farms-which-is-worthwhile-in-the-long-term/%5D,404
404,1,2,0:04,0,/solar-cables,404
404,1,3,0:06,0,/products/power-cables/low-voltage-cables/n2x2y/,404
404,1,1,0:03,0,/product-category/solar-cables/,404
404,1,1,-,100,/wp-content/cache/breeze-minification/js/breeze_797f299d7c49ad925d2a31e345811b1bea4e6e862eb72c2ff5da1fd85047a044c31e8c0b67ad3dee86ae3e300dc81f6b3f8c9e324971480a778d1ac464b1b1d7.js,404
404,1,1,-,100,/de/produkte/stromkabel/mittelspannungskabel/n2xsf2y-gm/?utm_source=chatgpt.com,404
404,1,1,-,100,/eajx%C3%82,404
404,1,1,-,100,/de/stromkabel/mittelspannungskabel/?gad_source=2&amp;gclid=Cj0KCQjwmK_CBhCEARIsAMKwcD6UUkHwiUqogoeZoSthCY0zq3meIDmEvjlye2z8I7wp-2zW8r_5cZkaAi6-EALw_wcB,404
404,1,2,0:05,0,/de/die-perfekte-kabelanfrage-so-sparen-sie-sich-unnoetige-rueckfragen/,404
404,1,1,0:10,0,/wp-content/uploads/2024/01/agbs.pdf,404
N2X2Y,1,2,-,50,/de/produkte/stromkabel/niederspannungskabel/n2x2y/,Product
N2X2Y,1,9,0:31,0,/products/power-cables/low-voltage-cables/n2x2y/,Product
N2XS(F)2Y,1,1,0:20,0,/de/produkte/stromkabel/mittelspannungskabel/n2xsf2y-2/,Product
N2XSY,1,1,0:34,0,/products/power-cables/medium-voltage-cables/n2xsy/,Product
N2XY,1,1,1:22,0,/products/power-cables/low-voltage-cables/n2xy/,Product
NA2XS(F)2Y,1,1,1:24,0,/products/power-cables/medium-voltage-cables/na2xsf2y/,Product
NA2XS(FL)2Y,1,1,0:54,0,/products/power-cables/medium-voltage-cables/na2xsfl2y/,Product
NA2XS(FL)2Y,1,1,0:13,0,/de/produkte/stromkabel/mittelspannungskabel/na2xsfl2y/,Product
NA2XSY,1,1,0:50,0,/products/power-cables/medium-voltage-cables/na2xsy/,Product
NAYCWY,1,2,1:32,0,/products/power-cables/low-voltage-cables/naycwy/,Product
NY2Y,1,1,1:24,0,/products/power-cables/low-voltage-cables/ny2y/,Product
NYCWY,1,1,0:18,0,/products/power-cables/low-voltage-cables/nycwy/,Product
"Search: ""na2xsf2x""",1,1,0:36,0,/search/na2xsf2x/,Search
"Search: ""wether it""",1,1,0:03,0,/search/wether+it/,Search
"Search: ""wether its a""",1,1,0:10,0,/search/wether+its+a/,Search
Uncategorized,1,1,0:07,0,/uncategorized/,Category
1 Title Visitors Views View Duration Bounce Rate URL Page Type
2 Home &#8211; English 2643 4037 0:57 52.6996 / Page
3 Home &#8211; Deutsch 1483 2444 0:47 20.2907 /de/start/ Page
4 Team &#8211; Deutsch 944 1352 1:13 11.8547 /de/team/ Page
5 Legal Notice &#8211; Deutsch 521 653 1:05 5.8615 /de/impressum/ Page
6 Team &#8211; English 441 569 1:11 8.0745 /team/ Page
7 Kabelabkürzungen entschlüsselt – der Schlüssel zur richtigen Kabelwahl 397 458 2:57 84.3521 /de/kabelabkuerzungen-entschluesselt-der-schluessel-zur-richtigen-kabelwahl/ Post
8 Contact &#8211; English 318 487 2:01 12.7841 /contact/ Page
9 Contact &#8211; Deutsch 312 424 1:35 11.3372 /de/kontakt/ Page
10 Medium Voltage Cables 309 447 0:39 14.121 /power-cables/medium-voltage-cables/ Category
11 N2XS(FL)2Y 304 397 3:02 59.2375 /de/produkte/stromkabel/hochspannungskabel/n2xsfl2y/ Product
12 Mittelspannungskabel 284 405 0:33 6.25 /de/stromkabel/mittelspannungskabel/ Category
13 Low Voltage Cables 255 348 0:53 3.3898 /power-cables/low-voltage-cables/ Category
14 NA2XS(FL)2Y 235 302 2:59 55.0781 /de/produkte/stromkabel/hochspannungskabel/na2xsfl2y/ Product
15 Blog &#8211; English 229 424 0:37 8.1395 /blog/ Blog
16 NA2XS(F)2Y 213 316 1:47 44.9219 /de/produkte/stromkabel/mittelspannungskabel/na2xsf2y-2/ Product
17 Sicherheit bei Kabeltrommeln: Unfallfrei und effizient arbeiten 206 244 2:24 80.543 /de/sicherheit-bei-kabeltrommeln-unfallfrei-und-effizient-arbeiten/ Post
18 Stromkabel 199 269 0:20 4.5045 /de/stromkabel/ Category
19 Hochspannungskabel 194 245 0:36 4.3478 /de/stromkabel/hochspannungskabel/ Category
20 Niederspannungskabel 184 252 0:34 6.9124 /de/stromkabel/niederspannungskabel/ Category
21 Power Cables 184 231 0:20 3.9409 /power-cables/ Category
22 High Voltage Cables 171 238 0:18 8.8398 /power-cables/high-voltage-cables/ Category
23 Solar Cables 154 213 0:46 7.0175 /solar-cables/ Category
24 Welche Kabel für Windkraft? Unterschiede von Nieder- bis Höchstspannung erklärt 133 146 1:27 57.8571 /de/welche-kabel-fuer-windkraft-unterschiede-von-nieder-bis-hoechstspannung-erklaert/ Post
25 NA2XS(FL)2Y 122 138 0:45 57.6 /de/produkte/stromkabel/mittelspannungskabel/na2xsfl2y-2/ Product
26 Solarkabel 118 134 0:18 4.0984 /de/solarkabel/ Category
27 Copper or aluminum cable? Cost comparison for underground cable and grid connection 108 131 3:02 84.1667 /cost-comparison-copper-vs-aluminum-cables-in-wind-farms-which-is-worthwhile-in-the-long-term/ Post
28 Kupfer oder Aluminiumkabel im Windpark? Kostenvergleich für Erdkabel und Netzanschluss 108 120 4:08 85.0877 /de/kostenvergleich-kupfer-vs-aluminiumkabel-in-windparks-was-lohnt-sich-langfristig/ Post
29 KLZ wächst weiter – neue Stärke im Bereich Financial &amp; Sales 105 127 1:59 9.9099 /de/klz-waechst-weiter-neue-staerke-im-bereich-financial-sales/ Post
30 N2X2Y 105 143 4:01 64.7059 /de/produkte/stromkabel/niederspannungskabel/n2x2y-2/ Product
31 N2XS(F)2Y 104 135 0:53 38.843 /de/produkte/stromkabel/mittelspannungskabel/n2xsf2y-2/ Product
32 Cable drum safety: Ensuring smooth operations and accident-free environments 102 114 3:02 79.2453 /cable-drum-safety-ensuring-smooth-operations-and-accident-free-environments/ Post
33 Which cables for wind power? Differences from low to extra-high voltage explained 98 117 5:29 81.982 /which-cables-for-wind-power-differences-from-low-to-extra-high-voltage-explained-2/ Post
34 H1Z2Z2-K 89 111 2:43 48.9362 /de/produkte/solarkabel/h1z2z2-k/ Product
35 N2XS(FL)2Y 87 110 1:39 58.6957 /de/produkte/stromkabel/mittelspannungskabel/n2xsfl2y-2/ Product
36 Engpass bei NA2XSF2Y? Wir haben das Dreileiter-Mittelspannungskabel 86 119 2:05 33.6842 /de/na2xsf2y-dreileiter-mittelspannungskabel-lieferbar/ Post
37 N2XS(FL)2Y 86 96 2:53 46.6667 /products/power-cables/high-voltage-cables/n2xsfl2y/ Product
38 Shop 81 97 0:26 16.4706 /products/ Shop
39 NYCWY 78 97 1:47 78.8235 /de/produkte/stromkabel/niederspannungskabel/nycwy-2/ Product
40 Cable abbreviations decoded – the key to choosing the right cable 74 85 2:00 84.3373 /cable-abbreviations-decoded-the-key-to-choosing-the-right-cable/ Post
41 NA2XS(FL)2Y 73 86 1:07 53.2468 /products/power-cables/high-voltage-cables/na2xsfl2y-3/ Product
42 H1Z2Z2-K 69 82 3:35 44.5946 /products/solar-cables/h1z2z2-k/ Product
43 KLZ Neuigkeiten 68 75 0:56 8.8235 /de/klz-neuigkeiten/ Category
44 Zukunft sichern mit H1Z2Z2-K: Unser Solarkabel zur Intersolar 2025 66 75 2:18 63.6364 /de/zukunft-sichern-mit-h1z2z2-k-unser-solarkabel-zur-intersolar-2025/ Post
45 N2X2Y 65 75 3:02 54.1667 /products/power-cables/low-voltage-cables/n2x2y/ Product
46 Terms &#8211; English 63 86 0:33 37.3134 /terms/ Page
47 Kabel Technologie 61 71 0:47 3.125 /de/kabel-technologie/ Category
48 Recycling of cable drums: sustainability in wind power projects 60 77 1:30 66.1972 /recycling-of-cable-drums-sustainability-in-wind-power-projects/ Post
49 Legal Notice &#8211; English 59 66 1:15 7.8125 /legal-notice/ Page
50 Terms &#8211; Deutsch 57 73 1:40 16.9231 /de/agbs/ Page
51 Recycling von Kabeltrommeln: Nachhaltigkeit im Windkraftprojekt 54 62 2:47 63.1579 /de/recycling-von-kabeltrommeln-nachhaltigkeit-im-windkraftprojekt/ Post
52 Welcome to the future of KLZ: our new website is live! 54 66 1:14 46.7742 /welcome-to-the-future-of-klz-our-new-website-is-live/ Post
53 N2XSY 48 52 3:03 54.902 /products/power-cables/medium-voltage-cables/n2xsy/ Product
54 N2XY 48 60 0:32 67.7966 /products/power-cables/low-voltage-cables/n2xy/ Product
55 NA2XS2Y 48 60 2:12 54.1667 /products/power-cables/medium-voltage-cables/na2xs2y/ Product
56 Die besten Erdkabel für Windkraft und Solar – jetzt bei uns bestellen 47 54 1:53 41.6667 /de/die-besten-erdkabel-fuer-windkraft-und-solar-jetzt-bei-uns-bestellen/ Post
57 The art of cable logistics: moving the backbone of modern energy networks 47 51 2:27 86 /the-art-of-cable-logistics-moving-the-backbone-of-modern-energy-networks/ Post
58 N2XS(F)2Y 46 60 1:11 22.449 /products/power-cables/medium-voltage-cables/n2xsf2y/ Product
59 N2XY 46 52 1:10 67.3469 /de/produkte/stromkabel/niederspannungskabel/n2xy-2/ Product
60 NA2XY 46 65 3:03 48.2759 /products/power-cables/low-voltage-cables/na2xy/ Product
61 2025-01 45 55 0:19 4.4444 /2025/01/ Date Archive (Month)
62 Cable Logistics 43 48 0:44 15.9091 /cable-logistics/ Category
63 Kabel-Logistik 43 50 0:22 2.2727 /de/kabel-logistik/ Category
64 Shortage of NA2XSF2Y? We have the three-core medium-voltage cable 40 42 1:01 64.2857 /na2xsf2y-three-conductor-medium-voltage-cable-available/ Post
65 Milliarden-Paket für Infrastruktur: Der Kabel-Boom steht bevor 38 43 3:05 38.4615 /de/milliarden-paket-fuer-infrastruktur-der-kabel-boom-steht-bevor/ Post
66 NA2XS(F)2Y 38 55 2:38 35.5556 /products/power-cables/medium-voltage-cables/na2xsf2y/ Product
67 Warum das NA2XS(F)2Y das ideale Kabel für Ihr Energieprojekt ist 38 51 3:11 18.1818 /de/n2xsf2y-mittelspannungskabel-energieprojekt/ Post
68 2025-08 37 37 0:20 2.7027 /2025/08/ Date Archive (Month)
69 Cable Technology 37 41 0:16 8.1081 /cable-technology/ Category
70 KLZ News 33 37 0:47 8.8235 /klz-news/ Category
71 2025-11 32 35 0:11 3.125 /2025/11/ Date Archive (Month)
72 NA2XSY 31 37 2:45 38.7097 /de/produkte/stromkabel/mittelspannungskabel/na2xsy-2/ Product
73 Securing the future with H1Z2Z2-K: Our solar cable for Intersolar 2025 31 35 3:29 41.9355 /securing-the-future-with-h1z2z2-k-our-solar-cable-for-intersolar-2025/ Post
74 NA2XY 30 36 0:27 53.125 /de/produkte/stromkabel/niederspannungskabel/na2xy-2/ Product
75 Windparkbau im Fokus: drei typische Kabelherausforderungen 30 33 1:29 32.2581 /de/windparkbau-im-fokus-drei-typische-kabelherausforderungen/ Post
76 N2XS2Y 29 49 0:30 24.3902 /de/produkte/stromkabel/mittelspannungskabel/n2xs2y-2/ Product
77 NAYCWY 29 31 0:59 70 /de/produkte/stromkabel/niederspannungskabel/naycwy-2/ Product
78 2025-06 28 28 0:45 7.1429 /2025/06/ Date Archive (Month)
79 NA2XS(FL)2Y 28 31 2:34 53.3333 /products/power-cables/medium-voltage-cables/na2xsfl2y/ Product
80 Green energy starts underground &#8211; and with a plan 27 36 1:40 66.6667 /green-energy-starts-underground-and-with-a-plan/ Post
81 N2XSY 27 32 2:22 40.7407 /de/produkte/stromkabel/mittelspannungskabel/n2xsy-2/ Product
82 Privacy Policy &#8211; English 27 29 0:15 14.8148 /privacy-policy/ Page
83 So wählen Sie das richtige Kabel für Ihr nächstes Projekt aus 27 112 5:10 56.0976 /de/so-waehlen-sie-das-richtige-kabel-fuer-ihr-naechstes-projekt-aus/ Post
84 Erneuerbare Energien 26 35 0:22 15.3846 /de/erneuerbare-energien/ Category
85 Focus on wind farm construction: three typical cable challenges 26 32 4:43 77.7778 /focus-on-wind-farm-construction-three-typical-cable-challenges/ Post
86 NAY2Y 26 32 0:37 64.2857 /de/produkte/stromkabel/niederspannungskabel/nay2y-2/ Product
87 N2XS(FL)2Y 25 29 4:54 22.2222 /products/power-cables/medium-voltage-cables/n2xsfl2y-3/ Product
88 NA2X2Y 25 28 7:00 34.6154 /products/power-cables/low-voltage-cables/na2x2y/ Product
89 NYY 25 30 3:05 21.4286 /de/produkte/stromkabel/niederspannungskabel/nyy-2/ Product
90 Privacy Policy &#8211; Deutsch 25 28 0:13 11.5385 /de/datenschutz/ Page
91 Renewable Energy 25 31 0:17 16 /reneweable-energy/ Category
92 KLZ im Adressbuch der Windenergie 2025 24 32 2:39 64.5161 /de/klz-im-adressbuch-der-windenergie-2025/ Post
93 NA2X2Y 23 29 1:35 38.4615 /de/produkte/stromkabel/niederspannungskabel/na2x2y-2/ Product
94 2025-09 22 22 0:08 0 /2025/09/ Date Archive (Month)
95 Die perfekte Kabelanfrage: So sparen Sie sich unnötige Rückfragen 22 28 4:01 11.5385 /de/die-perfekte-kabelanfrage-so-sparen-sie-sich-unnoetige-rueckfragen/ Post
96 Kabeltrommelqualität: Die Grundlage der Kabelzuverlässigkeit 22 25 0:41 17.3913 /de/kabeltrommelqualitaet-die-grundlage-der-kabelzuverlaessigkeit/ Post
97 Why the N2XS(F)2Y is the ideal cable for your energy project 22 22 0:30 77.2727 /why-the-n2xsf2y-is-the-ideal-cable-for-your-energy-project/ Post
98 2025-02 21 23 0:12 0 /2025/02/ Date Archive (Month)
99 2025-10 21 26 1:00 0 /2025/10/ Date Archive (Month)
100 Die Kunst der Kabellogistik: Der Transport des Fundamentes moderner Energienetze 21 22 2:34 14.2857 /de/die-kunst-der-kabellogistik-der-transport-des-fundamentes-moderner-energienetze/ Post
101 H1Z2Z2-K 21 26 2:17 13.6364 /products/solar-cables/h1z2z2-k/ Product
102 NAYY 21 34 1:57 18.1818 /de/produkte/stromkabel/niederspannungskabel/nayy-2/ Product
103 NY2Y 21 26 0:54 31.8182 /de/produkte/stromkabel/niederspannungskabel/ny2y-2/ Product
104 NYY 21 26 0:51 30.4348 /products/power-cables/low-voltage-cables/nyy/ Product
105 The best underground cables for wind power and solar &#8211; order from us now 21 22 1:34 52.381 /the-best-underground-cables-for-wind-power-and-solar-order-from-us-now/ Post
106 Warum die richtigen Kabel der geheime Held der grünen Energie sind 21 22 1:27 36.3636 /de/warum-die-richtigen-kabel-der-geheime-held-der-gruenen-energie-sind/ Post
107 NA2XS2Y 20 24 2:20 10 /de/produkte/stromkabel/mittelspannungskabel/na2xs2y-2/ Product
108 Netzausbau 2025: Warum jede neue Leitung ein Schritt zur Energiewende ist 20 20 1:10 70 /de/netzausbau-2025-warum-jede-neue-leitung-ein-schritt-zur-energiewende-ist/ Post
109 NYCWY 20 23 1:58 55 /products/power-cables/low-voltage-cables/nycwy/ Product
110 404 19 19 - 100 /de/produkte/stromkabel/mittelspannungskabel 404
111 H1Z2Z2-K 19 23 4:37 10 /de/produkte/solarkabel/h1z2z2-k-2/ Product
112 The perfect cable inquiry: How to save yourself unnecessary queries 19 19 0:32 47.3684 /the-perfect-cable-inquiry-how-to-save-yourself-unnecessary-queries/ Post
113 Von smart bis nachhaltig: So sieht die Energiewirtschaft in naher Zukunft aus 19 20 1:24 57.8947 /de/von-smart-bis-nachhaltig-so-sieht-die-energiewirtschaft-in-naher-zukunft-aus/ Post
114 Reicht Windenergie wirklich nicht? Ein Blick hinter die Schlagzeilen 18 19 0:58 27.7778 /de/reicht-windenergie-wirklich-nicht-ein-blick-hinter-die-schlagzeilen/ Post
115 2025-05 17 20 1:06 0 /2025/05/ Date Archive (Month)
116 N2XS2Y 17 20 6:33 52.9412 /products/power-cables/medium-voltage-cables/n2xs2y/ Product
117 Willkommen in der Zukunft von KLZ: Unsere neue Website ist online! 17 19 0:45 11.7647 /de/willkommen-in-der-zukunft-von-klz-unsere-neue-website-ist-online/ Post
118 100% renewable energy? Only with the right cable infrastructure! 16 16 1:55 56.25 /100-renewable-energy-only-with-the-right-cable-infrastructure/ Post
119 NA2XSY 16 19 4:57 22.2222 /products/power-cables/medium-voltage-cables/na2xsy/ Product
120 NAY2Y 16 17 3:33 64.7059 /products/power-cables/low-voltage-cables/nay2y/ Product
121 Was macht ein erstklassiges Kabel aus? Finden Sie es hier heraus! 16 20 0:56 23.5294 /de/was-macht-ein-erstklassiges-kabel-aus-finden-sie-es-hier-heraus/ Post
122 Wie die Kabelbranche Nachhaltigkeit und erneuerbare Energien vorantreibt 16 19 4:12 50 /de/wie-die-kabelbranche-nachhaltigkeit-und-erneuerbare-energien-vorantreibt/ Post
123 2025-03 15 16 0:13 6.25 /2025/03/ Date Archive (Month)
124 2025-04 15 15 0:45 0 /2025/04/ Date Archive (Month)
125 NAYCWY 15 19 3:47 26.6667 /products/power-cables/low-voltage-cables/naycwy/ Product
126 What makes a first-class cable? Find out here! 15 15 0:43 20 /what-makes-a-first-class-cable-find-out-here/ Post
127 NY2Y 14 15 0:13 64.2857 /products/power-cables/low-voltage-cables/ny2y/ Product
128 Why wind farm grid connection cables must withstand extreme loads 14 14 0:31 50 /why-wind-farm-grid-connection-cables-must-withstand-extreme-loads/ Post
129 100 % erneuerbare Energie? Nur mit der richtigen Kabelinfrastruktur! 13 23 1:46 42.8571 /de/100-erneuerbare-energie-nur-mit-der-richtigen-kabelinfrastruktur/ Post
130 Cable drum quality: the foundation of cable reliability 13 15 2:36 23.0769 /cable-drum-quality-the-foundation-of-cable-reliability/ Post
131 Erkenntnisse über die grüne Energiewende: Herausforderungen und Chancen 13 13 0:23 23.0769 /de/erkenntnisse-ueber-die-gruene-energiewende-herausforderungen-und-chancen/ Post
132 How to choose the right cable for your next project 13 16 0:22 20 /how-to-choose-the-right-cable-for-your-next-project/ Post
133 NAYY 13 13 1:17 38.4615 /products/power-cables/low-voltage-cables/nayy/ Product
134 Billion-euro package for infrastructure: The cable boom is coming 12 15 0:01 40 /billion-euro-package-for-infrastructure-the-cable-boom-is-coming/ Post
135 Das müssen Sie über erneuerbare Energien im Jahr 2025 wissen 12 13 0:20 25 /de/das-muessen-sie-ueber-erneuerbare-energien-im-jahr-2025-wissen/ Post
136 Expanding the grid by 2025: Building the foundation for a successful energy transition 12 12 0:04 66.6667 /expanding-the-grid-by-2025-building-the-foundation-for-a-successful-energy-transition/ Post
137 KLZ in the Directory of Wind Energy 2025 12 15 5:34 23.0769 /klz-in-the-directory-of-wind-energy-2025/ Post
138 Home 11 121 2:30 30 / Page
139 This what you need to know about renewable energies in 2025 11 13 0:44 45.4545 /this-what-you-need-to-know-about-renewable-energies-in-2025/ Post
140 2019-09 10 10 0:09 20 /2019/09/ Date Archive (Month)
141 2024-11 10 10 1:32 30 /2024/11/ Date Archive (Month)
142 404 10 20 0:35 0 /terms/): 404
143 How the cable industry is driving sustainability and renewable energies forward 10 11 0:54 36.3636 /how-the-cable-industry-is-driving-sustainability-and-renewable-energies-forward/ Post
144 Klimaneutral bis 2050? Was wir tun müssen, um das Ziel zu erreichen 10 10 0:35 20 /de/klimaneutral-bis-2050-was-wir-tun-muessen-um-das-ziel-zu-erreichen/ Post
145 2024-12 9 9 0:06 22.2222 /2024/12/ Date Archive (Month)
146 404 9 9 - 100 /checkout 404
147 404 8 10 0:08 0 /de/stromkabel/mittelspannungskabel/ 404
148 404 8 13 0:56 0 /de/stromkabel/niederspannungskabel/ 404
149 Eye-opening realities of green energy transformation 8 12 0:34 37.5 /eye-opening-realities-of-green-energy-transformation/ Post
150 How the right cables quietly power the green energy revolution 8 8 0:11 37.5 /how-the-right-cables-quietly-power-the-green-energy-revolution/ Post
151 Grüne Energie beginnt unter der Erde – und zwar mit Plan 7 10 0:34 22.2222 /de/gruene-energie-beginnt-unter-der-erde-und-zwar-mit-plan/ Post
152 Is wind energy really enough? A deeper dive behind the headlines 7 7 3:26 42.8571 /is-wind-energy-really-enough-a-deeper-dive-behind-the-headlines/ Post
153 Warum Windpark-Netzanschlusskabel extremen Belastungen standhalten müssen 7 7 1:33 28.5714 /de/warum-windpark-netzanschlusskabel-extremen-belastungen-standhalten-muessen/ Post
154 From smart to sustainable: this is what the energy industry will look like in the near future 6 7 0:02 42.8571 /from-smart-to-sustainable-this-is-what-the-energy-industry-will-look-like-in-the-near-future/ Post
155 Thanks &#8211; English 6 6 0:13 0 /thanks/ Page
156 404 5 7 0:05 0 /de/stromkabel/hochspannungskabel/ 404
157 404 5 10 0:03 0 /de/stromkabel/ 404
158 Climate neutral by 2050? What we need to do to achieve this goal 5 5 0:06 40 /climate-neutral-by-2050-what-we-need-to-do-to-achieve-this-goal/ Post
159 Thanks &#8211; Deutsch 5 5 0:20 20 /de/danke/ Page
160 Blog &#8211; English 4 11 2:16 0 /blog/ Page
161 3 4 0:13 0 0 -
162 404 3 3 0:09 33.3333 /de/produkte/solarkabel/h1z2z2-k-2/ 404
163 404 3 4 0:03 0 /de/solarkabel/ 404
164 Marisa Archive 3 3 - 100 /author/70dd118b8358b039/ Author Archive
165 404 2 2 0:04 0 /de/solarkabel 404
166 404 2 2 0:02 0 /product-category/power-cables/medium-voltage-cables/ 404
167 404 2 2 - 100 /wp-content/cache/breeze-minification/js/breeze_9805ec35ddc3b51dd052fc959c92b9c608e8c126b294bf6dff0059060fe0ba2075085254f24ff8659260baab9516eefc917b92bfac0673df7ab111c3c541429c.js 404
168 N2XS(F)2Y 2 4 0:15 0 /products/power-cables/medium-voltage-cables/n2xsf2y/ Product
169 N2XS(F)2Y 2 2 0:24 0 /products/power-cables/medium-voltage-cables/n2xsf2y/ Product
170 N2XS(F)2Y 2 3 0:04 0 /de/produkte/stromkabel/mittelspannungskabel/n2xsf2y/ Product
171 404 1 1 - 100 /products/power-cables/high-voltage-cables/n2xfk2y-high-voltage-cables/ 404
172 404 1 2 0:00 0 /power-cables/high-voltage-cables/ 404
173 404 1 2 0:11 0 /de/recycling-von-kabeltrommeln-nachhaltigkeit-im-windkraftprojekt/ 404
174 404 1 1 0:01 0 /de/solarkabel/?orderby=popularity&amp;paged=1 404
175 404 1 1 0:03 0 /de/produkte/solarkabel/h1z2z2-k-2/bil 404
176 404 1 1 - 100 /wp-content/cache/breeze-minification/js/breeze_dcb838df562c4231e788912419a3f72faa5dd65dcdd419b7685048f4b1620f0c7a9e43bc80543ba26f26bac4a084ece9033b401a705fe0a49a1f3d7e192af842.js 404
177 404 1 1 0:01 0 /de/start/%20north%20dataklz%20cables%20north%20data 404
178 404 1 1 0:19 0 /de/stromkabel/mittelspannungskabel/n2xsy/ 404
179 404 1 1 0:14 0 /stromkabel/niederspannungskabel/ 404
180 404 1 2 0:26 0 /de/?p=46951&amp;preview=true 404
181 404 1 2 - 50 /product-category/power-cables/ 404
182 404 1 1 - 100 /products/power-cables/high-voltage-cables/n2xsfl2y/beltrager 404
183 404 1 1 - 100 /de/stromkabel/mittelspannungskabel/?gad_source=2&amp;gclid=Cj0KCQjwmK_CBhCEARIsAMKwcD7ZMki42JRsyIqLhuIsjoD4EMyMuqZ7LTNVE1CQZz_g6OyC8AIYWPQaArMuEALw_wcB 404
184 404 1 1 0:15 0 /wp-content/uploads/2024/02/agbs.pdf 404
185 404 1 2 0:07 0 /power-cables/low-voltage-cables/ 404
186 404 1 2 0:01 0 /power-cables/medium-voltage-cables/ 404
187 404 1 1 - 100 /wp-content/cache/breeze-minification/js/breeze_8bc685a40ea644b02847846593300af35e19f3770486376bb3ef1bbe052f4385eef81507e0500134f2fe29ed003a03e22bfdecee306723fdee84216490592a2b.js 404
188 404 1 1 - 100 /wp-content/uploads/2024/12/Medium-Voltage-Cables-&ndash;-KLZ-Cables-12-30-2024_05_20_PM-477x1024.webp 404
189 404 1 1 - 100 /de/stromkabel/mittelspannungskabel/?gad_source=2&amp;gclid=Cj0KCQjwmK_CBhCEARIsAMKwcD5W7eUP8XTHo4k7XUuTjgMg0jTMJOyr6uNEk5Qz_HJTF1y8YXIz1q0aAm4qEALw_wcB 404
190 404 1 1 14:18 0 /stromkabel/mittelspannungskabel/ 404
191 404 1 1 0:14 0 /de/start/%20north%20data 404
192 404 1 2 0:06 0 /de/?p=45906 404
193 404 1 1 - 100 /de/stromkabel/mittelspannungskabel/?gad_source=2&amp;wbraid=CloKCQjw9anCBhCfARJJAI67itmi53OghAGllVRruDcGaerAvNNjyLaeHscS7YPwfaDa15EOby39EIEP8xG-k11jnu8ulV4kj14DXeHVdUjMVjz2iF-jSBoCvMk 404
194 404 1 1 - 100 /de/stromkabel/mittelspannungskabel/?gad_source=2&amp;wbraid=CloKCQjw9anCBhCfARJJAI67itlMLr_lFFlTEuKOndhXM09OWwK_g5VQ7hvsZiACe06Tjh3mV5TaGtSc-4UZDex8xYI5jTrZFZM4nSaRkpzvELSCCNPNzxoCMTg 404
195 404 1 1 0:06 0 /cost-comparison-copper-vs-aluminum-cables-in-wind-farms-which-is-worthwhile-in-the-long-term/%5D 404
196 404 1 2 0:04 0 /solar-cables 404
197 404 1 3 0:06 0 /products/power-cables/low-voltage-cables/n2x2y/ 404
198 404 1 1 0:03 0 /product-category/solar-cables/ 404
199 404 1 1 - 100 /wp-content/cache/breeze-minification/js/breeze_797f299d7c49ad925d2a31e345811b1bea4e6e862eb72c2ff5da1fd85047a044c31e8c0b67ad3dee86ae3e300dc81f6b3f8c9e324971480a778d1ac464b1b1d7.js 404
200 404 1 1 - 100 /de/produkte/stromkabel/mittelspannungskabel/n2xsf2y-gm/?utm_source=chatgpt.com 404
201 404 1 1 - 100 /eajx%C3%82 404
202 404 1 1 - 100 /de/stromkabel/mittelspannungskabel/?gad_source=2&amp;gclid=Cj0KCQjwmK_CBhCEARIsAMKwcD6UUkHwiUqogoeZoSthCY0zq3meIDmEvjlye2z8I7wp-2zW8r_5cZkaAi6-EALw_wcB 404
203 404 1 2 0:05 0 /de/die-perfekte-kabelanfrage-so-sparen-sie-sich-unnoetige-rueckfragen/ 404
204 404 1 1 0:10 0 /wp-content/uploads/2024/01/agbs.pdf 404
205 N2X2Y 1 2 - 50 /de/produkte/stromkabel/niederspannungskabel/n2x2y/ Product
206 N2X2Y 1 9 0:31 0 /products/power-cables/low-voltage-cables/n2x2y/ Product
207 N2XS(F)2Y 1 1 0:20 0 /de/produkte/stromkabel/mittelspannungskabel/n2xsf2y-2/ Product
208 N2XSY 1 1 0:34 0 /products/power-cables/medium-voltage-cables/n2xsy/ Product
209 N2XY 1 1 1:22 0 /products/power-cables/low-voltage-cables/n2xy/ Product
210 NA2XS(F)2Y 1 1 1:24 0 /products/power-cables/medium-voltage-cables/na2xsf2y/ Product
211 NA2XS(FL)2Y 1 1 0:54 0 /products/power-cables/medium-voltage-cables/na2xsfl2y/ Product
212 NA2XS(FL)2Y 1 1 0:13 0 /de/produkte/stromkabel/mittelspannungskabel/na2xsfl2y/ Product
213 NA2XSY 1 1 0:50 0 /products/power-cables/medium-voltage-cables/na2xsy/ Product
214 NAYCWY 1 2 1:32 0 /products/power-cables/low-voltage-cables/naycwy/ Product
215 NY2Y 1 1 1:24 0 /products/power-cables/low-voltage-cables/ny2y/ Product
216 NYCWY 1 1 0:18 0 /products/power-cables/low-voltage-cables/nycwy/ Product
217 Search: "na2xsf2x" 1 1 0:36 0 /search/na2xsf2x/ Search
218 Search: "wether it" 1 1 0:03 0 /search/wether+it/ Search
219 Search: "wether its a" 1 1 0:10 0 /search/wether+its+a/ Search
220 Uncategorized 1 1 0:07 0 /uncategorized/ Category

View File

@@ -1,93 +1,112 @@
---
title: Terms &#8211; Deutsch
excerpt: >-
[vc_column column_padding=&#8221;no-extra-padding&#8221;
column_padding_tablet=&#8221;inherit&#8221;
column_padding_phone=&#8221;inherit&#8221;
column_padding_position=&#8221;all&#8221;
column_element_direction_desktop=&#8221;default&#8221;
column_element_spacing=&#8221;default&#8221;
desktop_text_alignment=&#8221;default&#8221;
tablet_text_alignment=&#8221;default&#8221;
phone_text_alignment=&#8221;default&#8221;
background_color_opacity=&#8221;1&#8243;
background_hover_color_opacity=&#8221;1&#8243;
column_backdrop_filter=&#8221;none&#8221;
column_shadow=&#8221;none&#8221;&#8230;
title: AGB
excerpt: Liefer- und Zahlungsbedingungen der KLZ Vertriebs GmbH.
featuredImage: null
locale: de
---
# Terms &#8211; Deutsch
<h1 class="p1">Liefer- und Zahlungsbedingungen</h1>
<p class="p1">Stand November 2024</p>
*Stand November 2024*
<h3 class="p1"><span class="s1">1.</span> Allgemeines</h3>
<p class="p2">Diese Liefer- und Zahlungsbedingungen (L&amp;Z) der KLZ Vertriebs GmbH gelten ausschließlich; entgegenstehende oder von unseren Bedingungen abweichende Bedingungen des Kunden erkennen wir nicht an, es sei denn, wir hätten ausdrücklich schriftlich ihrer Geltung zugestimmt. Unsere L&amp;Z gelten auch dann, wenn wir in Kenntnis entgegenstehender oder von unseren L&amp;Z abweichender Bedingungen des Bestellers die Lieferung an diesen vorbehaltlos ausführen. Unsere L&amp;Z gelten nur gegenüber Unternehmern im Sinn von § 310 Abs. 1 BGB sowie juristischen Personen des <span class="s3">öffentlichen </span>Rechts oder öffentlich-rechtliches Sondervermögen.</p>
<p class="p2">Nebenabreden, Vorbehalte, Änderungen, Ergänzungen usw. bedürfen zu ihrer Wirksamkeit unserer schriftlichen Bestätigung.</p>
<p class="p2">Hinweise auf die Geltung gesetzlicher Vorschriften haben nur klarstellende Bedeutung. Auch ohne eine derartige Klarstellung gelten daher die gesetzlichen Vorschriften, soweit sie in diesen L&amp;Z nicht unmittelbar abgeändert oder <span class="s3">ausdrücklich </span>ausgeschlossen werden.</p>
## 1. Allgemeines
<h3 class="p1"><span class="s1">2.</span> Angebote</h3>
<p class="p1">Sofern nicht ausdrücklich als bindend bezeichnet, sind unsere Angebote freibleibend; die Bestellung des <span class="s2">Kunden ist </span>als Angebot gemäß § 145 BGB zu qualifizieren.</p>
Diese Liefer- und Zahlungsbedingungen (L&Z) der KLZ Vertriebs GmbH gelten ausschließlich; entgegenstehende oder von unseren Bedingungen abweichende Bedingungen des Kunden erkennen wir nicht an, es sei denn, wir hätten ausdrücklich schriftlich ihrer Geltung zugestimmt. Unsere L&Z gelten auch dann, wenn wir in Kenntnis entgegenstehender oder von unseren L&Z abweichender Bedingungen des Bestellers die Lieferung an diesen vorbehaltlos ausführen. Unsere L&Z gelten nur gegenüber Unternehmern im Sinn von § 310 Abs. 1 BGB sowie juristischen Personen des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen.
<h3 class="p1"><span class="s1">3.</span> Preise</h3>
<p class="p1">Alle von uns genannten Preise verstehen sich zzgl. der jeweiligen gesetzlichen Mehrwertsteuer vor Metallzuschlag fracht- frei innerhalb der Bundesrepublik Deutschland (Festland), jedoch ohne Abladen. Die Verkaufspreise, soweit sie als Hohlpreis deklariert sind, enthalten keinerlei Metallwerte. Diese werden zusätzlich separat berechnet.</p>
Nebenabreden, Vorbehalte, Änderungen, Ergänzungen usw. bedürfen zu ihrer Wirksamkeit unserer schriftlichen Bestätigung.
<h3 class="p1"><span class="s1">4.</span> Metallnotierung</h3>
<p class="p2">Basis zur Kupferabrechnung ist die Notierung &#8220;LME Copper official price cash offer&#8221;, Durchschnitt des Liefervormonats zuzüglich der dann aktuellen von uns benannten Kupfer-Prämie.</p>
<p class="p2">Basis zur Aluminiumabrechnung ist die Notierung &#8220;LME Aluminium official price cash offer&#8221;, Durchschnitt des Liefervormonats zuzüglich der dann von uns benannten Aluminium-Prämie. USD werden auf Basis des EUR/USD LME-FX-Rate (MTLE) in EUR umgerechnet. Die entsprechenden Notierungen können Sie der Web-Seite www.westmetall.com entnehmen. Die Prämienzuschläge können stark variieren und KLZ behält sich das Recht vor, diese fristgerecht anzupassen, ungeachtet der Angebotslegung.</p>
Hinweise auf die Geltung gesetzlicher Vorschriften haben nur klarstellende Bedeutung. Auch ohne eine derartige Klarstellung gelten daher die gesetzlichen Vorschriften, soweit sie in diesen L&Z nicht unmittelbar abgeändert oder ausdrücklich ausgeschlossen werden.
<h3 class="p1"><span class="s1">5.</span> Metallzahl</h3>
<p class="p2">Die von uns ausgewiesene Metallzahl ist eine rein kaufmännische Berechnungsgröße für den Metallinhalt, die in die Berechnung des Gesamtpreises eines Kabels eingeht. Damit entsprechen wir Ihrem Wunsch eine Vergleichbarkeit in ihrem System auf Hohlpreisbasis zu ermöglichen. Die Metallzahl gibt damit nicht das Gewicht des tatsächlich im Kabel enthaltenen Leitermetalls an. Sie ist ein rein kalkulatorischer Berechnungsfaktor, der jedoch keine unmittelbaren Rückschlüsse auf die im Kabel verwendeten Kupfer- bzw. Aluminiummengen zulässt. Wir weisen ausdrücklich darauf hin, final nur den Vollpreis für Vergleichszwecke heranzuziehen. Soweit Sie es wünschen andere Metallzahlen zu Grunde zu legen, sind wir gerne dazu bereit, das Angebot in den Bestandteilen umzurechnen. Bei jeglicher Änderung bleibt aber der Vollpreis der gleiche Betrag.</p>
## 2. Angebote
<h3 class="p1"><span class="s1">6.</span> Auftragsänderung / Auftragsstorno</h3>
<p class="p2">Nach Auftragsbestätigung werden Änderungen an bestätigten Aufträgen nur nach Prüfung und gesonderter ausdrücklicher Zustimmung durch uns akzeptiert. Wir behalten uns bei allen Auftragsänderungen das Recht vor, einen durch die Änderung entstandenen Mehraufwand, wie z.B. Bearbeitungskosten oder Entsorgungskosten in Rech<span class="s2">nung zu stellen.</span></p>
Sofern nicht ausdrücklich als bindend bezeichnet, sind unsere Angebote freibleibend; die Bestellung des Kunden ist als Angebot gemäß § 145 BGB zu qualifizieren.
<h3 class="p1"><span class="s1">7.</span> Eigentumsvorbehalt</h3>
<p class="p2">Wir behalten uns an den von uns gelieferten Waren nachfolgend: Vorbehaltsware bis zur vollständigen Begleichung aller unserer Forderungen aus den Geschäftsbeziehungen mit dem Besteller, das Eigentum vor. Der Eigentumsvorbehalt bleibt auch dann bestehen, wenn einzelne Forderungen in eine laufende Rechnung aufgenommen <span class="s2">werden (Kontokorrentvorbehalt).</span></p>
## 3. Preise
<h3 class="p1"><span class="s1">8. </span>Zahlungsbedingungen <span class="s3">| </span>Aufrechnung <span class="s3">| </span>Zurückbehaltungsrechte</h3>
<p class="p2">Unsere Rechnungen sind 14 Tage nach Rechnungsdatum ohne jeden Abzug zahlbar. Bei Nichteinhaltung der vereinbarten Zahlungsbedingungen sind wir berechtigt, Zinsen in Höhe von 7 %-Punkten <span class="s4">über dem </span>Basiszinssatz zu berechnen; das Recht zur Geltendmachung weitergehender Schäden, insbesondere <span class="s5">nachgewiesener </span>höherer Zinsen, bleibt hiervon unberührt.</p>
Alle von uns genannten Preise verstehen sich zzgl. der jeweiligen gesetzlichen Mehrwertsteuer vor Metallzuschlag fracht- frei innerhalb der Bundesrepublik Deutschland (Festland), jedoch ohne Abladen. Die Verkaufspreise, soweit sie als Hohlpreis deklariert sind, enthalten keinerlei Metallwerte. Diese werden zusätzlich separat berechnet.
<h3 class="p1"><span class="s1">9.</span> Liefervorbehalt <span class="s3">|</span> Teillieferungen</h3>
<p class="p2">Sämtliche Lieferzusagen unsererseits stehen, sofern nichts anderes ausdrücklich schriftlich vereinbart ist, unter dem Vorbehalt der richtigen und rechtzeitigen Belieferung durch unsere Produzenten. Wir behalten uns jederzeit Teillieferungen vor. Darüber hinaus behalten wir uns branchenübliche Über- oder Unter<span class="s4">lieferungen </span>bis zu 10 % der bestellten Menge vor.</p>
## 4. Metallnotierung
<h3 class="p1"><span class="s1">10.</span> Lieferfristen und Liefertermine</h3>
<p class="p2">Die Lieferfrist wird individuell vereinbart bzw. von uns bei Annahme der Bestellung angegeben. Sofern wir verbindliche Lieferfristen aus Gründen, die wir nicht zu vertreten haben, nicht einhalten können (Nichtverfügbarkeit der Leistung), werden wir den Besteller hierüber unverzüglich informieren und gleichzeitig die voraussichtliche, neue Lieferfrist mitteilen. Ist die Leistung auch innerhalb der neuen Lieferfrist nicht verfügbar, sind wir berechtigt, ganz oder teilweise vom Vertrag zurückzutreten. Eine bereits erbrachte Gegenleistung des Bestellers werden wir unverzüglich erstatten. Nichtverfügbarkeit der Leistung liegt beispielsweise vor bei nicht recht- zeitiger Selbstbelieferung durch unseren Zulieferer, wenn wir ein kongruentes Deckungsgeschäft abgeschlossen haben, bei sonstigen Störungen in der Lieferkette etwa aufgrund höherer Gewalt oder wenn wir im Einzelfall zur <span class="s3">Beschaffung nicht verpflichtet sind.</span></p>
<p class="p2">Der Eintritt unseres Lieferverzugs bestimmt sich nach den gesetzlichen Vorschriften. In jedem Fall ist aber eine <span class="s3">Mahnung durch </span>den Käufer erforderlich.</p>
<p class="p2">Die gesetzlichen Rechte bleiben im Übrigen unberührt.</p>
<p class="p2">Fixgeschäfte setzen die ausdrückliche schriftliche Bezeichnung als solche voraus. Ansonsten ist der Besteller <span class="s4">stets </span>verpflichtet, uns schriftlich eine angemessene Nachfrist zu setzen, wenn von uns zugesagte Termine und/ oder Fristen nicht eingehalten werden. Wird auch die Nachfrist nicht eingehalten, ist der Besteller berechtigt, vom <span class="s3">Vertrag zurückzutreten.</span></p>
<p class="p2">Im Fall höherer Gewalt und/oder sonstiger von uns nicht vorhersehbarer außergewöhnlicher und/oder unverschul<span class="s3">deter </span>Umstände, auch wenn sie bei unserem Vorlieferanten eintreten, verlängert sich eine von uns zugesagte <span class="s4">Lieferfrist bis zur </span>Behebung des vorerwähnten Ereignisses. Ist dieser Zeitpunkt nicht überblickbar, sind sowohl der Besteller als auch wir berechtigt, von dem abgeschlossenen Vertrag zurückzutreten. In diesem Fall sind beiderseits <span class="s3">Schadensersatzansprüche </span>ausgeschlossen. Wir verpflichten uns, bei Bekanntwerden vorerwähnter Umstände <span class="s4">den</span> <span class="s4">Besteller</span> <span class="s4">hiervon</span> <span class="s4">unverzüglich</span> <span class="s4">zu </span>benachrichtigen.</p>
<p class="p2">Ist die Einhaltung eines Termins davon abhängig, dass uns seitens des Bestellers bestimmte Angaben und/oder Pläne, Freigabeerklärungen oder ähnliches erteilt werden, beginnt die Lieferfrist erst von dem Zeitpunkt an zu lau- fen, zu dem uns die vollständigen Angaben des Bestellers schriftlich vorliegen. Wird die Anlieferung auf Wunsch des Bestellers über den vertraglich vorgesehenen Zeitpunkt hinausgeschoben, kann von uns beginnend mit einer Frist von frühestens 10 Werktagen nach Anzeige der Versandbereitschaft dem Besteller ein Lagergeld in Höhe von 2 % des Rechnungsbetrages für jeden angefangenen Monat, maximal jedoch <span class="s3">10 % insgesamt berechnet werden.</span></p>
Basis zur Kupferabrechnung ist die Notierung "LME Copper official price cash offer", Durchschnitt des Liefervormonats zuzüglich der dann aktuellen von uns benannten Kupfer-Prämie.
<h3 class="p1"><span class="s1">11.</span> Abrufaufträge</h3>
<p class="p2">Wird uns ein Abrufauftrag erteilt und werden über die Abruftermine keine gesonderten schriftlichen Vereinbarungen getroffen, ist der Besteller verpflichtet, uns die einzelnen Abruftermine so mitzuteilen, dass zwischen Eingang der Abrufmitteilung bei uns und Auslieferung mindestens 14 Werktage und die letzte Auslieferung spätestens 90 <span class="s2">Tage nach unserer Auftragsbestätigung </span>liegt.</p>
Basis zur Aluminiumabrechnung ist die Notierung "LME Aluminium official price cash offer", Durchschnitt des Liefervormonats zuzüglich der dann von uns benannten Aluminium-Prämie. USD werden auf Basis des EUR/USD LME-FX-Rate (MTLE) in EUR umgerechnet. Die entsprechenden Notierungen können Sie der Web-Seite [www.westmetall.com](https://www.westmetall.com) entnehmen. Die Prämienzuschläge können stark variieren und KLZ behält sich das Recht vor, diese fristgerecht anzupassen, ungeachtet der Angebotslegung.
<h3 class="p1"><span class="s1">12.</span> Maß- und Gewichtsangaben</h3>
<p class="p2">Alle Angaben über Durchmesser, Gewicht, technische Gestaltung, Herstellung und Umfang der von uns zu liefernden <span class="s3">Ware stehen </span>unter dem Vorbehalt der Abweichung innerhalb der handelsüblichen zulässigen Toleranzen. Darüber hinaus behalten wir uns Änderungen, die einer technischen Verbesserung dienen, jederzeit vor. Farbabweichungen und/oder Abweichungen in der äußeren Beschaffenheit der von uns zu liefernden Ware, die jedoch deren Qualität und technische Wirksamkeit unbeeinflusst lässt, begründen keine Mängelhaftungsansprüche des Bestellers.</p>
## 5. Metallzahl
<h3 class="p1"><span class="s1">13.</span> Gefahrübergang und -tragung</h3>
<p class="p2">Die Lieferung erfolgt DAP frei Bestimmungsort Deutschland, wo auch der Erfüllungsort für die Lieferung und eine etwaige Nacherfüllung ist.</p>
<p class="p2">Wird die bestellte Ware von uns versandbereit gestellt und/oder verzögert sich die Versendung und/oder der Abruf aus Gründen, die vom Besteller zu vertreten sind, sind wir berechtigt, Ersatz des hieraus entstehenden Schadens einschließlich Mehraufwendungen zu verlangen. Hierfür berechnen wir eine pauschale Entschädigung i.H.v 2% des Rechnungsbetrages für jeden angefangenen Monat, maximal jedoch 10 % insgesamt beginnend mit der Lieferfrist bzw. mangels einer Lieferfrist mit der Mitteilung der Versandbereitschaft der Ware.</p>
<p class="p2">Der Nachweis eines höheren Schadens und unsere gesetzlichen Ansprüche (insbesondere Ersatz von Mehraufwendungen, angemessene Entschädigung, Kündigung) bleiben unberührt; die Pauschale ist aber auf weitergehende Geldansprüche anzurechnen. Dem Besteller bleibt der Nachweis gestattet, dass uns überhaupt kein oder nur ein <span class="s3">wesentlich geringerer Schaden </span>als vorstehende Pauschale entstanden ist. Rücksendungen an uns, die nicht vorher von uns schriftlich bestätigt worden sind, erfolgen auf alleinige Gefahr des <span class="s4">Bestellers.</span></p>
Die von uns ausgewiesene Metallzahl ist eine rein kaufmännische Berechnungsgröße für den Metallinhalt, die in die Berechnung des Gesamtpreises eines Kabels eingeht. Damit entsprechen wir Ihrem Wunsch eine Vergleichbarkeit in ihrem System auf Hohlpreisbasis zu ermöglichen. Die Metallzahl gibt damit nicht das Gewicht des tatsächlich im Kabel enthaltenen Leitermetalls an. Sie ist ein rein kalkulatorischer Berechnungsfaktor, der jedoch keine unmittelbaren Rückschlüsse auf die im Kabel verwendeten Kupfer- bzw. Aluminiummengen zulässt. Wir weisen ausdrücklich darauf hin, final nur den Vollpreis für Vergleichszwecke heranzuziehen. Soweit Sie es wünschen andere Metallzahlen zu Grunde zu legen, sind wir gerne dazu bereit, das Angebot in den Bestandteilen umzurechnen. Bei jeglicher Änderung bleibt aber der Vollpreis der gleiche Betrag.
<h3 class="p1"><span class="s1">14.</span> Mängelhaftung</h3>
<p class="p2">Wir haften nur dann für die Einhaltung objektiver Anforderungen an der Ware, wenn und soweit zwischen dem Besteller und uns keine Beschaffenheitsvereinbarung getroffen wurde. Die einzuhaltenden subjektiven Anforderungen gehen den einzuhaltenden objektiven Anforderungen vor. Im Zweifel ergeben sich die vereinbarten Anforderungen <span class="s3">an die Ware aus dem von uns </span>bereitgestellten Datenblatt. Einzelne, nicht immer auszuschließende marginale Abweichungen, dürfen durch Reparaturen, wie zum Beispiel Mantelmanschetten nachgebessert werden.</p>
<p class="p2">Jedwede Mängelhaftungsansprüche des Bestellers setzen voraus, dass dieser die ihm übersandte Ware unverzüglich, d. h. in der Regel sofort bei Anlieferung (noch in Anwesenheit des Transporteurs) auf ihre ordnungsgemäße Beschaffenheit hin überprüft und uns zu verzeichnende sichtbare Mängel unmittelbar nach Erhalt der Ware und verdeckte Mängel unmittelbar nach deren Feststellung schriftlich mitteilt. Soweit ein rechtzeitig gerügter, nicht nur unerheblicher Mangel der Kaufsache vorliegt, sind wir nach unserer Wahl zur Mangelbeseitigung oder zur Ersatz<span class="s3">lieferung (Nacherfüllung) berechtigt.</span></p>
<p class="p2">Wir übernehmen im Rahmen der Nacherfüllung in keinem Fall Ein- oder Ausbaukosten, wenn und soweit die Mangelhaftigkeit der Ware zum Zeitpunkt des Einbaus dem Besteller bekannt oder grob fahrlässig unbekannt geblieben <span class="s4">ist.</span></p>
<p class="p2">Sind wir zur Mangelbeseitigung/Ersatzlieferung nicht bereit oder nicht in der Lage oder verzögert sich diese über angemessene Fristen hinaus aus Gründen, die wir zu vertreten haben, oder schlägt sie in sonstiger Weise fehl, so ist der Besteller nach seiner Wahl berechtigt, vom Vertrag zurückzutreten oder eine entsprechende Minderung des <span class="s3">Kaufpreises zu verlangen.</span></p>
<p class="p2">Weitergehende Ansprüche des Bestellers, gleich aus welchem Rechtsgrund, sind nach näherer Maßgabe der Regelungen in nachstehender Ziffer 15 ausgeschlossen bzw. beschränkt.</p>
<p class="p1">Die Verjährungsfristen für Mängelhaftungsansprüche beträgt 24 Monate ab Übergabe der Ware.</p>
<p class="p1">Sollte es bei einer Mängelrüge zu unterschiedlichen Meinungen bezüglich des Kabelschaden kommen, gilt hier im Zweifelsfall nur die Expertise des VDE-Instituts selbst. Andere, auch akkreditierte Testlabore, akzeptieren wir nicht. Wir weisen ausdrücklich daraufhin, dass beim Verlegen des Kabels in den Graben oder in Rohren, bzw. in Bauwerke eine ständige Sichtkontrolle durch den Kabelverleger vorzunehmen ist, ob Auffälligkeiten zu vermerken sind. Eine spätere Reklamation, die fahrlässiges Verhalten vermuten lässt, schränkt sich damit ein.</p>
## 6. Auftragsänderung / Auftragsstorno
<h3 class="p1"><span class="s1">15.</span> Schadenersatz <span class="s3">| </span>Gesamthaftung</h3>
<p class="p2">Wir haften unbeschränkt nur für Vorsatz und grobe Fahrlässigkeit sowie für Schäden aus einer Verletzung von Leben, Körper oder Gesundheit, die auf mindestens fahrlässiger Pflichtverletzung unsererseits oder unserer gesetzlichen Vertreter oder Erfüllungsgehilfen beruhen; ebenso haften wir unbeschränkt im Fall von uns übernommenen bzw. abgegebenen Garantien und Zusicherungen, sofern ein davon umfasster Mangel unsere Haftung auslöst sowie im Fall einer Haftung nach dem Produkthaftungsgesetz oder sonstigen Gefährdungshaftungstatbeständen. Im Fall sonstiger schuldhafter Verletzung wesentlicher Vertragspflichten („Kardinalpflichten“) ist unsere verbleibende Haftung auf den vertragstypischen vorhersehbaren Schaden beschränkt. Mangelfolgeschäden sowie entgangener Gewinn schließen wir grundsätzlich aus.</p>
Nach Auftragsbestätigung werden Änderungen an bestätigten Aufträgen nur nach Prüfung und gesonderter ausdrücklicher Zustimmung durch uns akzeptiert. Wir behalten uns bei allen Auftragsänderungen das Recht vor, einen durch die Änderung entstandenen Mehraufwand, wie z.B. Bearbeitungskosten oder Entsorgungskosten in Rechnung zu stellen.
<h3 class="p1"><span class="s1">16.</span> Kabeltrommeln</h3>
<p class="p2">Unsere Kabel werden auf stabilen Vollholztrommeln geliefert. Auf Wunsch vermitteln wir Ihnen Partner, die diese Trommeln gegen eine Gebühr abholen.</p>
## 7. Eigentumsvorbehalt
<h3 class="p1"><span class="s1">17.</span> Sonstiges</h3>
<p class="p2">Es gilt ausschließlich das Recht der Bundesrepublik Deutschland unter Ausschluss des UN-Kaufrechts (CISG). Gerichtsstand ist nach unserer Wahl Stuttgart, der Erfüllungsort der Lieferverpflichtung oder das für den Sitz des Bestellers zuständige Gericht, sofern der Besteller Kaufmann, juristische Person des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen ist oder keinen allgemeinen Gerichtsstand im Inland hat.</p>
<p class="p2">Mit der Veröffentlichung der vorliegenden L&amp;Z im Internet werden alle von uns früher verwendeten Bedingungen gegenstandslos.</p>
Wir behalten uns an den von uns gelieferten Waren nachfolgend: Vorbehaltsware bis zur vollständigen Begleichung aller unserer Forderungen aus den Geschäftsbeziehungen mit dem Besteller, das Eigentum vor. Der Eigentumsvorbehalt bleibt auch dann bestehen, wenn einzelne Forderungen in eine laufende Rechnung aufgenommen werden (Kontokorrentvorbehalt).
<p class="p1"><a href="/uploads/2025/01/agbs.pdf">Download als PDF</a></p>
## 8. Zahlungsbedingungen | Aufrechnung | Zurückbehaltungsrechte
Unsere Rechnungen sind 14 Tage nach Rechnungsdatum ohne jeden Abzug zahlbar. Bei Nichteinhaltung der vereinbarten Zahlungsbedingungen sind wir berechtigt, Zinsen in Höhe von 7 %-Punkten über dem Basiszinssatz zu berechnen; das Recht zur Geltendmachung weitergehender Schäden, insbesondere nachgewiesener höherer Zinsen, bleibt hiervon unberührt.
## 9. Liefervorbehalt | Teillieferungen
Sämtliche Lieferzusagen unsererseits stehen, sofern nichts anderes ausdrücklich schriftlich vereinbart ist, unter dem Vorbehalt der richtigen und rechtzeitigen Belieferung durch unsere Produzenten. Wir behalten uns jederzeit Teillieferungen vor. Darüber hinaus behalten wir uns branchenübliche Über- oder Unterlieferungen bis zu 10 % der bestellten Menge vor.
## 10. Lieferfristen und Liefertermine
Die Lieferfrist wird individuell vereinbart bzw. von uns bei Annahme der Bestellung angegeben. Sofern wir verbindliche Lieferfristen aus Gründen, die wir nicht zu vertreten haben, nicht einhalten können (Nichtverfügbarkeit der Leistung), werden wir den Besteller hierüber unverzüglich informieren und gleichzeitig die voraussichtliche, neue Lieferfrist mitteilen. Ist die Leistung auch innerhalb der neuen Lieferfrist nicht verfügbar, sind wir berechtigt, ganz oder teilweise vom Vertrag zurückzutreten. Eine bereits erbrachte Gegenleistung des Bestellers werden wir unverzüglich erstatten. Nichtverfügbarkeit der Leistung liegt beispielsweise vor bei nicht rechtzeitiger Selbstbelieferung durch unseren Zulieferer, wenn wir ein kongruentes Deckungsgeschäft abgeschlossen haben, bei sonstigen Störungen in der Lieferkette etwa aufgrund höherer Gewalt oder wenn wir im Einzelfall zur Beschaffung nicht verpflichtet sind.
Der Eintritt unseres Lieferverzugs bestimmt sich nach den gesetzlichen Vorschriften. In jedem Fall ist aber eine Mahnung durch den Käufer erforderlich.
Die gesetzlichen Rechte bleiben im Übrigen unberührt.
Fixgeschäfte setzen die ausdrückliche schriftliche Bezeichnung als solche voraus. Ansonsten ist der Besteller stets verpflichtet, uns schriftlich eine angemessene Nachfrist zu setzen, wenn von uns zugesagte Termine und/ oder Fristen nicht einhalten werden. Wird auch die Nachfrist nicht eingehalten, ist der Besteller berechtigt, vom Vertrag zurückzutreten.
Im Fall höherer Gewalt und/oder sonstiger von uns nicht vorhersehbarer außergewöhnlicher und/oder unverschuldeter Umstände, auch wenn sie bei unserem Vorlieferanten eintreten, verlängert sich eine von uns zugesagte Lieferfrist bis zur Behebung des vorerwähnten Ereignisses. Ist dieser Zeitpunkt nicht überblickbar, sind sowohl der Besteller als auch wir berechtigt, von dem abgeschlossenen Vertrag zurückzutreten. In diesem Fall sind beiderseits Schadensersatzansprüche ausgeschlossen. Wir verpflichten uns, bei Bekanntwerden vorerwähnter Umstände den Besteller hiervon unverzüglich zu benachrichtigen.
Ist die Einhaltung eines Termins davon abhängig, dass uns seitens des Bestellers bestimmte Angaben und/oder Pläne, Freigabeerklärungen oder ähnliches erteilt werden, beginnt die Lieferfrist erst von dem Zeitpunkt an zu laufen, zu dem uns die vollständigen Angaben des Bestellers schriftlich vorliegen. Wird die Anlieferung auf Wunsch des Bestellers über den vertraglich vorgesehenen Zeitpunkt hinausgeschoben, kann von uns beginnend mit einer Frist von frühestens 10 Werktagen nach Anzeige der Versandbereitschaft dem Besteller ein Lagergeld in Höhe von 2 % des Rechnungsbetrages für jeden angefangenen Monat, maximal jedoch 10 % insgesamt berechnet werden.
## 11. Abrufaufträge
Wird uns ein Abrufauftrag erteilt und werden über die Abruftermine keine gesonderten schriftlichen Vereinbarungen getroffen, ist der Besteller verpflichtet, uns die einzelnen Abruftermine so mitzuteilen, dass zwischen Eingang der Abrufmitteilung bei uns und Auslieferung mindestens 14 Werktage und die letzte Auslieferung spätestens 90 Tage nach unserer Auftragsbestätigung liegt.
## 12. Maß- und Gewichtsangaben
Alle Angaben über Durchmesser, Gewicht, technische Gestaltung, Herstellung und Umfang der von uns zu liefernden Ware stehen unter dem Vorbehalt der Abweichung innerhalb der handelsüblichen zulässigen Toleranzen. Darüber hinaus behalten wir uns Änderungen, die einer technischen Verbesserung dienen, jederzeit vor. Farbabweichungen und/oder Abweichungen in der äußeren Beschaffenheit der von uns zu liefernden Ware, die jedoch deren Qualität und technische Wirksamkeit unbeeinflusst lassen, begründen keine Mängelhaftungsansprüche des Bestellers.
## 13. Gefahrübergang und -tragung
Die Lieferung erfolgt DAP frei Bestimmungsort Deutschland, wo auch der Erfüllungsort für die Lieferung und eine etwaige Nacherfüllung ist.
Wird die bestellte Ware von uns versandbereit gestellt und/oder verzögert sich die Versendung und/oder der Abruf aus Gründen, die vom Besteller zu vertreten sind, sind wir berechtigt, Ersatz des hieraus entstehenden Schadens einschließlich Mehraufwendungen zu verlangen. Hierfür berechnen wir eine pauschale Entschädigung i.H.v 2% des Rechnungsbetrages für jeden angefangenen Monat, maximal jedoch 10 % insgesamt beginnend mit der Lieferfrist bzw. mangels einer Lieferfrist mit der Mitteilung der Versandbereitschaft der Ware.
Der Nachweis eines höheren Schadens und unsere gesetzlichen Ansprüche (insbesondere Ersatz von Mehraufwendungen, angemessene Entschädigung, Kündigung) bleiben unberührt; die Pauschale ist aber auf weitergehende Geldansprüche anzurechnen. Dem Besteller bleibt der Nachweis gestattet, dass uns überhaupt kein oder nur ein wesentlich geringerer Schaden als vorstehende Pauschale entstanden ist. Rücksendungen an uns, die nicht vorher von uns schriftlich bestätigt worden sind, erfolgen auf alleinige Gefahr des Bestellers.
## 14. Mängelhaftung
Wir haften nur dann für die Einhaltung objektiver Anforderungen an der Ware, wenn und soweit zwischen dem Besteller und uns keine Beschaffenheitsvereinbarung getroffen wurde. Die einzuhaltenden subjektiven Anforderungen gehen den einzuhaltenden objektiven Anforderungen vor. Im Zweifel ergeben sich die vereinbarten Anforderungen an die Ware aus dem von uns bereitgestellten Datenblatt. Einzelne, nicht immer auszuschließende marginale Abweichungen, dürfen durch Reparaturen, wie zum Beispiel Mantelmanschetten nachgebessert werden.
Jedwede Mängelhaftungsansprüche des Bestellers setzen voraus, dass dieser die ihm übersandte Ware unverzüglich, d. h. in der Regel sofort bei Anlieferung (noch in Anwesenheit des Transporteurs) auf ihre ordnungsgemäße Beschaffenheit hin überprüft und uns zu verzeichnende sichtbare Mängel unmittelbar nach Erhalt der Ware und verdeckte Mängel unmittelbar nach deren Feststellung schriftlich mitteilt. Soweit ein rechtzeitig gerügter, nicht nur unerheblicher Mangel der Kaufsache vorliegt, sind wir nach unserer Wahl zur Mangelbeseitigung oder zur Ersatzlieferung (Nacherfüllung) berechtigt.
Wir übernehmen im Rahmen der Nacherfüllung in keinem Fall Ein- oder Ausbaukosten, wenn und soweit die Mangelhaftigkeit der Ware zum Zeitpunkt des Einbaus dem Besteller bekannt oder grob fahrlässig unbekannt geblieben ist.
Sind wir zur Mangelbeseitigung/Ersatzlieferung nicht bereit oder nicht in der Lage oder verzögert sich diese über angemessene Fristen hinaus aus Gründen, die wir zu vertreten haben, oder schlägt sie in sonstiger Weise fehl, so ist der Besteller nach seiner Wahl berechtigt, vom Vertrag zurückzutreten oder eine entsprechende Minderung des Kaufpreises zu verlangen.
Weitergehende Ansprüche des Bestellers, gleich aus welchem Rechtsgrund, sind nach näherer Maßgabe der Regelungen in nachstehender Ziffer 15 ausgeschlossen bzw. beschränkt.
Die Verjährungsfristen für Mängelhaftungsansprüche beträgt 24 Monate ab Übergabe der Ware.
Sollte es bei einer Mängelrüge zu unterschiedlichen Meinungen bezüglich des Kabelschaden kommen, gilt hier im Zweifelsfall nur die Expertise des VDE-Instituts selbst. Andere, auch akkreditierte Testlabore, akzeptieren wir nicht. Wir weisen ausdrücklich daraufhin, dass beim Verlegen des Kabels in den Graben oder in Rohren, bzw. in Bauwerke eine ständige Sichtkontrolle durch den Kabelverleger vorzunehmen ist, ob Auffälligkeiten zu vermerken sind. Eine spätere Reklamation, die fahrlässiges Verhalten vermuten lässt, schränkt sich damit ein.
## 15. Schadenersatz | Gesamthaftung
Wir haften unbeschränkt nur für Vorsatz und grobe Fahrlässigkeit sowie für Schäden aus einer Verletzung von Leben, Körper oder Gesundheit, die auf mindestens fahrlässiger Pflichtverletzung unsererseits oder unserer gesetzlichen Vertreter oder Erfüllungsgehilfen beruhen; ebenso haften wir unbeschränkt im Fall von uns übernommenen bzw. abgegebenen Garantien und Zusicherungen, sofern ein davon umfasster Mangel unsere Haftung auslöst sowie im Fall einer Haftung nach dem Produkthaftungsgesetz oder sonstigen Gefährdungshaftungstatbeständen. Im Fall sonstiger schuldhafter Verletzung wesentlicher Vertragspflichten („Kardinalpflichten“) ist unsere verbleibende Haftung auf den vertragstypischen vorhersehbaren Schaden beschränkt. Mangelfolgeschäden sowie entgangener Gewinn schließen wir grundsätzlich aus.
## 16. Kabeltrommeln
Unsere Kabel werden auf stabilen Vollholztrommeln geliefert. Auf Wunsch vermitteln wir Ihnen Partner, die diese Trommeln gegen eine Gebühr abholen.
## 17. Sonstiges
Es gilt ausschließlich das Recht der Bundesrepublik Deutschland unter Ausschluss des UN-Kaufrechts (CISG). Gerichtsstand ist nach unserer Wahl Stuttgart, der Erfüllungsort der Lieferverpflichtung oder das für den Sitz des Bestellers zuständige Gericht, sofern der Besteller Kaufmann, juristische Person des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen ist oder keinen allgemeinen Gerichtsstand im Inland hat.
Mit der Veröffentlichung der vorliegenden L&Z im Internet werden alle von uns früher verwendeten Bedingungen gegenstandslos.
[Download als PDF](/AGB-KLZ-1-2026.pdf)

View File

@@ -1,53 +1,59 @@
---
title: Privacy Policy &#8211; Deutsch
excerpt: >-
[vc_column column_padding=&#8221;no-extra-padding&#8221;
column_padding_tablet=&#8221;inherit&#8221;
column_padding_phone=&#8221;inherit&#8221;
column_padding_position=&#8221;all&#8221;
column_element_direction_desktop=&#8221;default&#8221;
column_element_spacing=&#8221;default&#8221;
desktop_text_alignment=&#8221;default&#8221;
tablet_text_alignment=&#8221;default&#8221;
phone_text_alignment=&#8221;default&#8221;
background_color_opacity=&#8221;1&#8243;
background_hover_color_opacity=&#8221;1&#8243;
column_backdrop_filter=&#8221;none&#8221;
column_shadow=&#8221;none&#8221;&#8230;
title: Datenschutzerklärung
excerpt: Informationen zum Umgang mit Ihren persönlichen Daten bei der KLZ Vertriebs GmbH.
featuredImage: null
locale: de
---
# Privacy Policy &#8211; Deutsch
<h1>Datenschutzerklärung</h1>
<h3>1. Datenschutz auf einen Blick</h3>
<p><strong>Allgemeine Hinweise</strong><br />
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie unsere Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unten aufgeführten Datenschutzerklärung.</p>
<hr />
<h3>2. Allgemeine Hinweise und Pflichtinformationen</h3>
<p><strong>Datenschutz</strong><br />
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.</p>
<p>Wenn Sie diese Website nutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Diese Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck dies geschieht.</p>
<p>Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.</p>
<hr />
<p><strong>Widerruf Ihrer Einwilligung zur Datenverarbeitung</strong><br />
Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.</p>
<hr />
<p><strong>Beschwerderecht bei der zuständigen Aufsichtsbehörde</strong><br />
Im Falle von Verstößen gegen das Datenschutzrecht steht dem Betroffenen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu. Die zuständige Aufsichtsbehörde für Datenschutzfragen ist der Datenschutzbeauftragte des Bundeslandes, in dem unser Unternehmen seinen Sitz hat. Eine Liste der Datenschutzbeauftragten sowie deren Kontaktdaten können Sie folgendem Link entnehmen:<br />
<a href="https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html" target="_new" rel="noopener noreferrer nofollow" target="_blank">https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html</a>.</p>
<hr />
<p><strong>Recht auf Datenübertragbarkeit</strong><br />
Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder zur Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.</p>
<hr />
<p><strong>Auskunft, Sperrung, Löschung</strong><br />
Im Rahmen der geltenden gesetzlichen Bestimmungen haben Sie jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger sowie den Zweck der Datenverarbeitung. Gegebenenfalls haben Sie ein Recht auf Berichtigung, Sperrung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden.</p>
<hr />
<p><strong>Widerspruch gegen Werbe-E-Mails</strong><br />
Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-E-Mails, vor.</p>
<hr />
<h3>3. Datenerfassung in unserem Unternehmen</h3>
<p><strong>Datenübermittlung bei Vertragsschluss für Dienstleistungen und digitale Inhalte</strong><br />
Wir übermitteln personenbezogene Daten an Dritte nur dann, wenn dies im Rahmen der Vertragsabwicklung notwendig ist, z. B. an das mit der Zahlungsabwicklung beauftragte Kreditinstitut.</p>
<p>Eine weitergehende Übermittlung der Daten erfolgt nicht bzw. nur dann, wenn Sie der Übermittlung ausdrücklich zugestimmt haben. Eine Weitergabe Ihrer Daten an Dritte ohne ausdrückliche Einwilligung, etwa zu Werbezwecken, erfolgt nicht.</p>
<p>Rechtsgrundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet.
## 1. Datenschutz auf einen Blick
### Allgemeine Hinweise
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie unsere Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unten aufgeführten Datenschutzerklärung.
---
## 2. Allgemeine Hinweise und Pflichtinformationen
### Datenschutz
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.
Wenn Sie diese Website nutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Diese Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck dies geschieht.
Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.
---
### Widerruf Ihrer Einwilligung zur Datenverarbeitung
Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.
---
### Beschwerderecht bei der zuständigen Aufsichtsbehörde
Im Falle von Verstößen gegen das Datenschutzrecht steht dem Betroffenen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu. Die zuständige Aufsichtsbehörde für Datenschutzfragen ist der Datenschutzbeauftragte des Bundeslandes, in dem unser Unternehmen seinen Sitz hat. Eine Liste der Datenschutzbeauftragten sowie deren Kontaktdaten können Sie folgendem Link entnehmen:
[https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html](https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html).
---
### Recht auf Datenübertragbarkeit
Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder zur Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.
---
### Auskunft, Sperrung, Löschung
Im Rahmen der geltenden gesetzlichen Bestimmungen haben Sie jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger sowie den Zweck der Datenverarbeitung. Gegebenenfalls haben Sie ein Recht auf Berichtigung, Sperrung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden.
---
### Widerspruch gegen Werbe-E-Mails
Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-E-Mails, vor.
---
## 3. Datenerfassung in unserem Unternehmen
### Datenübermittlung bei Vertragsschluss für Dienstleistungen und digitale Inhalte
Wir übermitteln personenbezogene Daten an Dritte nur dann, wenn dies im Rahmen der Vertragsabwicklung notwendig ist, z. B. an das mit der Zahlungsabwicklung beauftragte Kreditinstitut.
Eine weitergehende Übermittlung der Daten erfolgt nicht bzw. nur dann, wenn Sie der Übermittlung ausdrücklich zugestimmt haben. Eine Weitergabe Ihrer Daten an Dritte ohne ausdrückliche Einwilligung, etwa zu Werbezwecken, erfolgt nicht.
Rechtsgrundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. b DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet.

View File

@@ -1,34 +1,26 @@
---
title: Legal Notice &#8211; Deutsch
excerpt: >-
[vc_column column_padding=&#8221;no-extra-padding&#8221;
column_padding_tablet=&#8221;inherit&#8221;
column_padding_phone=&#8221;inherit&#8221;
column_padding_position=&#8221;all&#8221;
column_element_direction_desktop=&#8221;default&#8221;
column_element_spacing=&#8221;default&#8221;
desktop_text_alignment=&#8221;default&#8221;
tablet_text_alignment=&#8221;default&#8221;
phone_text_alignment=&#8221;default&#8221;
background_color_opacity=&#8221;1&#8243;
background_hover_color_opacity=&#8221;1&#8243;
column_backdrop_filter=&#8221;none&#8221;
column_shadow=&#8221;none&#8221;&#8230;
title: Impressum
excerpt: Rechtliche Informationen und Kontaktdaten der KLZ Vertriebs GmbH.
featuredImage: null
locale: de
---
# Legal Notice &#8211; Deutsch
<h1>Impressum</h1>
<p><strong>Verantwortlich für den Inhalt:</strong><br />
Michael Bodemer</p>
<p><strong>KLZ Vertriebs GmbH</strong><br />
Raiffeisenstraße 22<br />
73630 Remshalden</p>
<p><a rel="noopener">info@klz-cables.com</a><br />
<a href="http://www.klz-cables.com" target="_new" rel="noopener noreferrer nofollow" target="_blank">www.klz-cables.com</a></p>
<p>Amtsgericht Stuttgart<br />
HRB-Nr. 798037<br />
Gerichtsstand: Stuttgart</p>
<p><strong>Urheberrecht:</strong><br />
## Verantwortlich für den Inhalt
Michael Bodemer
**KLZ Vertriebs GmbH**
Raiffeisenstraße 22
73630 Remshalden
Deutschland
## Kontakt
E-Mail: [info@klz-cables.com](mailto:info@klz-cables.com)
Web: [www.klz-cables.com](https://www.klz-cables.com)
## Registereintrag
Amtsgericht Stuttgart
HRB-Nr. 798037
Gerichtsstand: Stuttgart
## Urheberrecht
Alle auf dieser Website veröffentlichten Texte, Bilder und sonstigen Informationen unterliegen dem Urheberrecht, sofern nicht anders angegeben. Jegliche Vervielfältigung, Verbreitung, Speicherung, Übermittlung, Nachbildung oder Weitergabe der Inhalte ist ohne vorherige schriftliche Genehmigung ausdrücklich untersagt. Für weitere Informationen wenden Sie sich bitte an die oben genannte Adresse.

View File

@@ -96,6 +96,12 @@ locale: de
</div>
</div>
<div class="mt-4">
<a href="/vcf/michael-bodemer" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 no-underline">
vCard Michael Bodemer herunterladen
</a>
</div>
<h3>Verbindungen, die Geschichte schreiben</h3>
<p>Bei KLZ vereinen wir Tradition und Innovation zu zuverlässigen Energielösungen. Unsere Wurzeln reichen tief in die Geschichte der Kabeltechnologie zurück mit jeder Menge praktischer Erfahrung und einem Blick für zukunftsweisende Entwicklungen.</p>
<p>In jedem Projekt steckt nicht nur technisches Know-how, sondern auch das Bewusstsein für das Handwerk, das die Welt seit Generationen verbindet. Historische Illustrationen aus den frühen Tagen der Energiebranche erinnern uns daran, wie weit wir gekommen sind und dass echte Exzellenz immer mit Sorgfalt beginnt.
@@ -129,4 +135,10 @@ locale: de
</div>
</div>
<div class="mt-4">
<a href="/vcf/klaus-mintel" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 no-underline">
vCard Klaus Mintel herunterladen
</a>
</div>
<h2>Unser Manifest</h2>

View File

@@ -1,34 +1,26 @@
---
title: Legal Notice &#8211; English
excerpt: >-
[vc_column column_padding=&#8221;no-extra-padding&#8221;
column_padding_tablet=&#8221;inherit&#8221;
column_padding_phone=&#8221;inherit&#8221;
column_padding_position=&#8221;all&#8221;
column_element_direction_desktop=&#8221;default&#8221;
column_element_spacing=&#8221;default&#8221;
desktop_text_alignment=&#8221;default&#8221;
tablet_text_alignment=&#8221;default&#8221;
phone_text_alignment=&#8221;default&#8221;
background_color_opacity=&#8221;1&#8243;
background_hover_color_opacity=&#8221;1&#8243;
column_backdrop_filter=&#8221;none&#8221;
column_shadow=&#8221;none&#8221;&#8230;
title: Legal Notice
excerpt: Legal information and contact details for KLZ Vertriebs GmbH.
featuredImage: null
locale: en
---
# Legal Notice &#8211; English
<h1>Legal Notice</h1>
<p><strong>Responsible for the content:</strong><br />
Michael Bodemer</p>
<p>KLZ Vertriebs GmbH<br />
Raiffeisenstraße 22<br />
73630 Remshalden</p>
<p>info@klz-cables.com<br />
www.klz-cables.com</p>
<p>Local Court Stuttgart<br />
HRB-Nr. 798037<br />
Place of jurisdiction: Stuttgart</p>
<p>Copyright:<br />
## Responsible for the content
Michael Bodemer
**KLZ Vertriebs GmbH**
Raiffeisenstraße 22
73630 Remshalden
Germany
## Contact
Email: [info@klz-cables.com](mailto:info@klz-cables.com)
Web: [www.klz-cables.com](https://www.klz-cables.com)
## Registration
Local Court Stuttgart
HRB-Nr. 798037
Place of jurisdiction: Stuttgart
## Copyright
All texts, images and other information published on the website are subject to copyright unless otherwise indicated. Any duplication, distribution, storage, transmission, reproduction or forwarding of the contents without written permission is expressly prohibited. For further information, please contact the above address.

View File

@@ -1,41 +1,36 @@
---
title: Privacy Policy &#8211; English
excerpt: >-
[vc_column column_padding=&#8221;no-extra-padding&#8221;
column_padding_tablet=&#8221;inherit&#8221;
column_padding_phone=&#8221;inherit&#8221;
column_padding_position=&#8221;all&#8221;
column_element_direction_desktop=&#8221;default&#8221;
column_element_spacing=&#8221;default&#8221;
desktop_text_alignment=&#8221;default&#8221;
tablet_text_alignment=&#8221;default&#8221;
phone_text_alignment=&#8221;default&#8221;
background_color_opacity=&#8221;1&#8243;
background_hover_color_opacity=&#8221;1&#8243;
column_backdrop_filter=&#8221;none&#8221;
column_shadow=&#8221;none&#8221;&#8230;
title: Privacy Policy
excerpt: Information on how we handle your personal data at KLZ Vertriebs GmbH.
featuredImage: null
locale: en
---
# Privacy Policy &#8211; English
<h1>Privacy Policy</h1>
<h2 class="text-2xl mb-4">1. Data protection at a glance</h2>
<h3 class="text-xl">General information</h3>
<p class="mb-4">The following information provides a simple overview of what happens to your personal data when you visit our website. Personal data are all data with which you can be personally identified. For detailed information on the subject of data protection, please refer to our data protection declaration listed below this text.</p>
<h2 class="text-2xl mb-4">2. General notes and compulsory information</h2>
<h3 class="text-xl">Data protection</h3>
<p class="mb-4">The operators of these pages take the protection of your personal data very seriously. We treat your personal data confidentially and in accordance with the legal data protection regulations and this data protection declaration. When you use this website, various personal data is collected. Personal data is data with which you can be personally identified. This privacy policy explains what data we collect and what we use it for. It also explains how we do this and for what purpose. We would like to point out that data transmission over the Internet (e.g. communication by e-mail) can have security gaps. It is not possible to completely protect data from access by third parties.</p>
<h3 class="text-xl">Revocation of your consent to data processing</h3>
<p class="mb-4">Many data processing operations are only possible with your express consent. You can revoke a previously granted consent at any time. For this purpose, an informal notification by e-mail to us is sufficient. The lawfulness of the data processing that took place up to the revocation remains unaffected by the revocation.</p>
<h3 class="text-xl">Right of appeal to the competent supervisory authority</h3>
<p class="mb-4">In the event of violations of data protection law, the person concerned has a right of appeal to the competent supervisory authority. The competent supervisory authority for data protection issues is the data protection commissioner of the federal state in which our company is located. A list of the data protection officers and their contact details can be found at the following link: https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html.</p>
<h3 class="text-xl">Right to data transferability</h3>
<p class="mb-4">You have the right to have data which we process automatically on the basis of your consent or in fulfilment of a contract handed over to you or to a third party in a common, machine-readable format. If you request the direct transfer of the data to another responsible party, this will only be done to the extent that it is technically feasible.</p>
<h3 class="text-xl">Information, blocking, deletion</h3>
<p class="mb-4">Within the framework of the applicable legal provisions, you have the right at any time to receive information free of charge about your stored personal data, its origin and recipients and the purpose of the data processing and, if applicable, a right to correct, block or delete this data. For this purpose, as well as for further questions regarding personal data, you can contact us at any time at the address given in the imprint.</p>
<h3 class="text-xl">Contradiction against advertising mails</h3>
<p class="mb-4">The use of contact data published within the scope of the imprint obligation for the transmission of not expressly requested advertising and information material is hereby contradicted. The operators of the site expressly reserve the right to take legal action in the event of unsolicited sending of advertising information, such as through spam e-mails.</p>
<h2 class="text-2xl mb-4">3. Data collection in our company</h2>
<h3 class="text-xl">Data transfer upon conclusion of the contract for services and digital contents</h3>
<p class="mb-4">We only transfer personal data to third parties if this is necessary within the scope of processing the contract, e.g. to the credit institution commissioned with processing payments. Any further transmission of data will not take place or only if you have expressly agreed to the transmission. Your data will not be passed on to third parties without your express consent, for example for advertising purposes. The basis for data processing is Art. 6 Par. 1 letter b DSGVO, which permits the processing of data for the fulfilment of a contract or pre-contractual measures.</p>
## 1. Data protection at a glance
### General information
The following information provides a simple overview of what happens to your personal data when you visit our website. Personal data are all data with which you can be personally identified. For detailed information on the subject of data protection, please refer to our data protection declaration listed below this text.
## 2. General notes and compulsory information
### Data protection
The operators of these pages take the protection of your personal data very seriously. We treat your personal data confidentially and in accordance with the legal data protection regulations and this data protection declaration. When you use this website, various personal data is collected. Personal data is data with which you can be personally identified. This privacy policy explains what data we collect and what we use it for. It also explains how we do this and for what purpose. We would like to point out that data transmission over the Internet (e.g. communication by e-mail) can have security gaps. It is not possible to completely protect data from access by third parties.
### Revocation of your consent to data processing
Many data processing operations are only possible with your express consent. You can revoke a previously granted consent at any time. For this purpose, an informal notification by e-mail to us is sufficient. The lawfulness of the data processing that took place up to the revocation remains unaffected by the revocation.
### Right of appeal to the competent supervisory authority
In the event of violations of data protection law, the person concerned has a right of appeal to the competent supervisory authority. The competent supervisory authority for data protection issues is the data protection commissioner of the federal state in which our company is located. A list of the data protection officers and their contact details can be found at the following link: [https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html](https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html).
### Right to data transferability
You have the right to have data which we process automatically on the basis of your consent or in fulfilment of a contract handed over to you or to a third party in a common, machine-readable format. If you request the direct transfer of the data to another responsible party, this will only be done to the extent that it is technically feasible.
### Information, blocking, deletion
Within the framework of the applicable legal provisions, you have the right at any time to receive information free of charge about your stored personal data, its origin and recipients and the purpose of the data processing and, if applicable, a right to correct, block or delete this data. For this purpose, as well as for further questions regarding personal data, you can contact us at any time at the address given in the imprint.
### Contradiction against advertising mails
The use of contact data published within the scope of the imprint obligation for the transmission of not expressly requested advertising and information material is hereby contradicted. The operators of the site expressly reserve the right to take legal action in the event of unsolicited sending of advertising information, such as through spam e-mails.
## 3. Data collection in our company
### Data transfer upon conclusion of the contract for services and digital contents
We only transfer personal data to third parties if this is necessary within the scope of processing the contract, e.g. to the credit institution commissioned with processing payments. Any further transmission of data will not take place or only if you have expressly agreed to the transmission. Your data will not be passed on to third parties without your express consent, for example for advertising purposes. The basis for data processing is Art. 6 Par. 1 letter b DSGVO, which permits the processing of data for the fulfilment of a contract or pre-contractual measures.

View File

@@ -62,6 +62,13 @@ locale: en
<h2>Challenges exist to be solved, not to debate how complicated they are.</h2>
Michael Bodemer is the go-to guy when things get complicated—and lets face it, thats often the case with cable networks. With sharp insight and a knack for practical solutions, Michael is one of our key players. Hes not just detail-oriented; hes a driving force—whether its in planning, customer interactions, or securing the best cables for every project.
<div class="mt-4">
<a href="/vcf/michael-bodemer" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 no-underline">
Download vCard Michael Bodemer
</a>
</div>
<h3><strong>A Legacy of Excellence in Every Connection</strong></h3>
<p>At KLZ, our expertise is built on generations of dedication to the energy industry. With decades of hands-on experience, weve grown alongside the evolution of cable technology, combining traditional craftsmanship with modern innovation. Each project we take on reflects a deep understanding of what it takes to create lasting, reliable energy solutions.</p>
<p>Paired with historic illustrations from the industrys early days, our story is a reminder of how far cables have come and how much care has always gone into connecting the world.
@@ -69,4 +76,11 @@ Michael Bodemer is the go-to guy when things get complicated—and lets face
<h2>Sometimes all it takes is a clear head and a good cable to make the world a little better.</h2>
Klaus is the man with the experience, bringing perspective and calm to the table—even when cable chaos threatens to take over. With impressive industry knowledge and a network as solid as our cables, he ensures everything runs smoothly. Klaus isnt just a problem solver; hes a strategic thinker who knows how to get to the point with a touch of humor.
<div class="mt-4">
<a href="/vcf/klaus-mintel" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 no-underline">
Download vCard Klaus Mintel
</a>
</div>
<h2>Our manifesto</h2>

View File

@@ -1,93 +1,112 @@
---
title: Terms &#8211; English
excerpt: >-
[vc_column column_padding=&#8221;no-extra-padding&#8221;
column_padding_tablet=&#8221;inherit&#8221;
column_padding_phone=&#8221;inherit&#8221;
column_padding_position=&#8221;all&#8221;
column_element_direction_desktop=&#8221;default&#8221;
column_element_spacing=&#8221;default&#8221;
desktop_text_alignment=&#8221;default&#8221;
tablet_text_alignment=&#8221;default&#8221;
phone_text_alignment=&#8221;default&#8221;
background_color_opacity=&#8221;1&#8243;
background_hover_color_opacity=&#8221;1&#8243;
column_backdrop_filter=&#8221;none&#8221;
column_shadow=&#8221;none&#8221;&#8230;
title: Terms
excerpt: Delivery and Payment Terms of KLZ Vertriebs GmbH.
featuredImage: null
locale: en
---
# Terms &#8211; English
<h1 class="p1">Liefer- und Zahlungsbedingungen</h1>
<p class="p1">Stand November 2024</p>
*Status November 2024*
<h3 class="p1"><span class="s1">1.</span> Allgemeines</h3>
<p class="p2">Diese Liefer- und Zahlungsbedingungen (L&amp;Z) der KLZ Vertriebs GmbH gelten ausschließlich; entgegenstehende oder von unseren Bedingungen abweichende Bedingungen des Kunden erkennen wir nicht an, es sei denn, wir hätten ausdrücklich schriftlich ihrer Geltung zugestimmt. Unsere L&amp;Z gelten auch dann, wenn wir in Kenntnis entgegenstehender oder von unseren L&amp;Z abweichender Bedingungen des Bestellers die Lieferung an diesen vorbehaltlos ausführen. Unsere L&amp;Z gelten nur gegenüber Unternehmern im Sinn von § 310 Abs. 1 BGB sowie juristischen Personen des <span class="s3">öffentlichen </span>Rechts oder öffentlich-rechtliches Sondervermögen.</p>
<p class="p2">Nebenabreden, Vorbehalte, Änderungen, Ergänzungen usw. bedürfen zu ihrer Wirksamkeit unserer schriftlichen Bestätigung.</p>
<p class="p2">Hinweise auf die Geltung gesetzlicher Vorschriften haben nur klarstellende Bedeutung. Auch ohne eine derartige Klarstellung gelten daher die gesetzlichen Vorschriften, soweit sie in diesen L&amp;Z nicht unmittelbar abgeändert oder <span class="s3">ausdrücklich </span>ausgeschlossen werden.</p>
## 1. General
<h3 class="p1"><span class="s1">2.</span> Angebote</h3>
<p class="p1">Sofern nicht ausdrücklich als bindend bezeichnet, sind unsere Angebote freibleibend; die Bestellung des <span class="s2">Kunden ist </span>als Angebot gemäß § 145 BGB zu qualifizieren.</p>
These delivery and payment terms (DPT) of KLZ Vertriebs GmbH apply exclusively; contrary or deviating conditions of the customer are not recognized by us, unless we have expressly agreed in writing to their validity. Our DPT also apply if we execute the delivery to the customer without reservation in knowledge of contrary or deviating conditions of the order. Our DPT only apply to entrepreneurs within the meaning of § 310 (1) BGB as well as legal persons of public law or public-law special assets.
<h3 class="p1"><span class="s1">3.</span> Preise</h3>
<p class="p1">Alle von uns genannten Preise verstehen sich zzgl. der jeweiligen gesetzlichen Mehrwertsteuer vor Metallzuschlag fracht- frei innerhalb der Bundesrepublik Deutschland (Festland), jedoch ohne Abladen. Die Verkaufspreise, soweit sie als Hohlpreis deklariert sind, enthalten keinerlei Metallwerte. Diese werden zusätzlich separat berechnet.</p>
Side agreements, reservations, changes, supplements, etc. require our written confirmation for their validity.
<h3 class="p1"><span class="s1">4.</span> Metallnotierung</h3>
<p class="p2">Basis zur Kupferabrechnung ist die Notierung &#8220;LME Copper official price cash offer&#8221;, Durchschnitt des Liefervormonats zuzüglich der dann aktuellen von uns benannten Kupfer-Prämie.</p>
<p class="p2">Basis zur Aluminiumabrechnung ist die Notierung &#8220;LME Aluminium official price cash offer&#8221;, Durchschnitt des Liefervormonats zuzüglich der dann von uns benannten Aluminium-Prämie. USD werden auf Basis des EUR/USD LME-FX-Rate (MTLE) in EUR umgerechnet. Die entsprechenden Notierungen können Sie der Web-Seite www.westmetall.com entnehmen. Die Prämienzuschläge können stark variieren und KLZ behält sich das Recht vor, diese fristgerecht anzupassen, ungeachtet der Angebotslegung.</p>
References to the applicability of legal provisions have only clarifying significance. Even without such clarification, the legal provisions therefore apply, insofar as they are not directly amended or expressly excluded in these DPT.
<h3 class="p1"><span class="s1">5.</span> Metallzahl</h3>
<p class="p2">Die von uns ausgewiesene Metallzahl ist eine rein kaufmännische Berechnungsgröße für den Metallinhalt, die in die Berechnung des Gesamtpreises eines Kabels eingeht. Damit entsprechen wir Ihrem Wunsch eine Vergleichbarkeit in ihrem System auf Hohlpreisbasis zu ermöglichen. Die Metallzahl gibt damit nicht das Gewicht des tatsächlich im Kabel enthaltenen Leitermetalls an. Sie ist ein rein kalkulatorischer Berechnungsfaktor, der jedoch keine unmittelbaren Rückschlüsse auf die im Kabel verwendeten Kupfer- bzw. Aluminiummengen zulässt. Wir weisen ausdrücklich darauf hin, final nur den Vollpreis für Vergleichszwecke heranzuziehen. Soweit Sie es wünschen andere Metallzahlen zu Grunde zu legen, sind wir gerne dazu bereit, das Angebot in den Bestandteilen umzurechnen. Bei jeglicher Änderung bleibt aber der Vollpreis der gleiche Betrag.</p>
## 2. Offers
<h3 class="p1"><span class="s1">6.</span> Auftragsänderung / Auftragsstorno</h3>
<p class="p2">Nach Auftragsbestätigung werden Änderungen an bestätigten Aufträgen nur nach Prüfung und gesonderter ausdrücklicher Zustimmung durch uns akzeptiert. Wir behalten uns bei allen Auftragsänderungen das Recht vor, einen durch die Änderung entstandenen Mehraufwand, wie z.B. Bearbeitungskosten oder Entsorgungskosten in Rech<span class="s2">nung zu stellen.</span></p>
Unless expressly designated as binding, our offers are non-binding; the customer's order is to be qualified as an offer according to § 145 BGB.
<h3 class="p1"><span class="s1">7.</span> Eigentumsvorbehalt</h3>
<p class="p2">Wir behalten uns an den von uns gelieferten Waren nachfolgend: Vorbehaltsware bis zur vollständigen Begleichung aller unserer Forderungen aus den Geschäftsbeziehungen mit dem Besteller, das Eigentum vor. Der Eigentumsvorbehalt bleibt auch dann bestehen, wenn einzelne Forderungen in eine laufende Rechnung aufgenommen <span class="s2">werden (Kontokorrentvorbehalt).</span></p>
## 3. Prices
<h3 class="p1"><span class="s1">8. </span>Zahlungsbedingungen <span class="s3">| </span>Aufrechnung <span class="s3">| </span>Zurückbehaltungsrechte</h3>
<p class="p2">Unsere Rechnungen sind 14 Tage nach Rechnungsdatum ohne jeden Abzug zahlbar. Bei Nichteinhaltung der vereinbarten Zahlungsbedingungen sind wir berechtigt, Zinsen in Höhe von 7 %-Punkten <span class="s4">über dem </span>Basiszinssatz zu berechnen; das Recht zur Geltendmachung weitergehender Schäden, insbesondere <span class="s5">nachgewiesener </span>höherer Zinsen, bleibt hiervon unberührt.</p>
All prices stated by us are understood plus the respective statutory value-added tax, before metal surcharge, freight-free within the Federal Republic of Germany (mainland), however without unloading. The sales prices, insofar as they are declared as hollow prices, contain no metal values whatsoever. These are additionally calculated separately.
<h3 class="p1"><span class="s1">9.</span> Liefervorbehalt <span class="s3">|</span> Teillieferungen</h3>
<p class="p2">Sämtliche Lieferzusagen unsererseits stehen, sofern nichts anderes ausdrücklich schriftlich vereinbart ist, unter dem Vorbehalt der richtigen und rechtzeitigen Belieferung durch unsere Produzenten. Wir behalten uns jederzeit Teillieferungen vor. Darüber hinaus behalten wir uns branchenübliche Über- oder Unter<span class="s4">lieferungen </span>bis zu 10 % der bestellten Menge vor.</p>
## 4. Metal quotation
<h3 class="p1"><span class="s1">10.</span> Lieferfristen und Liefertermine</h3>
<p class="p2">Die Lieferfrist wird individuell vereinbart bzw. von uns bei Annahme der Bestellung angegeben. Sofern wir verbindliche Lieferfristen aus Gründen, die wir nicht zu vertreten haben, nicht einhalten können (Nichtverfügbarkeit der Leistung), werden wir den Besteller hierüber unverzüglich informieren und gleichzeitig die voraussichtliche, neue Lieferfrist mitteilen. Ist die Leistung auch innerhalb der neuen Lieferfrist nicht verfügbar, sind wir berechtigt, ganz oder teilweise vom Vertrag zurückzutreten. Eine bereits erbrachte Gegenleistung des Bestellers werden wir unverzüglich erstatten. Nichtverfügbarkeit der Leistung liegt beispielsweise vor bei nicht recht- zeitiger Selbstbelieferung durch unseren Zulieferer, wenn wir ein kongruentes Deckungsgeschäft abgeschlossen haben, bei sonstigen Störungen in der Lieferkette etwa aufgrund höherer Gewalt oder wenn wir im Einzelfall zur <span class="s3">Beschaffung nicht verpflichtet sind.</span></p>
<p class="p2">Der Eintritt unseres Lieferverzugs bestimmt sich nach den gesetzlichen Vorschriften. In jedem Fall ist aber eine <span class="s3">Mahnung durch </span>den Käufer erforderlich.</p>
<p class="p2">Die gesetzlichen Rechte bleiben im Übrigen unberührt.</p>
<p class="p2">Fixgeschäfte setzen die ausdrückliche schriftliche Bezeichnung als solche voraus. Ansonsten ist der Besteller <span class="s4">stets </span>verpflichtet, uns schriftlich eine angemessene Nachfrist zu setzen, wenn von uns zugesagte Termine und/ oder Fristen nicht eingehalten werden. Wird auch die Nachfrist nicht eingehalten, ist der Besteller berechtigt, vom <span class="s3">Vertrag zurückzutreten.</span></p>
<p class="p2">Im Fall höherer Gewalt und/oder sonstiger von uns nicht vorhersehbarer außergewöhnlicher und/oder unverschul<span class="s3">deter </span>Umstände, auch wenn sie bei unserem Vorlieferanten eintreten, verlängert sich eine von uns zugesagte <span class="s4">Lieferfrist bis zur </span>Behebung des vorerwähnten Ereignisses. Ist dieser Zeitpunkt nicht überblickbar, sind sowohl der Besteller als auch wir berechtigt, von dem abgeschlossenen Vertrag zurückzutreten. In diesem Fall sind beiderseits <span class="s3">Schadensersatzansprüche </span>ausgeschlossen. Wir verpflichten uns, bei Bekanntwerden vorerwähnter Umstände <span class="s4">den</span> <span class="s4">Besteller</span> <span class="s4">hiervon</span> <span class="s4">unverzüglich</span> <span class="s4">zu </span>benachrichtigen.</p>
<p class="p2">Ist die Einhaltung eines Termins davon abhängig, dass uns seitens des Bestellers bestimmte Angaben und/oder Pläne, Freigabeerklärungen oder ähnliches erteilt werden, beginnt die Lieferfrist erst von dem Zeitpunkt an zu lau- fen, zu dem uns die vollständigen Angaben des Bestellers schriftlich vorliegen. Wird die Anlieferung auf Wunsch des Bestellers über den vertraglich vorgesehenen Zeitpunkt hinausgeschoben, kann von uns beginnend mit einer Frist von frühestens 10 Werktagen nach Anzeige der Versandbereitschaft dem Besteller ein Lagergeld in Höhe von 2 % des Rechnungsbetrages für jeden angefangenen Monat, maximal jedoch <span class="s3">10 % insgesamt berechnet werden.</span></p>
Basis for copper billing is the quotation "LME Copper official price cash offer", average of the delivery month plus the then current copper premium named by us.
<h3 class="p1"><span class="s1">11.</span> Abrufaufträge</h3>
<p class="p2">Wird uns ein Abrufauftrag erteilt und werden über die Abruftermine keine gesonderten schriftlichen Vereinbarungen getroffen, ist der Besteller verpflichtet, uns die einzelnen Abruftermine so mitzuteilen, dass zwischen Eingang der Abrufmitteilung bei uns und Auslieferung mindestens 14 Werktage und die letzte Auslieferung spätestens 90 <span class="s2">Tage nach unserer Auftragsbestätigung </span>liegt.</p>
Basis for aluminum billing is the quotation "LME Aluminium official price cash offer", average of the delivery month plus the then current aluminum premium named by us. USD are converted to EUR on the basis of the EUR/USD LME-FX-Rate (MTLE). The corresponding quotations can be taken from the website [www.westmetall.com](https://www.westmetall.com). The premium surcharges can vary strongly and KLZ reserves the right to adjust these in due time, regardless of the offer submission.
<h3 class="p1"><span class="s1">12.</span> Maß- und Gewichtsangaben</h3>
<p class="p2">Alle Angaben über Durchmesser, Gewicht, technische Gestaltung, Herstellung und Umfang der von uns zu liefernden <span class="s3">Ware stehen </span>unter dem Vorbehalt der Abweichung innerhalb der handelsüblichen zulässigen Toleranzen. Darüber hinaus behalten wir uns Änderungen, die einer technischen Verbesserung dienen, jederzeit vor. Farbabweichungen und/oder Abweichungen in der äußeren Beschaffenheit der von uns zu liefernden Ware, die jedoch deren Qualität und technische Wirksamkeit unbeeinflusst lässt, begründen keine Mängelhaftungsansprüche des Bestellers.</p>
## 5. Metal number
<h3 class="p1"><span class="s1">13.</span> Gefahrübergang und -tragung</h3>
<p class="p2">Die Lieferung erfolgt DAP frei Bestimmungsort Deutschland, wo auch der Erfüllungsort für die Lieferung und eine etwaige Nacherfüllung ist.</p>
<p class="p2">Wird die bestellte Ware von uns versandbereit gestellt und/oder verzögert sich die Versendung und/oder der Abruf aus Gründen, die vom Besteller zu vertreten sind, sind wir berechtigt, Ersatz des hieraus entstehenden Schadens einschließlich Mehraufwendungen zu verlangen. Hierfür berechnen wir eine pauschale Entschädigung i.H.v 2% des Rechnungsbetrages für jeden angefangenen Monat, maximal jedoch 10 % insgesamt beginnend mit der Lieferfrist bzw. mangels einer Lieferfrist mit der Mitteilung der Versandbereitschaft der Ware.</p>
<p class="p2">Der Nachweis eines höheren Schadens und unsere gesetzlichen Ansprüche (insbesondere Ersatz von Mehraufwendungen, angemessene Entschädigung, Kündigung) bleiben unberührt; die Pauschale ist aber auf weitergehende Geldansprüche anzurechnen. Dem Besteller bleibt der Nachweis gestattet, dass uns überhaupt kein oder nur ein <span class="s3">wesentlich geringerer Schaden </span>als vorstehende Pauschale entstanden ist. Rücksendungen an uns, die nicht vorher von uns schriftlich bestätigt worden sind, erfolgen auf alleinige Gefahr des <span class="s4">Bestellers.</span></p>
The metal number indicated by us is a purely commercial calculation figure for the metal content, which goes into the calculation of the total price of a cable. Thereby we comply with your wish to enable comparability in your system on hollow price basis. The metal number does not indicate the weight of the conductor metal actually contained in the cable. It is a purely calculatory calculation factor, however, which does not allow any direct conclusions on the copper or aluminum quantities used in the cable. We expressly point out that only the full price should be used for comparison purposes. Insofar as you wish to use other metal numbers as a basis, we are happy to recalculate the offer in its components. However, the full price remains the same amount in any change.
<h3 class="p1"><span class="s1">14.</span> Mängelhaftung</h3>
<p class="p2">Wir haften nur dann für die Einhaltung objektiver Anforderungen an der Ware, wenn und soweit zwischen dem Besteller und uns keine Beschaffenheitsvereinbarung getroffen wurde. Die einzuhaltenden subjektiven Anforderungen gehen den einzuhaltenden objektiven Anforderungen vor. Im Zweifel ergeben sich die vereinbarten Anforderungen <span class="s3">an die Ware aus dem von uns </span>bereitgestellten Datenblatt. Einzelne, nicht immer auszuschließende marginale Abweichungen, dürfen durch Reparaturen, wie zum Beispiel Mantelmanschetten nachgebessert werden.</p>
<p class="p2">Jedwede Mängelhaftungsansprüche des Bestellers setzen voraus, dass dieser die ihm übersandte Ware unverzüglich, d. h. in der Regel sofort bei Anlieferung (noch in Anwesenheit des Transporteurs) auf ihre ordnungsgemäße Beschaffenheit hin überprüft und uns zu verzeichnende sichtbare Mängel unmittelbar nach Erhalt der Ware und verdeckte Mängel unmittelbar nach deren Feststellung schriftlich mitteilt. Soweit ein rechtzeitig gerügter, nicht nur unerheblicher Mangel der Kaufsache vorliegt, sind wir nach unserer Wahl zur Mangelbeseitigung oder zur Ersatz<span class="s3">lieferung (Nacherfüllung) berechtigt.</span></p>
<p class="p2">Wir übernehmen im Rahmen der Nacherfüllung in keinem Fall Ein- oder Ausbaukosten, wenn und soweit die Mangelhaftigkeit der Ware zum Zeitpunkt des Einbaus dem Besteller bekannt oder grob fahrlässig unbekannt geblieben <span class="s4">ist.</span></p>
<p class="p2">Sind wir zur Mangelbeseitigung/Ersatzlieferung nicht bereit oder nicht in der Lage oder verzögert sich diese über angemessene Fristen hinaus aus Gründen, die wir zu vertreten haben, oder schlägt sie in sonstiger Weise fehl, so ist der Besteller nach seiner Wahl berechtigt, vom Vertrag zurückzutreten oder eine entsprechende Minderung des <span class="s3">Kaufpreises zu verlangen.</span></p>
<p class="p2">Weitergehende Ansprüche des Bestellers, gleich aus welchem Rechtsgrund, sind nach näherer Maßgabe der Regelungen in nachstehender Ziffer 15 ausgeschlossen bzw. beschränkt.</p>
<p class="p1">Die Verjährungsfristen für Mängelhaftungsansprüche beträgt 24 Monate ab Übergabe der Ware.</p>
<p class="p1">Sollte es bei einer Mängelrüge zu unterschiedlichen Meinungen bezüglich des Kabelschaden kommen, gilt hier im Zweifelsfall nur die Expertise des VDE-Instituts selbst. Andere, auch akkreditierte Testlabore, akzeptieren wir nicht. Wir weisen ausdrücklich daraufhin, dass beim Verlegen des Kabels in den Graben oder in Rohren, bzw. in Bauwerke eine ständige Sichtkontrolle durch den Kabelverleger vorzunehmen ist, ob Auffälligkeiten zu vermerken sind. Eine spätere Reklamation, die fahrlässiges Verhalten vermuten lässt, schränkt sich damit ein.</p>
## 6. Order change / Order cancellation
<h3 class="p1"><span class="s1">15.</span> Schadenersatz <span class="s3">| </span>Gesamthaftung</h3>
<p class="p2">Wir haften unbeschränkt nur für Vorsatz und grobe Fahrlässigkeit sowie für Schäden aus einer Verletzung von Leben, Körper oder Gesundheit, die auf mindestens fahrlässiger Pflichtverletzung unsererseits oder unserer gesetzlichen Vertreter oder Erfüllungsgehilfen beruhen; ebenso haften wir unbeschränkt im Fall von uns übernommenen bzw. abgegebenen Garantien und Zusicherungen, sofern ein davon umfasster Mangel unsere Haftung auslöst sowie im Fall einer Haftung nach dem Produkthaftungsgesetz oder sonstigen Gefährdungshaftungstatbeständen. Im Fall sonstiger schuldhafter Verletzung wesentlicher Vertragspflichten („Kardinalpflichten“) ist unsere verbleibende Haftung auf den vertragstypischen vorhersehbaren Schaden beschränkt. Mangelfolgeschäden sowie entgangener Gewinn schließen wir grundsätzlich aus.</p>
After order confirmation, changes to confirmed orders are only accepted after examination and separate express consent by us. We reserve the right in all order changes to charge the additional effort caused by the change, such as processing costs or disposal costs in the bill.
<h3 class="p1"><span class="s1">16.</span> Kabeltrommeln</h3>
<p class="p2">Unsere Kabel werden auf stabilen Vollholztrommeln geliefert. Auf Wunsch vermitteln wir Ihnen Partner, die diese Trommeln gegen eine Gebühr abholen.</p>
## 7. Retention of title
<h3 class="p1"><span class="s1">17.</span> Sonstiges</h3>
<p class="p2">Es gilt ausschließlich das Recht der Bundesrepublik Deutschland unter Ausschluss des UN-Kaufrechts (CISG). Gerichtsstand ist nach unserer Wahl Stuttgart, der Erfüllungsort der Lieferverpflichtung oder das für den Sitz des Bestellers zuständige Gericht, sofern der Besteller Kaufmann, juristische Person des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen ist oder keinen allgemeinen Gerichtsstand im Inland hat.</p>
<p class="p2">Mit der Veröffentlichung der vorliegenden L&amp;Z im Internet werden alle von uns früher verwendeten Bedingungen gegenstandslos.</p>
We retain title to the goods delivered by us hereinafter: reserved goods until full settlement of all our claims from the business relationship with the orderer. The retention of title also remains in effect if individual claims are included in a running account (current account reservation).
<p class="p1"><a href="/uploads/2025/01/agbs.pdf">Download als PDF</a></p>
## 8. Payment terms | Offsetting | Right of retention
Our invoices are payable 14 days after invoice date without any deduction. In case of non-compliance with the agreed payment terms, we are entitled to calculate interest at a rate of 7 percentage points above the base interest rate; the right to assert further damages, in particular proven higher interest, remains unaffected by this.
## 9. Delivery reservation | Partial deliveries
All delivery promises on our part are subject, unless otherwise expressly agreed in writing, to the reservation of correct and timely delivery by our producers. We reserve the right to partial deliveries at any time. In addition, we reserve the right to industry-standard over- or underdeliveries up to 10% of the ordered quantity.
## 10. Delivery periods and dates
The delivery period is individually agreed or stated by us upon acceptance of the order. If we cannot meet binding delivery periods for reasons for which we are not responsible (non-availability of the service), we will inform the customer of this immediately and at the same time communicate the expected new delivery period. If the service is also not available within the new delivery period, we are entitled to withdraw from the contract in whole or in part. An already rendered consideration of the customer will be refunded immediately. Non-availability of the service exists, for example, in case of non-timely self-supply by our supplier, if we have concluded a congruent covering transaction, in case of other disruptions in the supply chain due to force majeure or if we are not obliged to procure in individual cases.
The occurrence of our delivery delay is determined according to the legal provisions. In any case, however, a dunning by the buyer is required.
The legal rights remain otherwise unaffected.
Fixed transactions require the express written designation as such. Otherwise, the orderer is always obliged to set us a reasonable grace period in writing if promised dates and/or periods are not met. If the grace period is also not met, the orderer is entitled to withdraw from the contract.
In case of force majeure and/or other unforeseeable extraordinary and/or unavoidable circumstances, even if they occur at our supplier, a promised delivery period is extended until the remediation of the aforementioned event. If this point in time is not foreseeable, both the orderer and we are entitled to withdraw from the concluded contract. In this case, both sides' claims for damages are excluded. We undertake, upon becoming aware of the aforementioned circumstances, to inform the orderer hereof immediately to notify.
If compliance with a date depends on the orderer providing certain information and/or plans, release declarations or similar, the delivery period only begins to run from the point in time at which the complete information of the orderer is available to us in writing. If the delivery is postponed beyond the contractually foreseen point in time at the request of the orderer, we can, starting with a period of at least 10 working days after notification of readiness for shipment, charge the orderer a storage fee of 2% of the invoice amount for each month started, however maximum 10% in total.
## 11. Call-off orders
If a call-off order is issued to us and no separate written agreements are made about the call-off dates, the orderer is obliged to inform us of the individual call-off dates in such a way that between receipt of the call-off notification with us and delivery at least 14 working days and the last delivery at the latest 90 days after our order confirmation lie.
## 12. Dimension and weight specifications
All information about diameter, weight, technical design, manufacture and scope of the goods to be delivered by us are subject to the reservation of deviation within the commercially usual permissible tolerances. Darüber hinaus behalten wir uns Änderungen, die einer technischen Verbesserung dienen, jederzeit vor. Color deviations and/or deviations in the external characteristics of the goods to be delivered by us, which however leave their quality and technical effectiveness unaffected, do not give rise to any claims for defects by the orderer.
## 13. Transfer of risk and burden
Delivery is made DAP free destination Germany, where the place of performance for delivery and any subsequent performance is also located.
If the ordered goods are made ready for shipment by us and/or the dispatch and/or the call-off is delayed for reasons for which the orderer is responsible, we are entitled to demand compensation for the damage resulting therefrom including additional expenses. For this, we calculate a flat-rate compensation of 2% of the invoice amount for each month started, however maximum 10% in total, starting with the delivery period or lacking a delivery period with the notification of readiness for shipment of the goods.
Proof of higher damage and our legal claims (in particular compensation for additional expenses, reasonable compensation, termination) remain unaffected; the flat-rate is however to be credited against further monetary claims. The orderer is permitted to prove that no damage or only a substantially lower damage than the aforementioned flat-rate has arisen for us. Returns to us, which have not been confirmed by us in writing beforehand, are at the sole risk of the orderer.
## 14. Liability for defects
We are only liable for compliance with objective requirements for the goods if and insofar as no quality agreement has been concluded between the orderer and us. The subjective requirements to be met take precedence over the objective requirements to be met. In case of doubt, the agreed requirements for the goods result from the data sheet provided by us. Individual, not always unavoidable marginal deviations may be remedied by repairs, such as sleeve cuffs.
Any claims for defects by the orderer presuppose that the orderer checks the goods sent to him immediately, i.e. usually immediately upon delivery (still in the presence of the carrier) for their proper condition and notifies us of visible defects to be recorded immediately after receipt of the goods and hidden defects immediately after their discovery in writing. Insofar as a timely, not only insignificant defect of the purchase item exists, we are entitled at our choice to remedy the defect or to make an replacement delivery (subsequent performance).
We do not assume any installation or removal costs within the scope of subsequent performance, if and insofar as the defectiveness of the goods was known to the orderer at the time of installation or grossly negligent unknown remained.
If we are not willing or able to remedy the defect/make a replacement delivery or if this is delayed beyond reasonable periods for reasons for which we are responsible, or if it fails in any other way, the orderer is entitled at his choice to withdraw from the contract or to demand a corresponding reduction of the purchase price.
Further claims of the orderer, regardless of the legal basis, are excluded or limited according to the detailed provisions in the following item 15.
The limitation periods for claims for defects are 24 months from delivery of the goods.
If there are different opinions regarding cable damage in the event of a defect notification, only the expertise of the VDE Institute itself applies in case of doubt. We do not accept other, even accredited test laboratories. We expressly point out that when laying the cable in the trench or in pipes, or in buildings, a constant visual inspection must be carried out by the cable layer to check for any noticeable features. A later complaint that suggests negligent behavior is thus restricted.
## 15. Damages | Total liability
We are liable without limitation only for intent and gross negligence as well as for damage resulting from a violation of life, body or health, which is based on at least negligent breach of duty on our part or on the part of our legal representatives or vicarious agents; we are also liable without limitation in the case of guarantees and assurances assumed or given by us, insofar as a defect covered by them triggers our liability, as well as in the case of liability under the Product Liability Act or other risk liability provisions. In the case of other culpable violation of essential contractual obligations ("cardinal obligations"), our remaining liability is limited to the contract-typical foreseeable damage. Consequential damages as well as lost profits are fundamentally excluded.
## 16. Cable drums
Our cables are delivered on stable solid wood drums. Upon request, we will mediate partners who will pick up these drums for a fee.
## 17. Other
Only the law of the Federal Republic of Germany applies, excluding the UN Convention on Contracts for the International Sale of Goods (CISG). The place of jurisdiction is at our choice Stuttgart, the place of performance for the delivery obligation or the court responsible for the seat of the orderer, provided the orderer is a merchant, a legal person under public law or a public-law special asset or has no general place of jurisdiction in Germany.
With the publication of these DPT on the Internet, all previously used conditions of ours become void.
[Download as PDF](/AGB-KLZ-1-2026.pdf)

View File

@@ -10,6 +10,12 @@ categories:
- Solarkabel
images:
- /uploads/2025/06/H1Z2Z2-K-scaled.webp
application: >
Das H1Z2Z2-K entspricht der Norm DIN EN 50618 (VDE 0283-618) und ist speziell für die
Verkabelung von Photovoltaiksystemen konzipiert. Es kann fest verlegt oder flexibel
geführt werden im Gebäude, im Freien, in Industrieanlagen, landwirtschaftlichen
Betrieben oder sogar in explosionsgefährdeten Bereichen. Die Leitung ist UV-, ozon-
und wasserbeständig (AD7) und darf direkt in der Erde verlegt werden.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,12 @@ categories:
- Niederspannungskabel
images:
- /uploads/2025/01/N2X2Y-scaled.webp
application: >
Das N2X2Y entspricht den Normen HD 603 S1 Teil 5G und HD 627 S1 Teil 4H (gleichlautend
mit DIN VDE 0276-603 und -627) und ist für eine Betriebsfrequenz von 50Hz ausgelegt.
Es eignet sich für die feste Verlegung in Innenräumen, im Erdreich, im Freien und in
Industrieumgebungen mit hohen Temperatur- und Belastungsanforderungen. Die maximale
Betriebstemperatur liegt bei +90°C, im Kurzschlussfall sind +250°C zulässig.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -9,6 +9,12 @@ description: >
categories:
- Hochspannungskabel
images: []
application: >
Die N2X(F)K2Y-Hochspannungskabelserie ist speziell für den Einsatz in
Hochspannungsanwendungen konzipiert, wobei sie eine optimale Leistung durch die
Verwendung von hochleitfähigem Kupfer und einer fortschrittlichen XLPE-Isolierung
bietet. Diese Kombination gewährleistet eine hohe Durchschlagsfestigkeit und eine
effiziente Thermozyklierung unter verschiedenen Betriebsbedingungen.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -9,6 +9,13 @@ description: >
categories:
- Hochspannungskabel
images: []
application: >
Die N2X(F)KLD2Y-Hochspannungskabel Serie 2 sind speziell für den Einsatz in
Hochspannungsanwendungen konzipiert, wobei sie eine optimale Leistung durch die
Verwendung von hochleitfähigen Kupferleitern und einer fortschrittlichen
XLPE-Isolierung bieten. Diese Kabelserie ist besonders geeignet für anspruchsvolle
industrielle Umgebungen, wo hohe Durchschlagsfestigkeit und
Thermozyklierungsfähigkeit erforderlich sind.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,12 @@ categories:
- Mittelspannungskabel
images:
- /uploads/2025/01/N2XS2Y-scaled.webp
application: >
Das N2XS2Y erfüllt die Normen DIN VDE 0276-620, HD 620 S2 und IEC 60502. Es eignet sich
zur Verlegung in Innenräumen, Kabelkanälen, im Freien, im Wasser, auf Kabelpritschen
und insbesondere im Erdreich. Aufgrund seines widerstandsfähigen Mantels wird es
häufig in Industrieanlagen, Kraftwerken und Schaltstationen eingesetzt, wo Stabilität
und Langlebigkeit gefordert sind.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,12 @@ categories:
- Mittelspannungskabel
images:
- /uploads/2025/01/N2XSF2Y-3-scaled.webp
application: >
Das N2XS(F)2Y erfüllt die gängigen Normen DIN VDE 0276-620, HD 620 S2 und IEC 60502 und
ist für die Verlegung in Innenräumen, Kabelkanälen, im Freien, in Wasser, Erde und auf
Kabelpritschen geeignet. Besonders in EVU-Netzen, Industrieanlagen und Kraftwerken
spielt dieses Kabel seine Stärken aus überall dort, wo Langlebigkeit,
Wasserdichtigkeit und Sicherheit gefragt sind.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,12 @@ categories:
- Hochspannungskabel
images:
- /uploads/2025/06/N2XSFL2Y-3-scaled.webp
application: >
Das N2XS(FL)2Y ist konzipiert für die Verlegung im Erdreich, in Kabelkanälen, in Rohren,
im Freien und in Innenräumen. Es entspricht der Norm IEC 60840 und lässt sich
individuell auf projektspezifische Anforderungen anpassen. Typisch eingesetzt wird es
in Übertragungsnetzen, Umspannwerken und großen Industrieanlagen, wo maximale
Zuverlässigkeit gefordert ist.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,12 @@ categories:
- Mittelspannungskabel
images:
- /uploads/2025/01/N2XSFL2Y-2-scaled.webp
application: >
Das N2XS(FL)2Y erfüllt die Standards DIN VDE 0276-620, HD 620 S2 und IEC 60502. Es
eignet sich hervorragend für die Verlegung in Innenräumen, Kabelkanälen, im Freien, in
Erde, im Wasser sowie auf Kabelpritschen insbesondere in EVU-Netzen,
Industrieanlagen und Schaltstationen, wo erhöhte Anforderungen an mechanische
Belastbarkeit und Wasserdichtigkeit bestehen.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,12 @@ categories:
- Mittelspannungskabel
images:
- /uploads/2025/01/N2XSY-scaled.webp
application: >
Das N2XSY erfüllt die Normen DIN VDE 0276-620, HD 620 S2 und IEC 60502. Es ist
ausgelegt für die Verlegung in Innenräumen, Kabelkanälen, im Wasser, im Erdreich oder
im Freien (bei geschützter Installation). Ob in Industrieanlagen, Kraftwerken oder
Schaltanlagen dieses Kabel sorgt für eine sichere und verlustarme
Energieübertragung im Mittelspannungsbereich.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,13 @@ categories:
- Niederspannungskabel
images:
- /uploads/2025/01/N2XY-scaled.webp
application: >
Das N2XY wird in Niederspannungsanlagen zur Energieverteilung eingesetzt zum Beispiel
in Kabeltrassen, Rohren, auf Wänden oder direkt im Erdreich. Es lässt sich sowohl im
Innen- als auch im Außenbereich installieren und ist auch für feuchte Umgebungen
geeignet. Dank verschiedener Aderkonfigurationen (einadrig bis vieradrig) und
Querschnitten bis 630mm² lässt sich das Kabel flexibel an die jeweilige Anwendung
anpassen.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -10,6 +10,12 @@ categories:
- Niederspannungskabel
images:
- /uploads/2025/01/NA2X2Y-scaled.webp
application: >
Das NA2X2Y entspricht der Norm DIN VDE 0276-603 (HD 603) und ist ausgelegt für die
feste Verlegung in Innenräumen, Kabelkanälen, im Erdreich, im Wasser oder im Freien.
Es kommt bevorzugt in Kraftwerken, Industrieanlagen, Schaltanlagen und Ortsnetzen zum
Einsatz überall dort, wo robuste Kabel gefragt sind, die im Betrieb und bei der
Verlegung hohen mechanischen Belastungen standhalten.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -9,6 +9,12 @@ description: >
categories:
- Hochspannungskabel
images: []
application: >
Die NA2X(F)K2Y-Hochspannungskabelserie ist speziell für den Einsatz in
Hochspannungsanwendungen entwickelt worden, wobei sie sich durch eine hohe
Strombelastbarkeit und exzellente Kurzschlussstromfestigkeit auszeichnet. Diese Kabel
sind mit einer fortschrittlichen XLPE-Isolierung ausgestattet, die eine hohe
Durchschlagsfestigkeit and Thermozyklierungsfähigkeit bietet.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

View File

@@ -9,6 +9,12 @@ description: >
categories:
- Hochspannungskabel
images: []
application: >
Die NA2X(F)KLD2Y-Hochspannungskabelserie ist für den Einsatz in
Hochspannungsanwendungen konzipiert und bietet eine optimale Lösung für
anspruchsvolle industrielle Umgebungen. Mit ihrer fortschrittlichen
XLPE-Isolierung und Kupferleitern erfüllt sie hohe Anforderungen an die
Durchschlagsfestigkeit und Thermozyklierung.
locale: de
---
<ProductTabs technicalData={<ProductTechnicalData data={{

Some files were not shown because too many files have changed in this diff Show More