Compare commits
55 Commits
fix/refere
...
fix/ci-sub
| Author | SHA1 | Date | |
|---|---|---|---|
| 0138064bcc | |||
| f74df4e8a2 | |||
| cf29d0c45a | |||
| 4aebd226f1 | |||
| 528bf83a67 | |||
| 05ca944828 | |||
| f2209c4815 | |||
| ded2aee989 | |||
| 37ecb81444 | |||
| 343e5a976c | |||
| 8817b5d586 | |||
| fb1e7efd30 | |||
| 3a7100d8b0 | |||
| 50d3d827e8 | |||
| 79813f5d8a | |||
| 4c0ee13d97 | |||
| a95fddca56 | |||
| f110ce9d46 | |||
| 68c4a056b7 | |||
| 57a67ffdf3 | |||
| 5977ebf23d | |||
| ee2bcea42b | |||
| 2f2fcfdf13 | |||
| 070f97dd6f | |||
| 2a46015d0d | |||
| 5329d96e3b | |||
| 0c01aa799d | |||
| 75c15fce43 | |||
| 3b20d4087c | |||
| 2dbafec633 | |||
| 2b79d18a93 | |||
| fa747763b8 | |||
| 0d71e2ad24 | |||
| 64d4edc175 | |||
| 222c894266 | |||
| 4a3aee7691 | |||
| fd8dac97d0 | |||
| bc5a78d73e | |||
| f43b945756 | |||
| 183342cb8c | |||
| 3728c37dce | |||
| f61f1411cb | |||
| d2668e36eb | |||
| 5678ddcfd9 | |||
| 06d5178614 | |||
| a983696153 | |||
| 6908964df5 | |||
| 159587f545 | |||
| c02b49644e | |||
| 313fa14c56 | |||
| 052be7d1c9 | |||
| aef10f706c | |||
| 35d9650626 | |||
| 140cb012df | |||
| e1c9720eeb |
@@ -43,9 +43,40 @@ jobs:
|
||||
- name: 🧹 Maintenance (High Density Cleanup)
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Purging old build layers and dangling images..."
|
||||
docker image prune -f
|
||||
docker builder prune -f --filter "until=24h"
|
||||
echo "=== System Disk Usage ==="
|
||||
df -h || true
|
||||
echo "=== Tool Cache Usage ==="
|
||||
du -sh /opt/hostedtoolcache/* || true
|
||||
du -sh /opt/hostedtoolcache/.[!.]* || true
|
||||
echo "Purging old tool caches..."
|
||||
rm -rf /opt/hostedtoolcache/* || true
|
||||
rm -rf /opt/hostedtoolcache/.[!.]* || true
|
||||
echo "=== Host Disk Usage ==="
|
||||
docker run --rm -v /:/host-root alpine df -h || true
|
||||
echo "=== Host Inode Usage ==="
|
||||
docker run --rm -v /:/host-root alpine df -i || true
|
||||
echo "=== Running Host Containers ==="
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || true
|
||||
echo "=== Host Docker Info ==="
|
||||
docker info || true
|
||||
echo "Pruning host Buildkit builder caches aggressively..."
|
||||
for container in $(docker ps --filter "name=buildx_buildkit_builder" --format "{{.Names}}"); do
|
||||
echo "Pruning Buildkit inside container: $container"
|
||||
docker exec -i "$container" buildctl prune --all || true
|
||||
done
|
||||
echo "Removing buildkit builder containers to clear overlay storage..."
|
||||
containers=$(docker ps -a --filter "name=buildx_buildkit_builder" -q)
|
||||
if [ -n "$containers" ]; then
|
||||
docker rm -f $containers || true
|
||||
fi
|
||||
echo "=== Truncating host Docker container log files ==="
|
||||
docker run --rm -v /:/host-root alpine sh -c "truncate -s 0 /host-root/mnt/HC_Volume_104796416/docker/containers/*/*.log" || true
|
||||
echo "Purging old build layers, unused volumes and images..."
|
||||
docker builder prune -a -f || true
|
||||
docker volume prune -f || true
|
||||
docker system prune -a -f --volumes || true
|
||||
echo "=== System Disk Usage After Prune ==="
|
||||
df -h || true
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -189,27 +220,33 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: bash scripts/registry-auth.sh
|
||||
|
||||
- name: 🐳 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: 🔐 Registry Login
|
||||
run: echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
- name: 🏗️ Build and Push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
provenance: false
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
|
||||
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
|
||||
NEXT_PUBLIC_APP_VERSION=${{ needs.prepare.outputs.image_tag }}
|
||||
UMAMI_WEBSITE_ID=${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
|
||||
UMAMI_API_ENDPOINT=${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
||||
NPM_TOKEN=${{ steps.auth.outputs.working_token }}
|
||||
tags: registry.infra.mintel.me/mintel/e-tib.com:${{ needs.prepare.outputs.image_tag }}
|
||||
secrets: |
|
||||
"NPM_TOKEN=${{ steps.auth.outputs.working_token }}"
|
||||
- name: 🏗️ Build and Push (Native Docker Buildkit)
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
NEXT_PUBLIC_TARGET: ${{ needs.prepare.outputs.target }}
|
||||
NEXT_PUBLIC_APP_VERSION: ${{ needs.prepare.outputs.image_tag }}
|
||||
UMAMI_WEBSITE_ID: ${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
|
||||
UMAMI_API_ENDPOINT: ${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
||||
NPM_TOKEN: ${{ steps.auth.outputs.working_token }}
|
||||
NPM_DOMAIN: ${{ steps.auth.outputs.working_domain }}
|
||||
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
|
||||
run: |
|
||||
echo "$NPM_TOKEN" > npm_token.txt
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--secret id=NPM_TOKEN,src=npm_token.txt \
|
||||
--build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
||||
--build-arg NEXT_PUBLIC_TARGET="$NEXT_PUBLIC_TARGET" \
|
||||
--build-arg NEXT_PUBLIC_APP_VERSION="$IMAGE_TAG" \
|
||||
--build-arg UMAMI_WEBSITE_ID="$UMAMI_WEBSITE_ID" \
|
||||
--build-arg UMAMI_API_ENDPOINT="$UMAMI_API_ENDPOINT" \
|
||||
--build-arg NPM_TOKEN="$NPM_TOKEN" \
|
||||
--build-arg NPM_DOMAIN="$NPM_DOMAIN" \
|
||||
-t registry.infra.mintel.me/mintel/e-tib.com:"$IMAGE_TAG" \
|
||||
.
|
||||
docker push registry.infra.mintel.me/mintel/e-tib.com:"$IMAGE_TAG"
|
||||
rm -f npm_token.txt
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# JOB 4: Deploy
|
||||
@@ -269,6 +306,19 @@ jobs:
|
||||
|
||||
GATEKEEPER_ORIGIN="${NEXT_PUBLIC_BASE_URL}/gatekeeper"
|
||||
|
||||
if [[ "$TARGET" == "production" ]]; then
|
||||
INTERNAL_SUBNET="10.199.10.0/24"
|
||||
elif [[ "$TARGET" == "testing" ]]; then
|
||||
INTERNAL_SUBNET="10.199.11.0/24"
|
||||
elif [[ "$TARGET" == "staging" ]]; then
|
||||
INTERNAL_SUBNET="10.199.12.0/24"
|
||||
else
|
||||
HEX_SHA="${{ needs.prepare.outputs.short_sha }}"
|
||||
DEC_SHA=$((16#$HEX_SHA))
|
||||
SUBNET_NUM=$(( 13 + (DEC_SHA % 230) ))
|
||||
INTERNAL_SUBNET="10.199.${SUBNET_NUM}.0/24"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "# Generated by CI - $TARGET"
|
||||
echo "IMAGE_TAG=$IMAGE_TAG"
|
||||
@@ -305,6 +355,7 @@ jobs:
|
||||
echo "COMPOSE_PROFILES=$COMPOSE_PROFILES"
|
||||
echo "AUTH_MIDDLEWARE=$AUTH_MIDDLEWARE"
|
||||
echo "AUTH_MIDDLEWARE_UNPROTECTED=$AUTH_MIDDLEWARE_UNPROTECTED"
|
||||
echo "INTERNAL_SUBNET=$INTERNAL_SUBNET"
|
||||
} > .env.deploy
|
||||
|
||||
- name: 🚀 SSH Deploy
|
||||
@@ -332,6 +383,7 @@ jobs:
|
||||
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
|
||||
scp docker-compose.yml root@alpha.mintel.me:$SITE_DIR/docker-compose.yml
|
||||
|
||||
ssh root@alpha.mintel.me "docker system prune -f"
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && echo '${{ secrets.REGISTRY_PASS }}' | docker login registry.infra.mintel.me -u '${{ secrets.REGISTRY_USER }}' --password-stdin"
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' pull"
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' up -d --remove-orphans"
|
||||
|
||||
1
.turbo-test/85340b254e0f10f1-manifest.json
Normal file
1
.turbo-test/85340b254e0f10f1-manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-lint.log":{"size":10746,"mtime_nanos":1779098006288513580,"mode":420,"is_dir":false}},"order":[".turbo/turbo-lint.log"]}
|
||||
1
.turbo-test/85340b254e0f10f1-meta.json
Normal file
1
.turbo-test/85340b254e0f10f1-meta.json
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"85340b254e0f10f1","duration":6595,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"9cc3e16c40a10f42f7a963333e800e1384f047b1f387b18b5f37769c34f36609"}
|
||||
BIN
.turbo-test/85340b254e0f10f1.tar.zst
Normal file
BIN
.turbo-test/85340b254e0f10f1.tar.zst
Normal file
Binary file not shown.
1
.turbo/0864e4a10f76dcaf-manifest.json
Normal file
1
.turbo/0864e4a10f76dcaf-manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-lint.log":{"size":11972,"mtime_nanos":1779970374061729177,"mode":420,"is_dir":false}},"order":[".turbo/turbo-lint.log"]}
|
||||
1
.turbo/0864e4a10f76dcaf-meta.json
Normal file
1
.turbo/0864e4a10f76dcaf-meta.json
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"0864e4a10f76dcaf","duration":6769,"sha":"68c4a056b70ff215c9ef3252506875dfd3ba4eee","dirty_hash":"29197232f806b237725188c5bfe8d43cb2f12f8bb9e0df915eecf0896e2c04a3"}
|
||||
BIN
.turbo/0864e4a10f76dcaf.tar.zst
Normal file
BIN
.turbo/0864e4a10f76dcaf.tar.zst
Normal file
Binary file not shown.
1
.turbo/7668a73054ba889d-manifest.json
Normal file
1
.turbo/7668a73054ba889d-manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-typecheck.log":{"size":85,"mtime_nanos":1779970373499305257,"mode":420,"is_dir":false}},"order":[".turbo/turbo-typecheck.log"]}
|
||||
1
.turbo/7668a73054ba889d-meta.json
Normal file
1
.turbo/7668a73054ba889d-meta.json
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"7668a73054ba889d","duration":6206,"sha":"68c4a056b70ff215c9ef3252506875dfd3ba4eee","dirty_hash":"29197232f806b237725188c5bfe8d43cb2f12f8bb9e0df915eecf0896e2c04a3"}
|
||||
BIN
.turbo/7668a73054ba889d.tar.zst
Normal file
BIN
.turbo/7668a73054ba889d.tar.zst
Normal file
Binary file not shown.
1
.turbo/9c3fa4d9e1993aff-manifest.json
Normal file
1
.turbo/9c3fa4d9e1993aff-manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-test.log":{"size":2150,"mtime_nanos":1779970370282819916,"mode":420,"is_dir":false}},"order":[".turbo/turbo-test.log"]}
|
||||
1
.turbo/9c3fa4d9e1993aff-meta.json
Normal file
1
.turbo/9c3fa4d9e1993aff-meta.json
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"9c3fa4d9e1993aff","duration":2989,"sha":"68c4a056b70ff215c9ef3252506875dfd3ba4eee","dirty_hash":"29197232f806b237725188c5bfe8d43cb2f12f8bb9e0df915eecf0896e2c04a3"}
|
||||
BIN
.turbo/9c3fa4d9e1993aff.tar.zst
Normal file
BIN
.turbo/9c3fa4d9e1993aff.tar.zst
Normal file
Binary file not shown.
1
.turbo/cache/32905076da5f4894-manifest.json
vendored
Normal file
1
.turbo/cache/32905076da5f4894-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-typecheck.log":{"size":92,"mtime_nanos":1779097674383861303,"mode":420,"is_dir":false}},"order":[".turbo/turbo-typecheck.log"]}
|
||||
1
.turbo/cache/32905076da5f4894-meta.json
vendored
Normal file
1
.turbo/cache/32905076da5f4894-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"32905076da5f4894","duration":4492,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"58f8ebc92a0452cbb0a09c5ebe800385fcc42ff82fee098bb7d6c8828bc526f0"}
|
||||
BIN
.turbo/cache/32905076da5f4894.tar.zst
vendored
Normal file
BIN
.turbo/cache/32905076da5f4894.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/7e2629942c881f80-manifest.json
vendored
Normal file
1
.turbo/cache/7e2629942c881f80-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-test.log":{"size":818,"mtime_nanos":1779097671747078359,"mode":420,"is_dir":false}},"order":[".turbo/turbo-test.log"]}
|
||||
1
.turbo/cache/7e2629942c881f80-meta.json
vendored
Normal file
1
.turbo/cache/7e2629942c881f80-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"7e2629942c881f80","duration":1855,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"58f8ebc92a0452cbb0a09c5ebe800385fcc42ff82fee098bb7d6c8828bc526f0"}
|
||||
BIN
.turbo/cache/7e2629942c881f80.tar.zst
vendored
Normal file
BIN
.turbo/cache/7e2629942c881f80.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/85340b254e0f10f1-manifest.json
vendored
Normal file
1
.turbo/cache/85340b254e0f10f1-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-lint.log":{"size":10746,"mtime_nanos":1779097675143794164,"mode":420,"is_dir":false}},"order":[".turbo/turbo-lint.log"]}
|
||||
1
.turbo/cache/85340b254e0f10f1-meta.json
vendored
Normal file
1
.turbo/cache/85340b254e0f10f1-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"85340b254e0f10f1","duration":5252,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"58f8ebc92a0452cbb0a09c5ebe800385fcc42ff82fee098bb7d6c8828bc526f0"}
|
||||
BIN
.turbo/cache/85340b254e0f10f1.tar.zst
vendored
Normal file
BIN
.turbo/cache/85340b254e0f10f1.tar.zst
vendored
Normal file
Binary file not shown.
129
.turbo/turbo-lint.log
Normal file
129
.turbo/turbo-lint.log
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
> e-tib-nextjs@2.2.12 lint /Volumes/Alpha SSD/Coding/e-tib.com
|
||||
> eslint .
|
||||
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/[slug]/page.tsx
|
||||
1:20 warning 'redirect' is defined but never used @typescript-eslint/no-unused-vars
|
||||
75:24 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/blog/[slug]/page.tsx
|
||||
1:20 warning 'redirect' is defined but never used @typescript-eslint/no-unused-vars
|
||||
39:24 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/error.tsx
|
||||
7:30 warning 'AlertTriangle' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/not-found.tsx
|
||||
6:10 warning 'Terminal' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/referenzen/[slug]/page.tsx
|
||||
92:26 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/referenzen/page.tsx
|
||||
1:21 warning 'Heading' is defined but never used @typescript-eslint/no-unused-vars
|
||||
1:30 warning 'Badge' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/JsonLd.tsx
|
||||
1:30 warning 'Graph' is defined but never used @typescript-eslint/no-unused-vars
|
||||
43:31 warning '_' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
48:29 warning '_' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CertificatesBlock.tsx
|
||||
6:10 warning 'Button' is defined but never used @typescript-eslint/no-unused-vars
|
||||
9:10 warning 'FileText' is defined but never used @typescript-eslint/no-unused-vars
|
||||
102:5 warning Error: Calling setState synchronously within an effect can trigger cascading renders
|
||||
|
||||
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
|
||||
* Update external systems with the latest state from React.
|
||||
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
|
||||
|
||||
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CertificatesBlock.tsx:102:5
|
||||
100 |
|
||||
101 | useEffect(() => {
|
||||
> 102 | setIsMounted(true);
|
||||
| ^^^^^^^^^^^^ Avoid calling setState() directly within an effect
|
||||
103 | }, []);
|
||||
104 |
|
||||
105 | const badgeText = badge || 'Zertifizierungen & Nachweise'; react-hooks/set-state-in-effect
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CompetenceBentoGrid.tsx
|
||||
53:5 warning Error: Calling setState synchronously within an effect can trigger cascading renders
|
||||
|
||||
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
|
||||
* Update external systems with the latest state from React.
|
||||
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
|
||||
|
||||
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CompetenceBentoGrid.tsx:53:5
|
||||
51 |
|
||||
52 | useEffect(() => {
|
||||
> 53 | setIsMounted(true);
|
||||
| ^^^^^^^^^^^^ Avoid calling setState() directly within an effect
|
||||
54 | }, []);
|
||||
55 |
|
||||
56 | const badge = props.badge || data?.badge || 'Leistungsspektrum'; react-hooks/set-state-in-effect
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/HeroVideo.tsx
|
||||
5:18 warning 'AnimatePresence' is defined but never used @typescript-eslint/no-unused-vars
|
||||
6:10 warning 'useState' is defined but never used @typescript-eslint/no-unused-vars
|
||||
6:20 warning 'useEffect' is defined but never used @typescript-eslint/no-unused-vars
|
||||
45:12 warning 'e' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/InteractiveGermanyMap.tsx
|
||||
5:18 warning 'Factory' is defined but never used @typescript-eslint/no-unused-vars
|
||||
5:27 warning 'Zap' is defined but never used @typescript-eslint/no-unused-vars
|
||||
53:9 warning 'hq' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
54:9 warning 'branch' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
55:9 warning 'projects' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
56:9 warning 'minorNodes' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/ReferencesSlider.test.tsx
|
||||
5:10 warning 'NextIntlClientProvider' is defined but never used @typescript-eslint/no-unused-vars
|
||||
21:12 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/TeamGridBlock.tsx
|
||||
20:39 warning 'title' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
|
||||
20:46 warning 'department' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
|
||||
20:58 warning 'showContact' is assigned a value but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/decorations/CorporateBackground.tsx
|
||||
36:13 warning 'distance' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/home/Hero.tsx
|
||||
9:39 warning 'useCallback' is defined but never used @typescript-eslint/no-unused-vars
|
||||
77:6 warning React Hook useEffect has missing dependencies: 'HERO_PLACEHOLDERS' and 't'. Either include them or remove the dependency array react-hooks/exhaustive-deps
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/home/MeetTheTeam.tsx
|
||||
8:9 warning 'teamT' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/Footer.tsx
|
||||
4:35 warning 'Accessibility' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/InitialLoader.tsx
|
||||
5:8 warning 'Image' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/PageTransitionShutter.tsx
|
||||
6:8 warning 'Image' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/search/AIOrb.tsx
|
||||
153:13 warning 'breathe' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
212:15 warning 'targetExcite' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/search/AISearchResults.tsx
|
||||
100:6 warning React Hook useEffect has missing dependencies: 'handleSearch' and 'initialQuery'. Either include them or remove the dependency array react-hooks/exhaustive-deps
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/AnimatedCounter.tsx
|
||||
3:28 warning 'useState' is defined but never used @typescript-eslint/no-unused-vars
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/lib/blog.ts
|
||||
6:7 warning 'BLOG_DIR' is assigned a value but never used @typescript-eslint/no-unused-vars
|
||||
94:7 warning 'next' is never reassigned. Use 'const' instead prefer-const
|
||||
95:7 warning 'prev' is never reassigned. Use 'const' instead prefer-const
|
||||
|
||||
✖ 45 problems (0 errors, 45 warnings)
|
||||
0 errors and 2 warnings potentially fixable with the `--fix` option.
|
||||
|
||||
31
.turbo/turbo-test.log
Normal file
31
.turbo/turbo-test.log
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
> e-tib-nextjs@2.2.12 test /Volumes/Alpha SSD/Coding/e-tib.com
|
||||
> vitest run --passWithNoTests
|
||||
|
||||
|
||||
[1m[46m RUN [49m[22m [36mv4.1.2 [39m[90m/Volumes/Alpha SSD/Coding/e-tib.com[39m
|
||||
|
||||
[32m✓[39m tests/mdx-links.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 4[2mms[22m[39m
|
||||
[32m✓[39m lib/mail/mailer.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 2[2mms[22m[39m
|
||||
[32m✓[39m lib/env.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
||||
[90mstderr[2m | components/layout/LanguageSwitcher.test.tsx[2m > [22m[2mLanguageSwitcher TDD[2m > [22m[2mtranslates /en/trade-fairs to /de/messen when switching to German
|
||||
[22m[39mReact does not recognize the `transitionMessage` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `transitionmessage` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
|
||||
|
||||
[32m✓[39m components/layout/LanguageSwitcher.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 21[2mms[22m[39m
|
||||
[32m✓[39m components/search/AISearchResults.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m 26[2mms[22m[39m
|
||||
[90mstderr[2m | components/blocks/ReferencesSlider.test.tsx[2m > [22m[2mReferencesSlider TDD[2m > [22m[2mrenders reference tiles correctly
|
||||
[22m[39mReceived `true` for a non-boolean attribute `fill`.
|
||||
|
||||
If you want to write it to the DOM, pass a string instead: fill="true" or fill={value.toString()}.
|
||||
|
||||
[32m✓[39m components/blocks/ReferencesSlider.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m 40[2mms[22m[39m
|
||||
[90mstdout[2m | app/[locale]/referenzen/[slug]/page.test.tsx
|
||||
[22m[39m[Config] Initializing Toggles: { feedbackEnabled: [33mfalse[39m }
|
||||
|
||||
[32m✓[39m app/[locale]/referenzen/[slug]/page.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m 6[2mms[22m[39m
|
||||
|
||||
[2m Test Files [22m [1m[32m7 passed[39m[22m[90m (7)[39m
|
||||
[2m Tests [22m [1m[32m19 passed[39m[22m[90m (19)[39m
|
||||
[2m Start at [22m 14:12:48
|
||||
[2m Duration [22m 1.80s[2m (transform 863ms, setup 0ms, import 2.94s, tests 105ms, environment 3.18s)[22m
|
||||
|
||||
4
.turbo/turbo-typecheck.log
Normal file
4
.turbo/turbo-typecheck.log
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
> e-tib-nextjs@2.2.12 typecheck /Volumes/Alpha SSD/Coding/e-tib.com
|
||||
> tsc --noEmit
|
||||
|
||||
@@ -20,13 +20,15 @@ ENV CI=true
|
||||
COPY pnpm-lock.yaml package.json .npmrc* ./
|
||||
COPY patches* ./patches/
|
||||
|
||||
ARG NPM_DOMAIN=git.infra.mintel.me
|
||||
|
||||
# Configure private registry and install dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
--mount=type=secret,id=NPM_TOKEN \
|
||||
if [ -f /run/secrets/NPM_TOKEN ]; then \
|
||||
export NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN); \
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc; \
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${NPM_TOKEN}" >> .npmrc; \
|
||||
echo "@mintel:registry=https://${NPM_DOMAIN}/api/packages/mmintel/npm" > .npmrc; \
|
||||
echo "//${NPM_DOMAIN}/api/packages/mmintel/npm/:_authToken=${NPM_TOKEN}" >> .npmrc; \
|
||||
fi && \
|
||||
pnpm install --no-frozen-lockfile && \
|
||||
rm -f .npmrc
|
||||
|
||||
@@ -5,8 +5,8 @@ RUN apk add --no-cache libc6-compat python3 make g++ curl
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Enable corepack for pnpm
|
||||
RUN corepack enable
|
||||
# Install pnpm v10 explicitly (corepack defaults to v11 which requires Node 22)
|
||||
RUN npm install -g pnpm@10.18.3
|
||||
|
||||
# Pre-set the pnpm store directory
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SITE_URL } from '@/lib/schema';
|
||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { getAllReferences } from '@/lib/references';
|
||||
import { defaultLocations } from '@/lib/map-data';
|
||||
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
||||
|
||||
// Import components used in MDX
|
||||
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
|
||||
@@ -26,8 +26,12 @@ import { FaqBlock } from '@/components/blocks/FaqBlock';
|
||||
import { CertificatesBlock } from '@/components/blocks/CertificatesBlock';
|
||||
import { HeroSection } from '@/components/blocks/HeroSection';
|
||||
import { TeamGrid } from '@/components/blocks/TeamGrid';
|
||||
import { DeepDrillAnimation } from '@/components/blocks/DeepDrillAnimation';
|
||||
import { DataGridPulse } from '@/components/blocks/DataGridPulse';
|
||||
import { GrowthChart } from '@/components/blocks/GrowthChart';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { AnimatedCounter } from '@/components/ui';
|
||||
|
||||
|
||||
const mdxComponents = {
|
||||
@@ -47,6 +51,10 @@ const mdxComponents = {
|
||||
TeamGrid,
|
||||
JsonLd,
|
||||
Button,
|
||||
AnimatedCounter,
|
||||
GrowthChart,
|
||||
DeepDrillAnimation,
|
||||
DataGridPulse,
|
||||
// Standard HTML element mapping for consistent E-TIB typography
|
||||
h1: (props: any) => <Heading level={1} size="2" className="hidden" {...props} />, // Hidden because Hero handles H1
|
||||
h2: (props: any) => <Heading level={2} size="3" className="mt-16 mb-6 border-b border-neutral-100 pb-4" {...props} />,
|
||||
@@ -145,7 +153,8 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
||||
|
||||
// Fetch references to enrich the map if it's used
|
||||
const references = await getAllReferences(locale);
|
||||
const enrichedLocations = defaultLocations.map(loc => {
|
||||
const allLocations = [...defaultLocations, ...minorLocations];
|
||||
const enrichedLocations = allLocations.map(loc => {
|
||||
if (loc.type === 'project') {
|
||||
const ref = references.find(r => r.slug === loc.id);
|
||||
if (ref) {
|
||||
@@ -153,7 +162,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
||||
...loc,
|
||||
featuredImage: ref.frontmatter.featuredImage,
|
||||
details: [
|
||||
ref.frontmatter.client || 'Kunde',
|
||||
ref.frontmatter.client || t('client'),
|
||||
ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear().toString(),
|
||||
],
|
||||
};
|
||||
@@ -169,7 +178,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
||||
id: r.slug,
|
||||
slug: r.slug,
|
||||
title: r.frontmatter.title,
|
||||
category: r.frontmatter.category || 'Projekt',
|
||||
category: Array.isArray(r.frontmatter.category) ? r.frontmatter.category[0] : (r.frontmatter.category || 'Projekt'),
|
||||
image: r.frontmatter.featuredImage
|
||||
}))} />,
|
||||
};
|
||||
|
||||
@@ -100,10 +100,6 @@ export default async function Layout(props: {
|
||||
label: safeLocale === 'de' ? 'Referenzen' : 'References',
|
||||
url: `/${safeLocale}/referenzen`
|
||||
},
|
||||
{
|
||||
label: safeLocale === 'de' ? 'Karriere' : 'Careers',
|
||||
url: `/${safeLocale}/${await mapFileSlugToTranslated('karriere', safeLocale)}`
|
||||
},
|
||||
{
|
||||
label: safeLocale === 'de' ? 'Messen' : 'Events',
|
||||
url: `/${safeLocale}/${await mapFileSlugToTranslated('messen', safeLocale)}`
|
||||
@@ -127,6 +123,13 @@ export default async function Layout(props: {
|
||||
'Error',
|
||||
'StandardPage',
|
||||
'Brochure',
|
||||
'JobListingBlock',
|
||||
'CallToAction',
|
||||
'InteractiveGermanyMap',
|
||||
'ReferencesSlider',
|
||||
'CompanyTimeline',
|
||||
'TeamGrid',
|
||||
'AISearch',
|
||||
];
|
||||
const clientMessages: Record<string, any> = {};
|
||||
for (const key of clientKeys) {
|
||||
|
||||
@@ -18,6 +18,9 @@ import JsonLd from '@/components/JsonLd';
|
||||
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Heading } from '@/components/ui/Heading';
|
||||
import { AnimatedCounter } from '@/components/ui';
|
||||
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
||||
import { ScaleOfImpact } from '@/components/blocks/ScaleOfImpact';
|
||||
import { getAllReferences } from '@/lib/references';
|
||||
|
||||
const mdxComponents = (references: any[]) => ({
|
||||
@@ -31,6 +34,9 @@ const mdxComponents = (references: any[]) => ({
|
||||
JsonLd,
|
||||
Button,
|
||||
Heading,
|
||||
AnimatedCounter,
|
||||
InteractiveGermanyMap,
|
||||
ScaleOfImpact,
|
||||
});
|
||||
|
||||
export async function generateMetadata(props: any): Promise<Metadata> {
|
||||
|
||||
38
app/[locale]/referenzen/[slug]/page.test.tsx
Normal file
38
app/[locale]/referenzen/[slug]/page.test.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import ReferenceDetail from './page';
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
notFound: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock next-intl/server
|
||||
vi.mock('next-intl/server', () => ({
|
||||
setRequestLocale: vi.fn(),
|
||||
getTranslations: vi.fn().mockResolvedValue(() => 'translated text'),
|
||||
}));
|
||||
|
||||
// Mock @/lib/references
|
||||
vi.mock('@/lib/references', () => ({
|
||||
getReferenceBySlug: vi.fn().mockResolvedValue({
|
||||
slug: 'test-slug',
|
||||
frontmatter: {
|
||||
title: 'Test Title',
|
||||
location: 'Test Location',
|
||||
category: 'Test Category',
|
||||
},
|
||||
content: 'Test content',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ReferenceDetail Page TDD', () => {
|
||||
it('renders reference detail correctly', async () => {
|
||||
const props = {
|
||||
params: Promise.resolve({ locale: 'de', slug: 'test-slug' })
|
||||
};
|
||||
|
||||
// We expect the execution to fail if getTranslations is not defined/imported inside page.tsx
|
||||
const component = await ReferenceDetail(props);
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Container, Badge, Heading } from '@/components/ui';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import { setRequestLocale, getTranslations } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import { getReferenceBySlug, getAllReferences } from '@/lib/references';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
@@ -57,6 +57,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
||||
|
||||
setRequestLocale(locale);
|
||||
const reference = await getReferenceBySlug(slug, locale);
|
||||
const t = await getTranslations('ReferenceDetail');
|
||||
|
||||
if (!reference) {
|
||||
notFound();
|
||||
@@ -119,12 +120,12 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
||||
eventProperties={{ location: 'reference_back_btn' }}
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
Zurück zur Übersicht
|
||||
{t('backToOverview')}
|
||||
</TrackedLink>
|
||||
</div>
|
||||
<div className="max-w-4xl">
|
||||
<Badge variant="accent" className="mb-4 md:mb-6">
|
||||
Projektreferenz
|
||||
{t('projectReference')}
|
||||
</Badge>
|
||||
<Heading level={1} variant="white" className="mb-8 leading-tight">
|
||||
{reference.frontmatter.title}
|
||||
@@ -134,21 +135,21 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
||||
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
||||
<MapPin className="w-8 h-8 text-primary shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Ort</p>
|
||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('location')}</p>
|
||||
<p className="font-semibold text-lg">{reference.frontmatter.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
||||
<Briefcase className="w-8 h-8 text-primary shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Auftraggeber</p>
|
||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('client')}</p>
|
||||
<p className="font-semibold text-lg">{reference.frontmatter.client}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
||||
<Calendar className="w-8 h-8 text-primary shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Zeitraum</p>
|
||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('period')}</p>
|
||||
<p className="font-semibold text-lg">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,7 +161,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
||||
{/* Main Content Area */}
|
||||
<Container className="py-16 md:py-24">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-neutral-dark mb-8">Leistungsumfang & Projektbeschreibung</h2>
|
||||
<h2 className="text-3xl font-bold text-neutral-dark mb-8">{t('scopeTitle')}</h2>
|
||||
|
||||
<div className="w-full">
|
||||
<MDXRemote source={reference.content} components={mdxComponents} />
|
||||
@@ -173,7 +174,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
||||
eventProperties={{ location: 'reference_bottom_back_btn' }}
|
||||
>
|
||||
<span className="relative z-10 flex items-center justify-center gap-2">
|
||||
Alle Referenzen ansehen
|
||||
{t('viewAll')}
|
||||
</span>
|
||||
<ButtonOverlay variant="primary" />
|
||||
</TrackedLink>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import Image from 'next/image';
|
||||
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
||||
import { defaultLocations } from '@/lib/map-data';
|
||||
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
@@ -34,6 +34,17 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
};
|
||||
}
|
||||
|
||||
function cleanLocation(location?: string): string {
|
||||
if (!location) return '';
|
||||
// Remove 5 digit postal codes
|
||||
let cleaned = location.replace(/\b\d{5}\b/g, '');
|
||||
// Clean up multiple spaces, slashes and commas
|
||||
cleaned = cleaned.replace(/\s*,\s*/g, ', ').replace(/\s*\/\s*/g, ' / ').trim();
|
||||
// Remove leading/trailing slash/comma residues
|
||||
cleaned = cleaned.replace(/^[,/\s]+|[,/\s]+$/g, '').trim();
|
||||
return cleaned || location;
|
||||
}
|
||||
|
||||
export default async function ReferenzenOverview(props: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
@@ -41,8 +52,8 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
|
||||
const references = await getAllReferences(locale);
|
||||
|
||||
// Enrich the static map locations with dynamic project data (images, details)
|
||||
const enrichedLocations = defaultLocations.map(loc => {
|
||||
const allLocations = [...defaultLocations, ...minorLocations];
|
||||
const enrichedLocations = allLocations.map(loc => {
|
||||
if (loc.type === 'project') {
|
||||
const ref = references.find(r => r.slug === loc.id);
|
||||
if (ref) {
|
||||
@@ -64,9 +75,9 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
{/* Map Hero Section */}
|
||||
<InteractiveGermanyMap
|
||||
isHero={true}
|
||||
badge="Unsere Referenzen"
|
||||
title={<>Erfolgreich umgesetzte<br/>Projekte.</>}
|
||||
description="Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben. Entdecken Sie unsere Standorte."
|
||||
badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'}
|
||||
title={locale === 'en' ? 'Successfully realized projects.' : 'Erfolgreich umgesetzte Projekte.'}
|
||||
description={locale === 'en' ? 'From broadband expansion to complex 110kV lines: A selection of our nationwide projects where we have created infrastructure for the future. Discover our locations.' : 'Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben. Entdecken Sie unsere Standorte.'}
|
||||
locations={enrichedLocations}
|
||||
/>
|
||||
|
||||
@@ -104,7 +115,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
<div className="absolute top-4 left-4 z-10">
|
||||
<span className="bg-white/95 backdrop-blur-md text-neutral-dark px-3 py-1.5 rounded-sm text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 shadow-sm border border-neutral-200">
|
||||
<MapPin className="w-3 h-3 text-primary" />
|
||||
{ref.frontmatter.location}
|
||||
{cleanLocation(ref.frontmatter.location)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,14 +129,14 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
{/* Technical Meta Data */}
|
||||
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 pr-16 md:pr-20 relative z-10">
|
||||
<div className="min-w-0">
|
||||
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">Kunde</span>
|
||||
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">{t('client')}</span>
|
||||
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
|
||||
<Briefcase className="w-3.5 h-3.5 text-primary shrink-0 mt-[2px]" />
|
||||
<span className="line-clamp-2">{ref.frontmatter.client}</span>
|
||||
<span className="line-clamp-2">{ref.frontmatter.client || t('client')}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">Zeitraum</span>
|
||||
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">{t('period')}</span>
|
||||
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
|
||||
<Calendar className="w-3.5 h-3.5 text-primary shrink-0 mt-[2px]" />
|
||||
<span className="line-clamp-2">{ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear()}</span>
|
||||
@@ -147,8 +158,8 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
|
||||
<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">Ihr nächstes Projekt?</h3>
|
||||
<p className="text-lg text-white/70 mb-8">Lassen Sie uns gemeinsam herausragende Infrastruktur bauen.</p>
|
||||
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('nextProjectTitle')}</h3>
|
||||
<p className="text-lg text-white/70 mb-8">{t('nextProjectDesc')}</p>
|
||||
<TrackedLink
|
||||
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
|
||||
className={getButtonClasses('accent', 'lg')}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function LeafletMap({ locations }: LeafletMapProps) {
|
||||
const firstLoc = locations[0];
|
||||
const map = L.map(mapRef.current, {
|
||||
center: [firstLoc.lat, firstLoc.lng],
|
||||
zoom: 6, // Zoom out to see both or more
|
||||
zoom: locations.length === 1 ? 16 : 6,
|
||||
scrollWheelZoom: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export interface CallToActionProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
text?: string; // Alias for description
|
||||
ctaLabel?: string;
|
||||
buttonText?: string; // Alias for ctaLabel
|
||||
ctaHref?: string;
|
||||
buttonLink?: string; // Alias for ctaHref
|
||||
theme?: 'light' | 'dark';
|
||||
}
|
||||
|
||||
@@ -32,14 +36,15 @@ const itemVariants = {
|
||||
},
|
||||
};
|
||||
|
||||
export const CallToAction: React.FC<CallToActionProps> = ({
|
||||
title = 'Bereit für Ihr Projekt?',
|
||||
description = 'Wir suchen stets nach neuen Herausforderungen und starken Partnern. Kontaktieren Sie uns für eine unverbindliche Beratung zu Ihrem Vorhaben.',
|
||||
ctaLabel = 'Jetzt Kontakt aufnehmen',
|
||||
ctaHref = '/de/kontakt',
|
||||
theme = 'light'
|
||||
}) => {
|
||||
export const CallToAction: React.FC<CallToActionProps> = (props) => {
|
||||
const { theme = 'light' } = props;
|
||||
const isDark = theme === 'dark';
|
||||
const t = useTranslations('CallToAction');
|
||||
|
||||
const title = props.title || t('title');
|
||||
const description = props.description || props.text || t('description');
|
||||
const ctaLabel = props.ctaLabel || props.buttonText || t('ctaLabel');
|
||||
const ctaHref = props.ctaHref || props.buttonLink || t('ctaHref');
|
||||
|
||||
return (
|
||||
<div className={`py-24 text-center ${isDark ? 'bg-primary-dark text-white' : 'bg-neutral-50 border-t border-neutral-100'}`}>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { LogoArcs } from '@/components/ui/LogoArcs';
|
||||
interface Certificate {
|
||||
title: string;
|
||||
description: string;
|
||||
pdfUrl: string;
|
||||
pdfUrl?: string;
|
||||
type: 'iso' | 'tax' | 'general';
|
||||
date?: string;
|
||||
}
|
||||
@@ -29,45 +29,70 @@ const defaultCertificates: Certificate[] = [
|
||||
{
|
||||
title: 'ISO 14001:2015',
|
||||
description: 'Umweltmanagementsystem',
|
||||
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 14001 Umweltmanagement.pdf',
|
||||
pdfUrl: '/assets/certificates/iso14001.pdf',
|
||||
type: 'iso',
|
||||
date: '14.12.2023',
|
||||
},
|
||||
{
|
||||
title: 'ISO 9001:2015',
|
||||
description: 'Qualitätsmanagementsystem',
|
||||
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 9001 Qualitätsmanagement.pdf',
|
||||
pdfUrl: '/assets/certificates/iso9001.pdf',
|
||||
type: 'iso',
|
||||
date: '14.12.2023',
|
||||
},
|
||||
{
|
||||
title: 'DIN EN ISO 45001:2018',
|
||||
description: 'Arbeits- und Gesundheitsschutz',
|
||||
pdfUrl: '/assets/certificates/Zertifizierung DIN EN ISO 45001 bis 05122028.pdf',
|
||||
pdfUrl: '/assets/certificates/iso45001.pdf',
|
||||
type: 'iso',
|
||||
date: '05.12.2025', // Assuming valid till
|
||||
date: '05.12.2025',
|
||||
},
|
||||
{
|
||||
title: 'Freistellungsbescheinigung',
|
||||
description: 'Nach § 48 b EStG',
|
||||
pdfUrl: '/assets/certificates/240209_Freistellungsbescheinigung § 48 b.pdf',
|
||||
pdfUrl: '/assets/certificates/freistellung.pdf',
|
||||
type: 'tax',
|
||||
date: '09.02.2024',
|
||||
},
|
||||
{
|
||||
title: 'Nachweis § 13b UStG',
|
||||
description: 'Steuerschuldnerschaft des Leistungsempfängers',
|
||||
pdfUrl: '/assets/certificates/240209_Nachweis § 13 b.pdf',
|
||||
pdfUrl: '/assets/certificates/nachweis13b.pdf',
|
||||
type: 'tax',
|
||||
date: '09.02.2024',
|
||||
},
|
||||
{
|
||||
title: 'Bescheinigung in Steuersachen',
|
||||
description: 'Zertifikat des Finanzamtes',
|
||||
pdfUrl: '/assets/certificates/250213_Bescheinigung in Steuersachen.pdf',
|
||||
pdfUrl: '/assets/certificates/bescheinigung.pdf',
|
||||
type: 'tax',
|
||||
date: '13.02.2025',
|
||||
},
|
||||
{
|
||||
title: 'Unbedenklichkeit IHK',
|
||||
description: 'Industrie- und Handelskammer',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'Unbedenklichkeit HWK',
|
||||
description: 'Handwerkskammer',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'Handelsregisterauszug',
|
||||
description: 'Amtsgericht Cottbus',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'Gewerbe-Anmeldung',
|
||||
description: 'Stadt Guben',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'Gewerbezentralregister',
|
||||
description: 'Auskunft aus dem Register',
|
||||
type: 'general',
|
||||
},
|
||||
];
|
||||
|
||||
export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {
|
||||
@@ -93,7 +118,7 @@ export function CertificatesBlock({ badge, title, description, certificates = de
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: 'easeOut' } },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: 'easeOut' as const } },
|
||||
};
|
||||
|
||||
if (!isMounted) {
|
||||
@@ -143,13 +168,17 @@ export function CertificatesBlock({ badge, title, description, certificates = de
|
||||
>
|
||||
{certificates.map((cert, index) => {
|
||||
const isIso = cert.type === 'iso';
|
||||
const Wrapper = cert.pdfUrl ? motion.a : motion.div;
|
||||
const wrapperProps = cert.pdfUrl ? {
|
||||
href: encodeURI(cert.pdfUrl),
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
} : {};
|
||||
|
||||
return (
|
||||
<motion.a
|
||||
<Wrapper
|
||||
key={index}
|
||||
href={cert.pdfUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{...wrapperProps as any}
|
||||
variants={itemVariants}
|
||||
className={`group relative rounded-2xl p-8 flex flex-col justify-between overflow-hidden transition-all duration-500 hover:shadow-xl hover:-translate-y-1 border ${
|
||||
isIso
|
||||
@@ -196,14 +225,22 @@ export function CertificatesBlock({ badge, title, description, certificates = de
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={`mt-auto inline-flex items-center text-sm font-bold uppercase tracking-wider group-hover:gap-3 transition-all ${
|
||||
<div className={`mt-auto inline-flex items-center text-sm font-bold uppercase tracking-wider transition-all ${
|
||||
cert.pdfUrl ? 'group-hover:gap-3' : ''
|
||||
} ${
|
||||
isIso ? 'text-primary-light' : 'text-primary'
|
||||
}`}>
|
||||
<span>Download PDF</span>
|
||||
<Download size={16} className="ml-2 group-hover:translate-y-0.5 transition-transform" />
|
||||
{cert.pdfUrl ? (
|
||||
<>
|
||||
<span>Download PDF</span>
|
||||
<Download size={16} className="ml-2 group-hover:translate-y-0.5 transition-transform" />
|
||||
</>
|
||||
) : (
|
||||
<span className="text-neutral-400">Nachweis liegt vor</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.a>
|
||||
</Wrapper>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as React from 'react';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { Building2, Compass, Layers, ArrowDownToLine, Wrench, Factory, Zap, MapPin, CheckCircle2 } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
const iconMap: Record<string, React.ElementType> = {
|
||||
Building2,
|
||||
@@ -16,37 +17,6 @@ const iconMap: Record<string, React.ElementType> = {
|
||||
CheckCircle2
|
||||
};
|
||||
|
||||
const defaultMilestones = [
|
||||
{
|
||||
date: '16.12.2015',
|
||||
year: '2015',
|
||||
title: 'Gründung E-TIB GmbH',
|
||||
desc: 'Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen.',
|
||||
iconName: 'Building2',
|
||||
},
|
||||
{
|
||||
date: '04.02.2019',
|
||||
year: '2019',
|
||||
title: 'Gründung E-TIB Ingenieurgesellschaft',
|
||||
desc: 'Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung.',
|
||||
iconName: 'Compass',
|
||||
},
|
||||
{
|
||||
date: '14.11.2019',
|
||||
year: '2019',
|
||||
title: 'Gründung E-TIB Verwaltung GmbH',
|
||||
desc: 'Zentrale Dienste, Erwerb, Vermietung, Verpachtung und Verwaltung von Immobilien und Maschinen.',
|
||||
iconName: 'Layers',
|
||||
},
|
||||
{
|
||||
date: '21.10.2025',
|
||||
year: '2025',
|
||||
title: 'Gründung E-TIB Bohrtechnik GmbH',
|
||||
desc: 'Spezialisierung auf präzise Horizontalspülbohrungen in allen Bodenklassen.',
|
||||
iconName: 'ArrowDownToLine',
|
||||
},
|
||||
];
|
||||
|
||||
interface Milestone {
|
||||
date: string;
|
||||
year: string;
|
||||
@@ -62,10 +32,45 @@ interface CompanyTimelineProps {
|
||||
}
|
||||
|
||||
export function CompanyTimeline({
|
||||
badge = 'Unsere Geschichte',
|
||||
title = 'Meilensteine der Entwicklung',
|
||||
milestones = defaultMilestones
|
||||
badge,
|
||||
title,
|
||||
milestones
|
||||
}: CompanyTimelineProps) {
|
||||
const t = useTranslations('CompanyTimeline');
|
||||
|
||||
const finalBadge = badge || t('badge');
|
||||
const finalTitle = title || t('title');
|
||||
|
||||
const finalMilestones = milestones || [
|
||||
{
|
||||
date: '16.12.2015',
|
||||
year: '2015',
|
||||
title: t('milestones.2015.title'),
|
||||
desc: t('milestones.2015.desc'),
|
||||
iconName: 'Building2',
|
||||
},
|
||||
{
|
||||
date: '04.02.2019',
|
||||
year: '2019',
|
||||
title: t('milestones.2019_ing.title'),
|
||||
desc: t('milestones.2019_ing.desc'),
|
||||
iconName: 'Compass',
|
||||
},
|
||||
{
|
||||
date: '14.11.2019',
|
||||
year: '2019',
|
||||
title: t('milestones.2019_verw.title'),
|
||||
desc: t('milestones.2019_verw.desc'),
|
||||
iconName: 'Layers',
|
||||
},
|
||||
{
|
||||
date: '21.10.2025',
|
||||
year: '2025',
|
||||
title: t('milestones.2025.title'),
|
||||
desc: t('milestones.2025.desc'),
|
||||
iconName: 'ArrowDownToLine',
|
||||
},
|
||||
];
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
@@ -84,10 +89,10 @@ export function CompanyTimeline({
|
||||
<div className="text-center mb-24">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-white border border-neutral-200 text-neutral-dark text-xs font-bold uppercase tracking-wider mb-6 shadow-sm">
|
||||
<span className="w-2 h-2 rounded-full bg-primary animate-pulse" />
|
||||
{badge}
|
||||
{finalBadge}
|
||||
</div>
|
||||
<h3 className="font-heading text-4xl md:text-5xl lg:text-6xl font-extrabold text-neutral-dark tracking-tight">
|
||||
{title}
|
||||
{finalTitle}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -104,7 +109,7 @@ export function CompanyTimeline({
|
||||
</div>
|
||||
|
||||
<div className="space-y-16 md:space-y-24">
|
||||
{milestones.map((milestone, i) => {
|
||||
{finalMilestones.map((milestone, i) => {
|
||||
const isEven = i % 2 === 0;
|
||||
const Icon = iconMap[milestone.iconName] || Building2;
|
||||
|
||||
|
||||
52
components/blocks/DataGridPulse.tsx
Normal file
52
components/blocks/DataGridPulse.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
export function DataGridPulse() {
|
||||
const locale = useLocale();
|
||||
return (
|
||||
<div className="relative w-full py-16 md:py-24 bg-neutral-50 border border-neutral-100/80 overflow-hidden flex flex-col items-center rounded-3xl mb-16 shadow-sm">
|
||||
|
||||
{/* Blueprint grid background */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03] pointer-events-none"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(#117C61 1px, transparent 1px), linear-gradient(90deg, #117C61 1px, transparent 1px)',
|
||||
backgroundSize: '40px 40px'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Pulsing horizontal lines (Data Flow) */}
|
||||
<motion.div
|
||||
animate={{ x: ["-100%", "100%"] }}
|
||||
transition={{ duration: 5, repeat: Infinity, ease: "linear" }}
|
||||
className="absolute top-1/3 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary/30 to-transparent blur-[1px]"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{ x: ["100%", "-100%"] }}
|
||||
transition={{ duration: 7, repeat: Infinity, ease: "linear" }}
|
||||
className="absolute top-2/3 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary/25 to-transparent blur-[1px]"
|
||||
/>
|
||||
|
||||
<div className="relative z-10 w-full max-w-2xl px-6 mx-auto flex flex-col items-center text-center">
|
||||
<span className="text-xs md:text-sm font-bold uppercase tracking-widest text-primary mb-4 bg-primary/10 px-3 py-1 rounded-full">
|
||||
{locale === 'en' ? 'Network Expansion in Numbers' : 'Netzausbau in Zahlen'}
|
||||
</span>
|
||||
|
||||
<div className="bg-white/80 backdrop-blur-md p-8 md:p-12 rounded-2xl border border-neutral-200/50 shadow-lg w-full">
|
||||
<div className="font-mono text-5xl md:text-7xl font-black text-neutral-900 tracking-tighter mb-4 leading-none animate-pulse-slow" style={{ fontVariantNumeric: 'tabular-nums' }}>
|
||||
372.161<span className="text-primary text-2xl md:text-4xl ml-1 font-sans">m</span>
|
||||
</div>
|
||||
<p className="text-text-secondary text-base md:text-lg font-medium max-w-md mx-auto leading-relaxed">
|
||||
{locale === 'en'
|
||||
? 'Cable routes laid since 2023. A massive infrastructure achievement, driven by our own machinery.'
|
||||
: 'Verlegte Kabeltrassen seit 2023. Eine massive Infrastrukturleistung, getragen von unserem eigenen Maschinenpark.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
components/blocks/DeepDrillAnimation.tsx
Normal file
64
components/blocks/DeepDrillAnimation.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
export function DeepDrillAnimation() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const locale = useLocale();
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start center", "end center"]
|
||||
});
|
||||
|
||||
const drillDepth = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative w-full py-32 flex flex-col items-center">
|
||||
|
||||
<div className="absolute inset-0 bg-[#050B14] -mx-[50vw] px-[50vw]" />
|
||||
|
||||
<div className="relative z-10 w-full max-w-3xl mx-auto flex flex-col items-center text-center text-white">
|
||||
|
||||
<h3 className="text-3xl font-heading font-extrabold mb-8 tracking-tight">
|
||||
{locale === 'en' ? 'Precision in Depth' : 'Präzision in der Tiefe'}
|
||||
</h3>
|
||||
<p className="text-white/60 text-lg mb-16 max-w-xl">
|
||||
{locale === 'en'
|
||||
? 'Horizontal Directional Drilling (HDD) allows us to cross obstacles and rivers with extreme care.'
|
||||
: 'Horizontalspülbohrverfahren (HDD) erlaubt uns, Hindernisse und Flüsse extrem schonend zu unterqueren.'}
|
||||
</p>
|
||||
|
||||
{/* The Drill Path */}
|
||||
<div className="relative w-2 h-96 bg-white/10 rounded-full mb-16">
|
||||
<motion.div
|
||||
className="absolute top-0 left-0 w-full bg-primary rounded-full"
|
||||
style={{ height: drillDepth, boxShadow: '0 0 20px rgba(130,237,32,0.6)' }}
|
||||
>
|
||||
{/* The Drill Head */}
|
||||
<div className="absolute -bottom-2 -left-2 w-6 h-6 bg-white rounded-full border-4 border-primary shadow-[0_0_15px_rgba(255,255,255,1)]" />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* The Reveal Data */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div className="font-mono text-6xl md:text-8xl font-black text-white tracking-tighter mb-4" style={{ fontVariantNumeric: 'tabular-nums' }}>
|
||||
102.771<span className="text-primary text-3xl md:text-5xl ml-2">m</span>
|
||||
</div>
|
||||
<div className="text-sm font-bold uppercase tracking-widest text-primary">
|
||||
{locale === 'en' ? 'Drilled distance since 2023' : 'Gebohrte Strecke seit 2023'}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
80
components/blocks/GrowthChart.tsx
Normal file
80
components/blocks/GrowthChart.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export function GrowthChart() {
|
||||
const data = [
|
||||
{ year: '2023', hdd: 29436, kabel: 76727 },
|
||||
{ year: '2024', hdd: 28680, kabel: 131964 },
|
||||
{ year: '2025', hdd: 44655, kabel: 163470 },
|
||||
];
|
||||
|
||||
const maxVal = 163470; // Highest value for scaling
|
||||
|
||||
return (
|
||||
<div className="w-full bg-white rounded-3xl p-8 md:p-12 shadow-sm border border-neutral-100 my-16">
|
||||
<div className="mb-10">
|
||||
<h3 className="text-3xl font-heading font-extrabold text-neutral-dark mb-2">Bewiesene Leistungsfähigkeit</h3>
|
||||
<p className="text-neutral-500 font-medium">Unsere verlässliche Baukapazität der letzten drei Jahre (in Metern)</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{data.map((item, index) => {
|
||||
const hddWidth = (item.hdd / maxVal) * 100;
|
||||
const kabelWidth = (item.kabel / maxVal) * 100;
|
||||
|
||||
return (
|
||||
<div key={item.year} className="relative">
|
||||
<div className="text-sm font-extrabold text-neutral-dark mb-2 w-16">{item.year}</div>
|
||||
<div className="space-y-2">
|
||||
{/* Kabelverlegung Bar */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1 bg-neutral-100 rounded-full h-6 overflow-hidden relative">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${kabelWidth}%` }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
transition={{ duration: 1.5, delay: 0.1 * index, ease: "easeOut" }}
|
||||
className="absolute top-0 left-0 h-full bg-primary rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
||||
Kabel: {new Intl.NumberFormat('de-DE').format(item.kabel)}m
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HDD Bar */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1 bg-neutral-100 rounded-full h-4 overflow-hidden relative">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${hddWidth}%` }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
transition={{ duration: 1.5, delay: 0.2 + 0.1 * index, ease: "easeOut" }}
|
||||
className="absolute top-0 left-0 h-full bg-neutral-dark rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
||||
HDD: {new Intl.NumberFormat('de-DE').format(item.hdd)}m
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex gap-6 border-t border-neutral-100 pt-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-primary" />
|
||||
<span className="text-xs font-bold text-neutral-500 uppercase">Kabelverlegung</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-neutral-dark" />
|
||||
<span className="text-xs font-bold text-neutral-500 uppercase">HDD Spülbohrung</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,10 @@ import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
||||
import { Location, defaultLocations } from '@/lib/map-data';
|
||||
import { Location, defaultLocations, minorLocations } from '@/lib/map-data';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
|
||||
const allLocations = [...defaultLocations, ...minorLocations];
|
||||
|
||||
interface Stat {
|
||||
value: string;
|
||||
@@ -24,22 +27,33 @@ interface InteractiveGermanyMapProps {
|
||||
}
|
||||
|
||||
export function InteractiveGermanyMap({
|
||||
badge = 'Einsatzgebiete',
|
||||
title = <>Deutschlandweit<br/>für Sie im Einsatz.</>,
|
||||
description = 'Von unseren strategischen Standorten in Guben und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.',
|
||||
stats = [
|
||||
{ value: '100', suffix: '%', label: 'Überregionale Reichweite' },
|
||||
{ value: '2', suffix: '+', label: 'Operative Standorte' },
|
||||
],
|
||||
locations = defaultLocations,
|
||||
badge,
|
||||
title,
|
||||
description,
|
||||
stats,
|
||||
locations = allLocations,
|
||||
isHero = false
|
||||
}: InteractiveGermanyMapProps) {
|
||||
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
|
||||
const router = useRouter();
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('InteractiveGermanyMap');
|
||||
const tStandard = useTranslations('StandardPage');
|
||||
|
||||
const finalStats = stats || [
|
||||
{ value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },
|
||||
{ value: '2', suffix: '+', label: locale === 'en' ? 'Operational Locations' : 'Operative Standorte' },
|
||||
];
|
||||
|
||||
const finalBadge = badge || tStandard('badge');
|
||||
// the map is mostly used with specific props, so we leave title & description to fallbacks if not provided
|
||||
const finalTitle = title || (locale === 'en' ? <>Nationwide<br/>in operation for you.</> : <>Deutschlandweit<br/>für Sie im Einsatz.</>);
|
||||
const finalDescription = description || (locale === 'en' ? 'From our strategic locations in Guben and Bülstedt, we control and implement complex infrastructure projects nationwide.' : 'Von unseren strategischen Standorten in Guben und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.');
|
||||
|
||||
const hq = locations.find((l) => l.type === 'hq');
|
||||
const branch = locations.find((l) => l.type === 'branch');
|
||||
const projects = locations.filter((l) => l.type === 'project');
|
||||
const minorNodes = locations.filter((l) => l.type === 'minor_node');
|
||||
|
||||
return (
|
||||
<div className={isHero ? "relative w-full" : "relative w-full max-w-7xl mx-auto py-16 md:py-24 px-4 sm:px-6 mt-16 md:mt-20"}>
|
||||
@@ -57,26 +71,26 @@ export function InteractiveGermanyMap({
|
||||
<div className="xl:col-span-5 text-white z-20">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-8">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>{badge}</span>
|
||||
<span>{finalBadge}</span>
|
||||
</div>
|
||||
|
||||
{isHero ? (
|
||||
<h1 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]">
|
||||
{title}
|
||||
<h1 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 text-balance">
|
||||
{finalTitle}
|
||||
</h1>
|
||||
) : (
|
||||
<h3 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]">
|
||||
{title}
|
||||
<h3 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 text-balance">
|
||||
{finalTitle}
|
||||
</h3>
|
||||
)}
|
||||
|
||||
<p className="text-white/60 text-lg mb-12 leading-relaxed">
|
||||
{description}
|
||||
{finalDescription}
|
||||
</p>
|
||||
|
||||
{/* Industrial Stats Grid */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{stats.map((stat, i) => (
|
||||
{finalStats.map((stat, i) => (
|
||||
<div key={i} className="bg-white/5 border border-white/10 rounded-2xl p-6 backdrop-blur-md relative overflow-hidden group">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
<div className="relative z-10">
|
||||
@@ -91,85 +105,57 @@ export function InteractiveGermanyMap({
|
||||
</div>
|
||||
|
||||
{/* Map Right */}
|
||||
<div className="xl:col-span-7 relative flex items-center justify-center lg:justify-end mt-8 lg:mt-0 z-10">
|
||||
<div className="xl:col-span-7 relative flex items-center justify-center min-h-[500px] lg:min-h-[700px] z-10 pointer-events-none">
|
||||
{/* Map Container - Enforce strict aspect ratio matching the SVG (1024x1024) */}
|
||||
<div className="relative w-full max-w-[600px] aspect-square z-10">
|
||||
<div className="relative w-full max-w-[600px] aspect-square group pointer-events-auto">
|
||||
|
||||
{/* Map SVG */}
|
||||
<div className="absolute inset-0 opacity-[0.25] mix-blend-screen drop-shadow-2xl brightness-200">
|
||||
<div className="absolute inset-0 opacity-[0.25] mix-blend-screen drop-shadow-2xl brightness-200 pointer-events-none">
|
||||
<Image
|
||||
src="/germany-map.svg"
|
||||
alt="Deutschlandkarte"
|
||||
alt={locale === 'en' ? "Map of Germany" : "Deutschlandkarte"}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Connections (Lines) */}
|
||||
<svg className="absolute inset-0 w-full h-full pointer-events-none z-10">
|
||||
{hq && branch && (
|
||||
<motion.line
|
||||
x1={`${hq.x}%`}
|
||||
y1={`${hq.y}%`}
|
||||
x2={`${branch.x}%`}
|
||||
y2={`${branch.y}%`}
|
||||
stroke="rgba(130,237,32,0.6)"
|
||||
strokeWidth="2"
|
||||
strokeDasharray="6 6"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 2, ease: "easeOut" }}
|
||||
/>
|
||||
)}
|
||||
{hq && projects.map((proj, idx) => (
|
||||
<motion.line
|
||||
key={proj.id}
|
||||
x1={`${hq.x}%`}
|
||||
y1={`${hq.y}%`}
|
||||
x2={`${proj.x}%`}
|
||||
y2={`${proj.y}%`}
|
||||
stroke="rgba(130,237,32,0.3)"
|
||||
strokeWidth="1.5"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 1.5, delay: 0.5 + idx * 0.2 }}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
{/* Minor Location Markers (The 100+ generic projects) */}
|
||||
{locations.filter(l => l.type === 'minor_node').map((loc, idx) => (
|
||||
<div
|
||||
key={`minor-${idx}`}
|
||||
className="absolute z-10 group/minor cursor-pointer w-10 h-10 flex items-center justify-center"
|
||||
style={{
|
||||
left: `${loc.x}%`,
|
||||
top: `${loc.y}%`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
onMouseEnter={() => setActiveLocation(loc)}
|
||||
onMouseLeave={() => setActiveLocation(null)}
|
||||
>
|
||||
<div className="w-1.5 h-1.5 bg-primary/80 rounded-full group-hover/minor:bg-white group-hover/minor:scale-150 transition-all duration-300" />
|
||||
<div className="absolute inset-0 m-auto w-2 h-2 bg-primary rounded-full opacity-0 group-hover/minor:animate-ping" />
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Location Pins */}
|
||||
{locations.map((loc, idx) => {
|
||||
{/* Major & Reference Location Markers (HQ, Branch, Project) */}
|
||||
{locations.filter(l => l.type !== 'minor_node').map((loc, idx) => {
|
||||
const isHQ = loc.type === 'hq';
|
||||
const isBranch = loc.type === 'branch';
|
||||
const isActive = activeLocation?.id === loc.id;
|
||||
|
||||
const isNearLeft = loc.x < 50;
|
||||
const isNearRight = loc.x >= 50;
|
||||
const isNearTop = loc.y < 55; // 55% threshold to strongly avoid the top header
|
||||
|
||||
const tooltipClasses = [
|
||||
'absolute w-[280px] sm:w-64 bg-white text-neutral-dark p-5 rounded-2xl shadow-2xl z-[9999] border border-neutral-100',
|
||||
isNearLeft ? 'left-1/2 sm:left-0 -translate-x-1/2 sm:translate-x-0' : 'left-1/2 sm:left-auto sm:right-0 -translate-x-1/2 sm:translate-x-0',
|
||||
isNearTop
|
||||
? 'top-full mt-4 before:absolute before:-top-6 before:left-0 before:w-full before:h-6'
|
||||
: 'bottom-full mb-4 before:absolute before:-bottom-6 before:left-0 before:w-full before:h-6'
|
||||
].join(' ');
|
||||
|
||||
const arrowClasses = [
|
||||
'absolute border-8 border-transparent drop-shadow-sm hidden sm:block',
|
||||
isNearLeft ? 'left-6' : 'right-6',
|
||||
isNearTop ? '-top-4 border-b-white border-t-0' : '-bottom-4 border-t-white border-b-0'
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<div
|
||||
key={loc.id}
|
||||
className={`absolute transform -translate-x-1/2 -translate-y-1/2 group cursor-pointer w-16 h-16 flex items-center justify-center ${isActive ? 'z-[60]' : 'z-20'}`}
|
||||
className={`absolute transform -translate-x-1/2 -translate-y-1/2 group/pin cursor-pointer w-16 h-16 flex items-center justify-center ${isActive ? 'z-[60]' : 'z-20'}`}
|
||||
style={{ left: `${loc.x}%`, top: `${loc.y}%` }}
|
||||
onMouseEnter={() => setActiveLocation(loc)}
|
||||
onMouseLeave={() => setActiveLocation(null)}
|
||||
onClick={() => {
|
||||
if (loc.href) router.push(loc.href);
|
||||
if (loc.href) {
|
||||
const href = loc.href.startsWith('/') ? `/${locale}${loc.href}` : loc.href;
|
||||
router.push(href);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Ping Animation for HQ / Branch */}
|
||||
@@ -177,121 +163,112 @@ export function InteractiveGermanyMap({
|
||||
<div className="absolute inset-0 m-auto w-6 h-6 rounded-full animate-ping bg-primary/50 scale-150" />
|
||||
)}
|
||||
|
||||
{/* Marker Dot */}
|
||||
{/* Elegant Marker Dot */}
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', delay: idx * 0.1 }}
|
||||
className={`relative flex items-center justify-center rounded-full shadow-[0_0_20px_rgba(130,237,32,0.4)] transition-transform duration-300 ${
|
||||
isActive ? 'scale-125 z-30 ring-4 ring-primary/30' : 'scale-100'
|
||||
className={`relative flex items-center justify-center rounded-full shadow-[0_0_20px_rgba(130,237,32,0.6)] transition-transform duration-300 ${
|
||||
isActive ? 'scale-125 z-30 ring-4 ring-primary/40' : 'scale-100 group-hover/pin:scale-110'
|
||||
} ${
|
||||
isHQ || isBranch
|
||||
? 'w-6 h-6 bg-primary text-[#050B14]'
|
||||
? 'w-7 h-7 bg-primary text-[#050B14]'
|
||||
: 'w-4 h-4 bg-white ring-[3px] ring-primary'
|
||||
}`}
|
||||
>
|
||||
{(isHQ || isBranch) && <MapPin className="w-3.5 h-3.5" />}
|
||||
{(isHQ || isBranch) && <MapPin className="w-4 h-4" />}
|
||||
</motion.div>
|
||||
|
||||
{/* Tooltip */}
|
||||
<AnimatePresence>
|
||||
{isActive && (
|
||||
<motion.div
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: isNearTop ? -15 : 15, scale: 0.9 },
|
||||
visible: {
|
||||
opacity: 1, y: 0, scale: 1,
|
||||
transition: {
|
||||
type: "spring", stiffness: 350, damping: 25,
|
||||
staggerChildren: 0.05, delayChildren: 0.05
|
||||
}
|
||||
},
|
||||
exit: {
|
||||
opacity: 0, y: isNearTop ? -10 : 10, scale: 0.95,
|
||||
transition: { duration: 0.15, ease: "easeIn" }
|
||||
}
|
||||
}}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
className={`${tooltipClasses} overflow-hidden p-0`}
|
||||
>
|
||||
{loc.featuredImage && (
|
||||
<motion.div
|
||||
variants={{ hidden: { opacity: 0 }, visible: { opacity: 1 } }}
|
||||
className="relative w-full h-32 bg-neutral-100 overflow-hidden"
|
||||
>
|
||||
<Image
|
||||
src={loc.featuredImage}
|
||||
alt={loc.name}
|
||||
fill
|
||||
className="object-cover transform transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-80" />
|
||||
<div className="absolute bottom-3 left-4 flex items-center gap-2">
|
||||
{isHQ ? <Factory className="w-4 h-4 text-primary drop-shadow" /> : <Zap className="w-4 h-4 text-primary drop-shadow" />}
|
||||
<span className="font-extrabold text-sm leading-tight text-white drop-shadow-md">{loc.name}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div className={loc.featuredImage ? "p-5 pt-4" : "p-5"}>
|
||||
{!loc.featuredImage && (
|
||||
<motion.div
|
||||
variants={{ hidden: { opacity: 0, x: -5 }, visible: { opacity: 1, x: 0 } }}
|
||||
className="flex items-center gap-2 mb-2"
|
||||
>
|
||||
{isHQ ? <Factory className="w-4 h-4 text-primary" /> : <Zap className="w-4 h-4 text-primary" />}
|
||||
<span className="font-extrabold text-sm leading-tight text-neutral-dark">{loc.name}</span>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<motion.p
|
||||
variants={{ hidden: { opacity: 0, y: 5 }, visible: { opacity: 1, y: 0 } }}
|
||||
className="text-xs text-neutral-500 font-medium"
|
||||
>
|
||||
{loc.description}
|
||||
</motion.p>
|
||||
|
||||
{loc.details && (
|
||||
<ul className="mt-4 space-y-2">
|
||||
{loc.details.map((detail, i) => (
|
||||
<motion.li
|
||||
key={i}
|
||||
variants={{ hidden: { opacity: 0, x: -5 }, visible: { opacity: 1, x: 0 } }}
|
||||
className="text-[11px] text-neutral-dark flex items-center gap-2 font-semibold"
|
||||
>
|
||||
<div className="w-4 h-4 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary" />
|
||||
</div>
|
||||
{detail}
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{loc.href && (
|
||||
<motion.div
|
||||
variants={{ hidden: { opacity: 0, y: 5 }, visible: { opacity: 1, y: 0 } }}
|
||||
className="mt-4 pt-3 border-t border-neutral-100 text-xs font-bold text-primary flex items-center gap-1 group/link"
|
||||
>
|
||||
Zum Projekt
|
||||
<span className="text-base leading-none translate-y-[1px] transform transition-transform duration-300 group-hover:translate-x-1.5">→</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Arrow Down/Up */}
|
||||
<div className={arrowClasses} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Global Tooltip */}
|
||||
<AnimatePresence>
|
||||
{activeLocation && (
|
||||
<motion.div
|
||||
key="tooltip"
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="absolute z-[9999] pointer-events-none"
|
||||
style={{
|
||||
left: `${activeLocation.x}%`,
|
||||
top: `${activeLocation.y}%`,
|
||||
transform: activeLocation.x < 50 ? 'translate(20px, -50%)' : 'translate(calc(-100% - 20px), -50%)',
|
||||
}}
|
||||
>
|
||||
<div className="bg-[#050B14]/95 backdrop-blur-xl border border-white/10 rounded-2xl shadow-2xl min-w-[200px] w-max max-w-[300px] text-white overflow-hidden">
|
||||
|
||||
{activeLocation.featuredImage && (
|
||||
<div className="relative w-full h-32 bg-neutral-900">
|
||||
<Image
|
||||
src={activeLocation.featuredImage}
|
||||
alt={activeLocation.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#050B14] to-transparent opacity-90" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<MapPin className="w-3.5 h-3.5 text-primary" />
|
||||
<span className="text-xs font-bold text-primary uppercase tracking-wider">
|
||||
{t(`types.${activeLocation.type === 'minor_node' ? 'project' : activeLocation.type}`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-heading font-bold text-lg leading-tight mb-2">
|
||||
{activeLocation.name}
|
||||
</div>
|
||||
{activeLocation.type !== 'minor_node' ? (
|
||||
<div className="text-white/60 text-sm line-clamp-3 mb-3">
|
||||
{activeLocation.description}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-white/60 text-xs mb-3">
|
||||
{t(`descriptions.${['wind','pv','fiber','power','battery'].includes(activeLocation.description || '') ? activeLocation.description : 'default'}`)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(activeLocation.details || activeLocation.type === 'minor_node') && (
|
||||
<ul className="mb-3 space-y-1.5 border-t border-white/10 pt-3">
|
||||
{activeLocation.type === 'minor_node' ? (
|
||||
<>
|
||||
<li className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
|
||||
<span>{t(`features.${['wind','pv','fiber','power','battery'].includes(activeLocation.description || '') ? activeLocation.description : 'default'}`)}</span>
|
||||
</li>
|
||||
<li className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
|
||||
<span>{t('features.method')}</span>
|
||||
</li>
|
||||
</>
|
||||
) : (
|
||||
activeLocation.details?.map((detail, i) => (
|
||||
<li key={i} className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
|
||||
<span>{detail}</span>
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{activeLocation.href && (
|
||||
<div className="inline-flex items-center text-xs font-bold text-primary">
|
||||
{t('learnMore')}
|
||||
<ArrowUpRight className="w-3.5 h-3.5 ml-1" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export interface JobListingBlockProps {
|
||||
title?: string;
|
||||
@@ -45,20 +46,23 @@ const itemVariants = {
|
||||
};
|
||||
|
||||
export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
const t = useTranslations('JobListingBlock');
|
||||
|
||||
const {
|
||||
title,
|
||||
showJobs = true,
|
||||
showFairs,
|
||||
fairsTitle = 'Nächste Messetermine',
|
||||
fairsTitle = t('fairsTitle'),
|
||||
fairs = [
|
||||
{ name: 'Intersolar München', date: 'Termin folgt', type: 'Energie-Messe', location: 'Messe München' },
|
||||
{ name: 'Windenergietage Linstow', date: 'Termin folgt', type: 'Fachkongress', location: 'Linstow' },
|
||||
{ name: 'Kabelwerkstatt Wiesbaden', date: 'Termin folgt', type: 'Fachmesse', location: 'Wiesbaden' }
|
||||
],
|
||||
emptyStateMessage = 'Aktuell sind alle Positionen besetzt. Senden Sie uns gerne eine Initiativbewerbung!',
|
||||
emptyStateLinkText = 'Jetzt Kontakt aufnehmen',
|
||||
emptyStateLinkHref = '/kontakt'
|
||||
emptyStateMessage = t('emptyStateMessage'),
|
||||
emptyStateLinkText = t('emptyStateLinkText'),
|
||||
emptyStateLinkHref = t('emptyStateLinkHref')
|
||||
} = props;
|
||||
|
||||
const title = props.title || t('title');
|
||||
|
||||
// Since Payload is removed, we use an empty array or static data
|
||||
const jobs: any[] = [];
|
||||
@@ -118,7 +122,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">Datum</p>
|
||||
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">{t('date')}</p>
|
||||
<p className="text-white/90 font-medium">{messe.date}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,7 +133,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">Location</p>
|
||||
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">{t('location')}</p>
|
||||
<p className="text-white/90 font-medium">{messe.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,7 +145,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">Stand</p>
|
||||
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">{t('booth')}</p>
|
||||
<p className="text-primary-light font-bold text-sm">{messe.booth}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,7 +184,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
|
||||
className="font-heading font-extrabold text-4xl text-neutral-dark mb-8"
|
||||
>
|
||||
{title || 'Aktuelle Stellenangebote'}
|
||||
{title}
|
||||
</motion.h2>
|
||||
|
||||
{jobs.length > 0 ? (
|
||||
@@ -206,7 +210,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||
<p className="text-text-secondary mt-1">{job.location}</p>
|
||||
</div>
|
||||
<div className="flex items-center text-primary font-bold group-hover:translate-x-2 transition-transform">
|
||||
Details ansehen
|
||||
{t('viewDetails')}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 ml-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
96
components/blocks/ReferencesSlider.test.tsx
Normal file
96
components/blocks/ReferencesSlider.test.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ReferencesSlider, Reference } from './ReferencesSlider';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
|
||||
// Mock next/link to render standard <a> tag
|
||||
vi.mock('next/link', () => ({
|
||||
default: vi.fn(({ children, href, onClick, ...props }: any) => {
|
||||
return (
|
||||
<a href={href} onClick={onClick} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}));
|
||||
|
||||
// Mock next/image to render standard img tag
|
||||
vi.mock('next/image', () => ({
|
||||
default: (props: any) => {
|
||||
return <img {...props} />;
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock next-intl
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => 'en',
|
||||
NextIntlClientProvider: ({ children }: any) => <>{children}</>
|
||||
}));
|
||||
|
||||
describe('ReferencesSlider TDD', () => {
|
||||
const mockReferences: Reference[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Referenz Projekt Eins',
|
||||
slug: 'referenz-projekt-eins',
|
||||
category: 'Kabeltiefbau',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Referenz Projekt Zwei',
|
||||
slug: 'referenz-projekt-zwei',
|
||||
category: 'Bohrtechnik',
|
||||
}
|
||||
];
|
||||
|
||||
it('renders reference tiles correctly', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
expect(screen.getByText('Referenz Projekt Eins')).toBeTruthy();
|
||||
expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('wraps each tile in a link to /referenzen/[slug]', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const links = screen.getAllByTestId('reference-tile-link');
|
||||
expect(links).toHaveLength(2);
|
||||
expect(links[0].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-eins');
|
||||
expect(links[1].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-zwei');
|
||||
});
|
||||
|
||||
it('allows navigation on simple click (no drag)', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
|
||||
|
||||
// Simulate simple click sequence
|
||||
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
|
||||
fireEvent.mouseUp(firstLink);
|
||||
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
||||
|
||||
firstLink.dispatchEvent(clickEvent);
|
||||
|
||||
expect(clickEvent.defaultPrevented).toBe(false);
|
||||
});
|
||||
|
||||
it('prevents navigation if mouse was dragged', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
|
||||
|
||||
// Simulate drag sequence
|
||||
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
|
||||
// Simulate mouse move of 20px
|
||||
fireEvent.mouseMove(firstLink, { pageX: 120, clientX: 120 });
|
||||
fireEvent.mouseUp(firstLink);
|
||||
|
||||
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
||||
firstLink.dispatchEvent(clickEvent);
|
||||
|
||||
expect(clickEvent.defaultPrevented).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||
import { useTranslations, useLocale } from 'next-intl';
|
||||
|
||||
export interface Reference {
|
||||
id: string;
|
||||
@@ -34,18 +35,23 @@ interface ReferencesSliderProps {
|
||||
}
|
||||
|
||||
export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
const t = useTranslations('ReferencesSlider');
|
||||
const locale = useLocale();
|
||||
|
||||
const { data } = props;
|
||||
const references = props.references || [];
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const dragDistanceRef = React.useRef(0);
|
||||
const startXRef = React.useRef(0);
|
||||
const scrollLeftRef = React.useRef(0);
|
||||
const isDraggingRef = React.useRef(false);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
const [startX, setStartX] = React.useState(0);
|
||||
const [scrollLeft, setScrollLeft] = React.useState(0);
|
||||
|
||||
const badge = props.badge || data?.badge || 'Ausgewählte Projekte';
|
||||
const title = props.title || data?.title || 'Referenzen & Erfolge';
|
||||
const description = props.description || data?.description || 'Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabeltiefbau, Bohrtechnik und Netzinfrastruktur.';
|
||||
const ctaLabel = props.ctaLabel || data?.ctaLabel || 'Alle Referenzen ansehen';
|
||||
const ctaHref = props.ctaHref || data?.ctaHref || '/referenzen';
|
||||
const badge = props.badge || data?.badge || t('badge');
|
||||
const title = props.title || data?.title || t('title');
|
||||
const description = props.description || data?.description || t('description');
|
||||
const ctaLabel = props.ctaLabel || data?.ctaLabel || t('ctaLabel');
|
||||
const ctaHref = props.ctaHref || data?.ctaHref || `/${locale}/referenzen`;
|
||||
|
||||
if (!references || references.length === 0) return null;
|
||||
|
||||
@@ -60,24 +66,36 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
const onMouseDown = (e: React.MouseEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
setIsDragging(true);
|
||||
setStartX(e.pageX - containerRef.current.offsetLeft);
|
||||
setScrollLeft(containerRef.current.scrollLeft);
|
||||
isDraggingRef.current = true;
|
||||
const startXVal = e.clientX - (containerRef.current.offsetLeft || 0);
|
||||
startXRef.current = startXVal;
|
||||
scrollLeftRef.current = containerRef.current.scrollLeft || 0;
|
||||
dragDistanceRef.current = 0;
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
setIsDragging(false);
|
||||
isDraggingRef.current = false;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
setIsDragging(false);
|
||||
isDraggingRef.current = false;
|
||||
};
|
||||
|
||||
const onMouseMove = (e: React.MouseEvent) => {
|
||||
if (!isDragging || !containerRef.current) return;
|
||||
if (!isDraggingRef.current || !containerRef.current) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - containerRef.current.offsetLeft;
|
||||
const walk = (x - startX) * 2; // Scroll-fast
|
||||
containerRef.current.scrollLeft = scrollLeft - walk;
|
||||
const x = e.clientX - (containerRef.current.offsetLeft || 0);
|
||||
const walk = (x - startXRef.current) * 2; // Scroll-fast
|
||||
containerRef.current.scrollLeft = scrollLeftRef.current - walk;
|
||||
dragDistanceRef.current = Math.abs(x - startXRef.current);
|
||||
};
|
||||
|
||||
const handleTileClick = (e: React.MouseEvent) => {
|
||||
if (dragDistanceRef.current > 10) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -117,7 +135,12 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
transition={{ delay: i * 0.1, duration: 0.6, ease: "easeOut" }}
|
||||
className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
|
||||
>
|
||||
<div className="relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl">
|
||||
<Link
|
||||
href={`/${locale}/referenzen/${ref.slug}`}
|
||||
onClick={handleTileClick}
|
||||
data-testid="reference-tile-link"
|
||||
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
src={imgSrc}
|
||||
alt={ref.title}
|
||||
@@ -135,7 +158,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
</span>
|
||||
<h4 className="font-heading text-xl md:text-2xl font-bold leading-tight break-words">{ref.title}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
|
||||
102
components/blocks/ScaleOfImpact.tsx
Normal file
102
components/blocks/ScaleOfImpact.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
export function ScaleOfImpact() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const locale = useLocale();
|
||||
|
||||
// Track scroll progress through this container
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start end", "end start"]
|
||||
});
|
||||
|
||||
// Map scroll progress to SVG path length
|
||||
const pathLength = useTransform(scrollYProgress, [0.2, 0.8], [0, 1]);
|
||||
|
||||
// Opacity for the massive background numbers
|
||||
const bgOpacity = useTransform(scrollYProgress, [0.1, 0.4, 0.8, 1], [0, 0.1, 0.1, 0]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative w-full min-h-[70vh] bg-[#050B14] flex flex-col items-center justify-center overflow-hidden border-y border-neutral-800"
|
||||
>
|
||||
{/* Massive Watermark Numbers */}
|
||||
<motion.div
|
||||
style={{ opacity: bgOpacity }}
|
||||
className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none select-none overflow-hidden mix-blend-overlay"
|
||||
>
|
||||
<div className="font-heading font-black text-[20vw] leading-none text-white opacity-20 whitespace-nowrap">
|
||||
<span className="font-mono tracking-tighter">474.932</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Foreground Content */}
|
||||
<div className="relative z-10 container max-w-7xl mx-auto px-4 flex flex-col items-center text-center">
|
||||
|
||||
<h2 className="text-3xl md:text-5xl font-heading font-extrabold text-white mb-6 tracking-tight">
|
||||
{locale === 'en' ? (
|
||||
<>An infrastructure <br className="md:hidden"/> that connects the country.</>
|
||||
) : (
|
||||
<>Eine Infrastruktur, <br className="md:hidden"/> die das Land verbindet.</>
|
||||
)}
|
||||
</h2>
|
||||
|
||||
<p className="text-white/60 text-lg md:text-xl max-w-2xl font-medium mb-16">
|
||||
{locale === 'en'
|
||||
? "We don't just talk about the energy transition. We build it. In the last 36 months alone, we have completed a route that stretches from our headquarters in Guben all the way to Munich."
|
||||
: "Wir sprechen nicht nur über die Energiewende. Wir bauen sie. Allein in den letzten 36 Monaten haben wir eine Strecke realisiert, die von unserem Hauptsitz in Guben bis nach München reicht."
|
||||
}
|
||||
</p>
|
||||
|
||||
{/* Abstract SVG Line Animation */}
|
||||
<div className="w-full max-w-4xl h-32 relative">
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 1000 100"
|
||||
preserveAspectRatio="none"
|
||||
className="absolute inset-0"
|
||||
>
|
||||
{/* Background dim line */}
|
||||
<path
|
||||
d="M0,50 Q250,0 500,50 T1000,50"
|
||||
fill="none"
|
||||
stroke="rgba(255,255,255,0.05)"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
{/* Glowing animated line */}
|
||||
<motion.path
|
||||
d="M0,50 Q250,0 500,50 T1000,50"
|
||||
fill="none"
|
||||
stroke="#82ED20" // primary color
|
||||
strokeWidth="4"
|
||||
style={{ pathLength }}
|
||||
className="drop-shadow-[0_0_15px_rgba(130,237,32,0.8)]"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Static Data Grid (No jumping text) */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-16 w-full max-w-4xl mt-12">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="font-mono text-5xl md:text-6xl font-black text-white tracking-tighter mb-2">100<span className="text-primary">+</span></div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Major Projects' : 'Großprojekte'}</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="font-mono text-5xl md:text-6xl font-black text-white tracking-tighter mb-2">372<span className="text-primary text-3xl">km</span></div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Cable Laying' : 'Kabelverlegung'}</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="font-mono text-5xl md:text-6xl font-black text-white tracking-tighter mb-2">102<span className="text-primary text-3xl">km</span></div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'HDD Drilling' : 'Spülbohrung'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export interface TeamMember {
|
||||
id: string;
|
||||
@@ -22,16 +23,18 @@ interface TeamGridProps {
|
||||
}
|
||||
|
||||
export function TeamGrid({ members }: TeamGridProps) {
|
||||
const t = useTranslations('TeamGrid');
|
||||
|
||||
if (!members || members.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section className="py-24 bg-white relative overflow-hidden">
|
||||
<div className="container relative z-10">
|
||||
<div className="mb-12">
|
||||
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">Persönliche Beratung</h2>
|
||||
<h3 className="font-heading text-4xl font-extrabold text-neutral-dark mb-4">Ihre Ansprechpartner</h3>
|
||||
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{t('badge')}</h2>
|
||||
<h3 className="font-heading text-4xl font-extrabold text-neutral-dark mb-4">{t('title')}</h3>
|
||||
<p className="text-text-secondary max-w-2xl text-lg">
|
||||
Sprechen Sie direkt mit unseren Experten für Ihr regionales Projekt.
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -56,7 +59,7 @@ export function TeamGrid({ members }: TeamGridProps) {
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
|
||||
</span>
|
||||
Geschäftsführung
|
||||
{t('management')}
|
||||
</div>
|
||||
<h3 className="font-heading font-black text-5xl md:text-6xl lg:text-7xl mb-4 leading-[1.1] tracking-tight">
|
||||
{members[0].name}
|
||||
@@ -145,7 +148,7 @@ export function TeamGrid({ members }: TeamGridProps) {
|
||||
{member.branch && (
|
||||
<div className="mb-6">
|
||||
<span className="inline-block px-3 py-1 bg-neutral-100 text-neutral-600 rounded-md text-xs uppercase tracking-widest font-semibold border border-neutral-200/60">
|
||||
{member.branch === 'e-tib' ? 'E-TIB GmbH' : member.branch === 'ing' ? 'Ingenieurgesellschaft' : member.branch === 'bohrtechnik' ? 'Bohrtechnik' : member.branch}
|
||||
{member.branch === 'e-tib' ? t('branchETIB') : member.branch === 'ing' ? t('branchIng') : member.branch === 'bohrtechnik' ? t('branchBohr') : member.branch}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { sendContactFormAction } from '@/app/actions/contact';
|
||||
|
||||
export function ContactForm() {
|
||||
const [status, setStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||
@@ -11,10 +12,18 @@ export function ContactForm() {
|
||||
e.preventDefault();
|
||||
setStatus('loading');
|
||||
|
||||
// Simulate server action using @mintel/mail or API route
|
||||
setTimeout(() => {
|
||||
setStatus('success');
|
||||
}, 1500);
|
||||
const formData = new FormData(e.currentTarget);
|
||||
try {
|
||||
const result = await sendContactFormAction(formData);
|
||||
if (result.success) {
|
||||
setStatus('success');
|
||||
} else {
|
||||
setStatus('error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -41,13 +50,31 @@ export function ContactForm() {
|
||||
<p>Vielen Dank für Ihr Interesse an der E-TIB Gruppe. Ein Ansprechpartner wird sich zeitnah mit Ihnen in Verbindung setzen.</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
) : status === 'error' ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="bg-red-500/10 border border-red-500 text-red-600 p-6 rounded-xl flex items-start gap-4 mb-6"
|
||||
>
|
||||
<div className="bg-red-500 text-white rounded-full p-1 mt-0.5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-lg mb-1">Fehler beim Senden</h4>
|
||||
<p>Leider ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal oder kontaktieren Sie uns direkt per E-Mail oder Telefon.</p>
|
||||
<Button variant="outline" size="sm" className="mt-4" onClick={() => setStatus('idle')}>Erneut versuchen</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : null}
|
||||
|
||||
{status !== 'success' && status !== 'error' && (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="firstName" className="block text-sm font-semibold text-neutral-dark">Vorname</label>
|
||||
<label htmlFor="name" className="block text-sm font-semibold text-neutral-dark">Vorname</label>
|
||||
<input
|
||||
id="firstName"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
disabled={status === 'loading'}
|
||||
className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all"
|
||||
@@ -57,7 +84,8 @@ export function ContactForm() {
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="lastName" className="block text-sm font-semibold text-neutral-dark">Nachname</label>
|
||||
<input
|
||||
id="lastName"
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
required
|
||||
disabled={status === 'loading'}
|
||||
className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all"
|
||||
@@ -70,6 +98,7 @@ export function ContactForm() {
|
||||
<label htmlFor="email" className="block text-sm font-semibold text-neutral-dark">E-Mail Adresse</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
disabled={status === 'loading'}
|
||||
@@ -82,6 +111,7 @@ export function ContactForm() {
|
||||
<label htmlFor="company" className="block text-sm font-semibold text-neutral-dark">Unternehmen (Optional)</label>
|
||||
<input
|
||||
id="company"
|
||||
name="company"
|
||||
disabled={status === 'loading'}
|
||||
className="w-full bg-neutral-50 px-4 py-3 rounded-lg border border-neutral-200 focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all"
|
||||
placeholder="Firma GmbH"
|
||||
@@ -92,6 +122,7 @@ export function ContactForm() {
|
||||
<label htmlFor="message" className="block text-sm font-semibold text-neutral-dark">Ihre Nachricht</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
required
|
||||
disabled={status === 'loading'}
|
||||
rows={5}
|
||||
@@ -100,6 +131,8 @@ export function ContactForm() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input type="text" name="company_website" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" />
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={status === 'loading'}
|
||||
|
||||
@@ -20,23 +20,23 @@ export default function Hero({ data }: { data?: any }) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
const [heroPlaceholder, setHeroPlaceholder] = useState(
|
||||
'Projekt beschreiben oder Kabel suchen...',
|
||||
t('searchPlaceholder')
|
||||
);
|
||||
const typingRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const HERO_PLACEHOLDERS = [
|
||||
'Querschnittsberechnung für 110kV Trasse', // Hochspannung
|
||||
'Wie schwer ist NAYY 4x150?',
|
||||
'Ich plane einen Solarpark, was brauche ich?', // Projekt Solar
|
||||
'Unterschied zwischen N2XSY und NAY2XSY?', // Fach
|
||||
'Mittelspannungskabel für Windkraftanlage', // Windpark
|
||||
'Welches Aluminiumkabel für 20kV?', // Mittelspannung
|
||||
t('placeholder1'),
|
||||
t('placeholder2'),
|
||||
t('placeholder3'),
|
||||
t('placeholder4'),
|
||||
t('placeholder5'),
|
||||
t('placeholder6'),
|
||||
];
|
||||
|
||||
// Typing animation for the hero search placeholder
|
||||
useEffect(() => {
|
||||
if (searchQuery) {
|
||||
setHeroPlaceholder('Projekt beschreiben oder Kabel suchen...');
|
||||
setHeroPlaceholder(t('searchPlaceholder'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export default function Hero({ data }: { data?: any }) {
|
||||
size="lg"
|
||||
className="rounded-xl px-6 py-4 shrink-0 flex items-center shadow-md font-bold cursor-pointer hover:bg-accent hover:brightness-110"
|
||||
>
|
||||
Fragen
|
||||
{t('ask')}
|
||||
<ChevronRight className="w-5 h-5 ml-2 -mr-1" />
|
||||
</Button>
|
||||
</form>
|
||||
@@ -160,7 +160,7 @@ export default function Hero({ data }: { data?: any }) {
|
||||
<div className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6">
|
||||
<div>
|
||||
<Button
|
||||
href="/contact"
|
||||
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
|
||||
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 hover:scale-105 transition-all outline-none"
|
||||
|
||||
@@ -3,7 +3,7 @@ import Image from 'next/image';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { ShieldCheck, Leaf, Lock, Accessibility, Zap, Mail, Phone } from 'lucide-react';
|
||||
import { LanguageSwitcher } from './LanguageSwitcher';
|
||||
|
||||
import { Tooltip } from '@/components/ui/Tooltip';
|
||||
interface CompanyInfo {
|
||||
contactEmail: string;
|
||||
contactPhone: string;
|
||||
@@ -104,12 +104,12 @@ export function Footer({ companyInfo }: FooterProps) {
|
||||
</h4>
|
||||
<ul className="space-y-2 md:space-y-4 relative z-10 flex-grow">
|
||||
{[
|
||||
{ label: locale === 'de' ? 'Kompetenzen' : 'Competencies', href: `/${locale}/kompetenzen` },
|
||||
{ label: locale === 'de' ? 'Über uns' : 'About us', href: `/${locale}/ueber-uns` },
|
||||
{ label: locale === 'de' ? 'Kompetenzen' : 'Competencies', href: `/${locale}/${locale === 'de' ? 'kompetenzen' : 'competencies'}` },
|
||||
{ label: locale === 'de' ? 'Über uns' : 'About us', href: `/${locale}/${locale === 'de' ? 'ueber-uns' : 'about-us'}` },
|
||||
{ label: locale === 'de' ? 'Unser Team' : 'Our Team', href: `/${locale}/team` },
|
||||
{ label: locale === 'de' ? 'Zertifikate' : 'Certificates', href: `/${locale}/zertifikate` },
|
||||
{ label: locale === 'de' ? 'Karriere' : 'Career', href: `/${locale}/karriere` },
|
||||
{ label: locale === 'de' ? 'Messen & Events' : 'Fairs & Events', href: `/${locale}/messen` },
|
||||
{ label: locale === 'de' ? 'Karriere' : 'Career', href: `/${locale}/${locale === 'de' ? 'karriere' : 'career'}` },
|
||||
{ label: locale === 'de' ? 'Messen & Events' : 'Fairs & Events', href: `/${locale}/${locale === 'de' ? 'messen' : 'trade-fairs'}` },
|
||||
{ label: locale === 'de' ? 'Kontakt' : 'Contact', href: `/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}` },
|
||||
].map((link) => (
|
||||
<li key={link.href}>
|
||||
@@ -131,8 +131,8 @@ export function Footer({ companyInfo }: FooterProps) {
|
||||
</h4>
|
||||
<ul className="space-y-2 md:space-y-4 relative z-10 flex-grow">
|
||||
{[
|
||||
{ label: locale === 'de' ? 'Impressum' : 'Imprint', href: `/${locale}/impressum` },
|
||||
{ label: locale === 'de' ? 'Datenschutz' : 'Privacy Policy', href: `/${locale}/datenschutz` },
|
||||
{ label: locale === 'de' ? 'Impressum' : 'Imprint', href: `/${locale}/${locale === 'de' ? 'impressum' : 'imprint'}` },
|
||||
{ label: locale === 'de' ? 'Datenschutz' : 'Privacy Policy', href: `/${locale}/${locale === 'de' ? 'datenschutz' : 'privacy-policy'}` },
|
||||
{ label: locale === 'de' ? 'AGB' : 'Terms', href: `/${locale}/${locale === 'de' ? 'agb' : 'terms'}` },
|
||||
].map((link) => (
|
||||
<li key={link.href}>
|
||||
@@ -165,10 +165,18 @@ export function Footer({ companyInfo }: FooterProps) {
|
||||
|
||||
{/* Quality Badges */}
|
||||
<div className="mt-4 md:mt-6 flex flex-wrap justify-center gap-x-4 gap-y-3 md:gap-x-8 md:gap-y-4 text-neutral-400 text-[10px] md:text-xs font-medium uppercase tracking-wider px-2">
|
||||
<div className="flex items-center gap-2"><ShieldCheck className="w-4 h-4 text-neutral-400" /><span>SSL Secured</span></div>
|
||||
<div className="flex items-center gap-2"><Leaf className="w-4 h-4 text-primary" /><span>Green Hosting</span></div>
|
||||
<div className="flex items-center gap-2"><Lock className="w-4 h-4 text-neutral-400" /><span>DSGVO Compliant</span></div>
|
||||
<div className="flex items-center gap-2"><Zap className="w-4 h-4 text-neutral-400" /><span>High Performance</span></div>
|
||||
<Tooltip content={locale === 'de' ? 'Ihre Daten werden sicher und verschlüsselt übertragen.' : 'Your data is transmitted securely and encrypted.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><ShieldCheck className="w-4 h-4 text-neutral-400" /><span>SSL Secured</span></div>
|
||||
</Tooltip>
|
||||
<Tooltip content={locale === 'de' ? 'Wir nutzen 100% Ökostrom für den Betrieb unserer Website.' : 'We use 100% green energy to run our website.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><Leaf className="w-4 h-4 text-primary" /><span>Green Hosting</span></div>
|
||||
</Tooltip>
|
||||
<Tooltip content={locale === 'de' ? 'Ihre Privatsphäre ist uns wichtig. Wir halten uns streng an den Datenschutz.' : 'Your privacy is important to us. We strictly adhere to data protection laws.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><Lock className="w-4 h-4 text-neutral-400" /><span>DSGVO Compliant</span></div>
|
||||
</Tooltip>
|
||||
<Tooltip content={locale === 'de' ? 'Blitzschnelle Ladezeiten für ein optimales Nutzererlebnis.' : 'Lightning-fast loading times for an optimal user experience.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><Zap className="w-4 h-4 text-neutral-400" /><span>High Performance</span></div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* Mintel Design & Version */}
|
||||
|
||||
70
components/layout/LanguageSwitcher.test.tsx
Normal file
70
components/layout/LanguageSwitcher.test.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import * as React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { LanguageSwitcher } from './LanguageSwitcher';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
usePathname: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock TransitionLink to render standard anchor tag and forward all props
|
||||
vi.mock('@/components/ui/TransitionLink', () => ({
|
||||
TransitionLink: ({ children, href, onClick, ...props }: any) => (
|
||||
<a href={href} onClick={onClick} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock HoverShineOverlay
|
||||
vi.mock('@/components/ui/HoverShineOverlay', () => ({
|
||||
HoverShineOverlay: () => <div data-testid="hover-shine" />,
|
||||
}));
|
||||
|
||||
describe('LanguageSwitcher TDD', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('translates /en/trade-fairs to /de/messen when switching to German', () => {
|
||||
(usePathname as any).mockReturnValue('/en/trade-fairs');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const deLink = screen.getByLabelText('Switch to Deutsch');
|
||||
expect(deLink.getAttribute('href')).toBe('/de/messen');
|
||||
});
|
||||
|
||||
it('translates /de/messen to /en/trade-fairs when switching to English', () => {
|
||||
(usePathname as any).mockReturnValue('/de/messen');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const enLink = screen.getByLabelText('Switch to English');
|
||||
expect(enLink.getAttribute('href')).toBe('/en/trade-fairs');
|
||||
});
|
||||
|
||||
it('translates /en/about-us to /de/ueber-uns when switching to German', () => {
|
||||
vi.mocked(usePathname).mockReturnValue('/en/about-us');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const deLink = screen.getByLabelText('Switch to Deutsch');
|
||||
expect(deLink.getAttribute('href')).toBe('/de/ueber-uns');
|
||||
});
|
||||
|
||||
it('translates /de/ueber-uns to /en/about-us when switching to English', () => {
|
||||
vi.mocked(usePathname).mockReturnValue('/de/ueber-uns');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const enLink = screen.getByLabelText('Switch to English');
|
||||
expect(enLink.getAttribute('href')).toBe('/en/about-us');
|
||||
});
|
||||
|
||||
it('preserves nested paths like blog posts', () => {
|
||||
vi.mocked(usePathname).mockReturnValue('/de/blog/some-post');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const enLink = screen.getByLabelText('Switch to English');
|
||||
expect(enLink.getAttribute('href')).toBe('/en/blog/some-post');
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,41 @@
|
||||
import { TransitionLink } from '@/components/ui/TransitionLink';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||
import enMessages from '@/messages/en.json';
|
||||
import deMessages from '@/messages/de.json';
|
||||
|
||||
const getMessages = (locale: string) => {
|
||||
return locale === 'de' ? deMessages : enMessages;
|
||||
};
|
||||
|
||||
// Synchronous version of mapSlugToFileSlug
|
||||
function syncMapSlugToFileSlug(translatedSlug: string, locale: string): string {
|
||||
const messages = getMessages(locale);
|
||||
const slugs = messages.Slugs;
|
||||
|
||||
if (slugs.pages && translatedSlug in slugs.pages) {
|
||||
return slugs.pages[translatedSlug as keyof typeof slugs.pages];
|
||||
}
|
||||
return translatedSlug;
|
||||
}
|
||||
|
||||
// Synchronous version of mapFileSlugToTranslated
|
||||
function syncMapFileSlugToTranslated(fileSlug: string, locale: string): string {
|
||||
const messages = getMessages(locale);
|
||||
const slugs = messages.Slugs;
|
||||
|
||||
const sections = [slugs.pages];
|
||||
for (const sectionData of sections) {
|
||||
if (sectionData && typeof sectionData === 'object') {
|
||||
for (const [translatedSlug, mappedSlug] of Object.entries(sectionData)) {
|
||||
if (mappedSlug === fileSlug) {
|
||||
return translatedSlug;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileSlug;
|
||||
}
|
||||
|
||||
interface LanguageSwitcherProps {
|
||||
mobile?: boolean;
|
||||
@@ -14,8 +49,23 @@ export function LanguageSwitcher({ mobile = false, isSolidMode = false }: Langua
|
||||
const currentLocale = pathname.startsWith('/en') ? 'en' : 'de';
|
||||
|
||||
const getSwitchedUrl = (newLocale: string) => {
|
||||
const pathWithoutLocale = pathname.replace(/^\/(en|de)/, '');
|
||||
return `/${newLocale}${pathWithoutLocale === '' ? '' : pathWithoutLocale}`;
|
||||
const segments = pathname.split('/').filter(Boolean);
|
||||
if (segments.length === 0) {
|
||||
return `/${newLocale}`;
|
||||
}
|
||||
|
||||
const currentLoc = segments[0] === 'en' || segments[0] === 'de' ? segments[0] : 'de';
|
||||
const hasLocale = segments[0] === 'en' || segments[0] === 'de';
|
||||
const pathSegments = hasLocale ? segments.slice(1) : segments;
|
||||
|
||||
if (pathSegments.length === 1) {
|
||||
const slug = pathSegments[0];
|
||||
const fileSlug = syncMapSlugToFileSlug(slug, currentLoc);
|
||||
const translatedSlug = syncMapFileSlugToTranslated(fileSlug, newLocale);
|
||||
return `/${newLocale}/${translatedSlug}`;
|
||||
}
|
||||
|
||||
return `/${newLocale}/${pathSegments.join('/')}`;
|
||||
};
|
||||
|
||||
const locales = [
|
||||
|
||||
61
components/search/AISearchResults.test.tsx
Normal file
61
components/search/AISearchResults.test.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AISearchResults } from './AISearchResults';
|
||||
|
||||
// Mock lucide-react
|
||||
vi.mock('lucide-react', () => ({
|
||||
ArrowUp: () => <div data-testid="arrow-up" />,
|
||||
X: () => <div data-testid="close-icon" />,
|
||||
Sparkles: () => <div data-testid="sparkles" />,
|
||||
ChevronRight: () => <div data-testid="chevron-right" />,
|
||||
RotateCcw: () => <div data-testid="rotate-ccw" />,
|
||||
Copy: () => <div data-testid="copy" />,
|
||||
Check: () => <div data-testid="check" />,
|
||||
}));
|
||||
|
||||
// Mock next/link
|
||||
vi.mock('next/link', () => ({
|
||||
default: ({ children, href }: any) => <a href={href}>{children}</a>,
|
||||
}));
|
||||
|
||||
// Mock useAnalytics
|
||||
vi.mock('../analytics/useAnalytics', () => ({
|
||||
useAnalytics: () => ({
|
||||
trackEvent: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock react-markdown
|
||||
vi.mock('react-markdown', () => ({
|
||||
default: ({ children }: any) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
// Mock remark-gfm
|
||||
vi.mock('remark-gfm', () => ({
|
||||
default: {},
|
||||
}));
|
||||
|
||||
// Mock next-intl
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => {
|
||||
const translate = (key: string) => key;
|
||||
translate.raw = (key: string) => {
|
||||
if (key === 'loadingTexts') return ['Lade...', 'Denke nach...'];
|
||||
if (key === 'prompts') return ['Prompt 1', 'Prompt 2'];
|
||||
return [];
|
||||
};
|
||||
return translate;
|
||||
},
|
||||
useLocale: () => 'en',
|
||||
}));
|
||||
|
||||
describe('AISearchResults Component Test (TDD)', () => {
|
||||
it('renders correctly when open', () => {
|
||||
const handleClose = vi.fn();
|
||||
const { container } = render(
|
||||
<AISearchResults isOpen={true} onClose={handleClose} />
|
||||
);
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -8,14 +8,9 @@ import { AnalyticsEvents } from '../analytics/analytics-events';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useTranslations } from 'next-intl';
|
||||
const AIOrb = dynamic(() => import('./AIOrb'), { ssr: false });
|
||||
|
||||
const LOADING_TEXTS = [
|
||||
'Durchsuche das Kabelhandbuch... 📖',
|
||||
'Frage den Senior-Ingenieur... 👴🔧',
|
||||
'Frage ChatGPTs Cousin 2. Grades... 🤖',
|
||||
];
|
||||
|
||||
interface ProductMatch {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -51,7 +46,13 @@ export function AISearchResults({
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
|
||||
const [copiedAll, setCopiedAll] = useState(false);
|
||||
const [loadingText, setLoadingText] = useState(LOADING_TEXTS[0]);
|
||||
const t = useTranslations('AISearch');
|
||||
|
||||
// We load texts dynamically from translations
|
||||
const loadingTexts = t.raw('loadingTexts') as string[];
|
||||
const prompts = t.raw('prompts') as string[];
|
||||
|
||||
const [loadingText, setLoadingText] = useState(loadingTexts[0]);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
@@ -141,11 +142,11 @@ export function AISearchResults({
|
||||
setTimeout(() => {
|
||||
setIsLoading(true);
|
||||
// Start rotating loading texts
|
||||
let textIdx = Math.floor(Math.random() * LOADING_TEXTS.length);
|
||||
setLoadingText(LOADING_TEXTS[textIdx]);
|
||||
let textIdx = Math.floor(Math.random() * loadingTexts.length);
|
||||
setLoadingText(loadingTexts[textIdx]);
|
||||
loadingIntervalRef.current = setInterval(() => {
|
||||
textIdx = (textIdx + 1) % LOADING_TEXTS.length;
|
||||
setLoadingText(LOADING_TEXTS[textIdx]);
|
||||
textIdx = (textIdx + 1) % loadingTexts.length;
|
||||
setLoadingText(loadingTexts[textIdx]);
|
||||
}, 2500);
|
||||
}, 400);
|
||||
|
||||
@@ -191,8 +192,8 @@ export function AISearchResults({
|
||||
console.error(err);
|
||||
const msg =
|
||||
err.name === 'AbortError'
|
||||
? 'Anfrage hat zu lange gedauert. Bitte versuche es erneut.'
|
||||
: err.message || 'Ein Fehler ist aufgetreten.';
|
||||
? t('timeoutError')
|
||||
: err.message || t('genericError');
|
||||
|
||||
// Show error as a system message in the chat instead of a separate error banner
|
||||
setMessages((prev) => [
|
||||
@@ -248,7 +249,7 @@ export function AISearchResults({
|
||||
|
||||
const handleCopyChat = () => {
|
||||
const fullChat = messages
|
||||
.map((m) => `${m.role === 'user' ? 'Du' : 'Ohm'}:\n${m.content}`)
|
||||
.map((m) => `${m.role === 'user' ? t('you') : 'Ohm'}:\n${m.content}`)
|
||||
.join('\n\n');
|
||||
handleCopy(fullChat);
|
||||
};
|
||||
@@ -294,7 +295,7 @@ export function AISearchResults({
|
||||
<div>
|
||||
<h2 className="text-white font-bold text-sm tracking-wide">Ohm</h2>
|
||||
<p className="text-[10px] text-white/30 font-medium tracking-wider uppercase">
|
||||
{isLoading ? 'Denkt nach...' : error ? 'Fehler aufgetreten' : 'Online'}
|
||||
{isLoading ? t('thinking') : error ? t('errorStatus') : t('online')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -303,14 +304,14 @@ export function AISearchResults({
|
||||
<button
|
||||
onClick={handleCopyChat}
|
||||
className="flex items-center gap-1.5 text-[10px] font-bold text-white/40 hover:text-white/80 transition-all duration-200 hover:bg-white/5 rounded-full px-3 py-1.5 cursor-pointer uppercase tracking-wider"
|
||||
title="gesamten Chat kopieren"
|
||||
title={t('copyChatTitle')}
|
||||
>
|
||||
{copiedAll ? (
|
||||
<Check className="w-3.5 h-3.5 text-accent" />
|
||||
) : (
|
||||
<Copy className="w-3.5 h-3.5" />
|
||||
)}
|
||||
<span>{copiedAll ? 'Kopiert' : 'Chat kopieren'}</span>
|
||||
<span>{copiedAll ? t('copied') : t('copyChat')}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -336,16 +337,15 @@ export function AISearchResults({
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xl md:text-2xl font-bold text-white/80">
|
||||
Wie kann ich helfen?
|
||||
{t('howCanIHelp')}
|
||||
</p>
|
||||
<p className="text-sm text-white/30 mt-2 max-w-md">
|
||||
Beschreibe dein Projekt, frag nach bestimmten Kabeln, oder nenne mir deine
|
||||
Anforderungen.
|
||||
{t('helpDescription')}
|
||||
</p>
|
||||
</div>
|
||||
{/* Quick prompts */}
|
||||
<div className="flex flex-wrap justify-center gap-2 mt-4">
|
||||
{['Windpark 33kV Verkabelung', 'NYCWY 4x185', 'Erdkabel für Solarpark'].map(
|
||||
{prompts.map(
|
||||
(prompt) => (
|
||||
<button
|
||||
key={prompt}
|
||||
@@ -384,7 +384,7 @@ export function AISearchResults({
|
||||
? 'top-2 right-2 bg-primary/10 hover:bg-primary/20 text-primary/60 hover:text-primary'
|
||||
: 'top-2 right-2 bg-white/5 hover:bg-white/10 text-white/40 hover:text-white'
|
||||
}`}
|
||||
title="Nachricht kopieren"
|
||||
title={t('copyMessage')}
|
||||
>
|
||||
{copiedIndex === index ? (
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
@@ -431,7 +431,7 @@ export function AISearchResults({
|
||||
{msg.role === 'assistant' && msg.products && msg.products.length > 0 && (
|
||||
<div className="mt-4 space-y-2 border-t border-white/[0.06] pt-4">
|
||||
<h4 className="text-[10px] font-bold tracking-widest uppercase text-white/30 mb-2">
|
||||
Empfohlene Produkte
|
||||
{t('recommendedProducts')}
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{msg.products.map((product, idx) => (
|
||||
@@ -509,7 +509,7 @@ export function AISearchResults({
|
||||
<AIOrb isThinking={false} hasError={true} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-red-300">Da ist was schiefgelaufen 😬</h3>
|
||||
<h3 className="text-sm font-bold text-red-300">{t('errorTitle')}</h3>
|
||||
<p className="text-xs text-red-300/60 mt-1">{error}</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -519,7 +519,7 @@ export function AISearchResults({
|
||||
className="flex items-center gap-1.5 text-[10px] font-bold text-red-300/50 hover:text-red-300 mt-2 transition-colors cursor-pointer"
|
||||
>
|
||||
<RotateCcw className="w-3 h-3" />
|
||||
Nochmal versuchen
|
||||
{t('tryAgain')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -544,7 +544,7 @@ export function AISearchResults({
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder="Nachricht eingeben..."
|
||||
placeholder={t('placeholder')}
|
||||
className="flex-1 bg-transparent border-none text-white text-sm md:text-base px-5 py-4 focus:outline-none placeholder:text-white/20"
|
||||
disabled={isLoading}
|
||||
tabIndex={1}
|
||||
@@ -567,20 +567,20 @@ export function AISearchResults({
|
||||
? 'bg-accent text-primary shadow-lg shadow-accent/20 hover:shadow-accent/40 hover:scale-105 active:scale-95'
|
||||
: 'bg-white/5 text-white/20'
|
||||
}`}
|
||||
aria-label="Nachricht senden"
|
||||
aria-label={t('send')}
|
||||
>
|
||||
<ArrowUp className="w-4 h-4" strokeWidth={2.5} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-3 mt-2.5">
|
||||
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15">
|
||||
Enter zum Senden · Esc zum Schließen
|
||||
{t('footerShortcuts')}
|
||||
</span>
|
||||
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15">
|
||||
·
|
||||
</span>
|
||||
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-accent/40 flex items-center gap-1">
|
||||
🛡️ DSGVO-konform · EU-Server
|
||||
{t('footerPrivacy')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
48
components/ui/AnimatedCounter.tsx
Normal file
48
components/ui/AnimatedCounter.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { motion, useMotionValue, useTransform, animate, useInView } from 'framer-motion';
|
||||
|
||||
interface AnimatedCounterProps {
|
||||
value: number;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
suffix?: string;
|
||||
prefix?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AnimatedCounter({
|
||||
value,
|
||||
duration = 2,
|
||||
delay = 0,
|
||||
suffix = '',
|
||||
prefix = '',
|
||||
className = '',
|
||||
}: AnimatedCounterProps) {
|
||||
const count = useMotionValue(0);
|
||||
const rounded = useTransform(count, (latest) => {
|
||||
// Format with thousands separator
|
||||
return new Intl.NumberFormat('de-DE').format(Math.round(latest));
|
||||
});
|
||||
|
||||
const ref = React.useRef(null);
|
||||
const inView = useInView(ref, { once: true, margin: "-100px" });
|
||||
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
const controls = animate(count, value, {
|
||||
duration: duration,
|
||||
delay: delay,
|
||||
ease: 'easeOut',
|
||||
});
|
||||
return controls.stop;
|
||||
}
|
||||
}, [inView, value, duration, delay, count]);
|
||||
|
||||
return (
|
||||
<motion.span ref={ref} className={className}>
|
||||
{prefix}<motion.span>{rounded}</motion.span>{suffix}
|
||||
</motion.span>
|
||||
);
|
||||
}
|
||||
19
components/ui/Tooltip.tsx
Normal file
19
components/ui/Tooltip.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
interface TooltipProps {
|
||||
children: ReactNode;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function Tooltip({ children, content }: TooltipProps) {
|
||||
return (
|
||||
<div className="group relative flex items-center justify-center">
|
||||
{children}
|
||||
<div className="absolute bottom-full mb-3 invisible opacity-0 translate-y-2 group-hover:visible group-hover:opacity-100 group-hover:translate-y-0 w-max max-w-[250px] px-4 py-2.5 text-[11px] md:text-xs text-white bg-neutral-900/95 backdrop-blur-md rounded-xl shadow-2xl border border-white/10 transition-all duration-300 pointer-events-none z-50 text-center leading-relaxed font-normal normal-case tracking-normal">
|
||||
{content}
|
||||
{/* Triangle Arrow */}
|
||||
<div className="absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-neutral-900/95" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,5 +7,6 @@ export * from './Card';
|
||||
export * from './Badge';
|
||||
export * from './Input';
|
||||
export * from './Textarea';
|
||||
export * from './AnimatedCounter';
|
||||
export * from './Label';
|
||||
export * from './Callout';
|
||||
|
||||
@@ -1,22 +1,59 @@
|
||||
---
|
||||
title: "Allgemeine Geschäftsbedingungen"
|
||||
date: "2024-03-20"
|
||||
date: "2026-05-28"
|
||||
excerpt: "Die Allgemeinen Geschäftsbedingungen der E-TIB GmbH für unsere Leistungen und Lieferungen."
|
||||
---
|
||||
|
||||
# Allgemeine Geschäftsbedingungen (AGB)
|
||||
|
||||
## 1. Geltungsbereich
|
||||
Diese Allgemeinen Geschäftsbedingungen gelten für alle Verträge, Lieferungen und sonstigen Leistungen der E-TIB GmbH gegenüber ihren Kunden.
|
||||
## 1. Geltungsbereich und Vertragsabschluss
|
||||
1. Diese Allgemeinen Geschäftsbedingungen (AGB) gelten ausschließlich für alle Verträge, Lieferungen und sonstigen Leistungen (einschließlich Planungs-, Ingenieur-, Kabeltiefbau-, Horizontalspülbohrungs- und Glasfasermontageleistungen) der E-TIB GmbH (nachfolgend „E-TIB“ genannt) gegenüber ihren Vertragspartnern (nachfolgend „Kunde“ genannt).
|
||||
2. Diese AGB gelten ausschließlich im B2B-Verkehr. Sie richten sich ausschließlich an Unternehmer im Sinne des § 14 BGB, juristische Personen des öffentlichen Rechts oder öffentlich-rechtliche Sondervermögen.
|
||||
3. Entgegenstehende, abweichende oder ergänzende Bedingungen des Kunden werden nicht Vertragsbestandteil, es sei denn, E-TIB stimmt ihrer Geltung ausdrücklich schriftlich zu.
|
||||
4. Ein Vertrag kommt erst durch die schriftliche Auftragsbestätigung von E-TIB oder durch den tatsächlichen Beginn der Ausführung der Leistungen durch E-TIB zustande.
|
||||
|
||||
## 2. Vertragsabschluss
|
||||
Ein Vertrag kommt erst durch unsere schriftliche Auftragsbestätigung oder durch die Ausführung des Auftrags zustande.
|
||||
## 2. Leistungsumfang und Mitwirkungspflichten des Kunden
|
||||
1. Der genaue Umfang der von E-TIB zu erbringenden Leistungen ergibt sich aus dem jeweiligen Angebot bzw. der schriftlichen Auftragsbestätigung und den darin vereinbarten Spezifikationen.
|
||||
2. Der Kunde ist verpflichtet, E-TIB bei der Erbringung der Leistungen vollumfänglich und auf eigene Kosten zu unterstützen. Dies umfasst insbesondere:
|
||||
- Die Bereitstellung aller für die Planung und Ausführung erforderlichen Unterlagen, Pläne (insbesondere Sparten- und Leitungspläne), Genehmigungen und behördlichen Freigaben rechtzeitig vor Baubeginn.
|
||||
- Die genaue und verbindliche Einweisung in die örtlichen Gegebenheiten sowie die genaue Kennzeichnung und Einmessung aller im Arbeitsbereich verlaufenden unterirdischen Leitungen, Rohre, Kanäle und sonstigen Hindernisse.
|
||||
- Die Sicherstellung des freien und ungehinderten Zugangs zum Baustellengelände sowie die Bereitstellung eventuell erforderlicher Lager- und Einrichtungsflächen.
|
||||
- Die Bereitstellung von Bauwasser und Baustrom in der erforderlichen Menge und Qualität an den Arbeitsstellen auf Kosten des Kunden.
|
||||
3. Versäumt der Kunde eine Mitwirkungspflicht oder erteilt fehlerhafte Auskünfte (insbesondere bezüglich der Lage unterirdischer Leitungen), haftet er E-TIB für alle daraus resultierenden Verzögerungen, Mehrkosten und Schäden. E-TIB ist in diesem Fall von der Haftung für Beschädigungen an nicht oder fehlerhaft eingezeichneten Leitungen freigestellt.
|
||||
|
||||
## 3. Preise und Zahlungsbedingungen
|
||||
Alle Preise verstehen sich in Euro zzgl. der gesetzlichen Mehrwertsteuer. Zahlungen sind innerhalb von 14 Tagen nach Rechnungsstellung ohne Abzug zu leisten.
|
||||
## 3. Ausführungsfristen, Behinderung und Bauverzögerungen
|
||||
1. Vereinbarte Ausführungsfristen und Termine sind nur dann verbindlich, wenn sie von E-TIB ausdrücklich schriftlich als fix bestätigt wurden.
|
||||
2. Die Einhaltung der Fristen setzt die rechtzeitige Erfüllung aller Mitwirkungspflichten des Kunden sowie den Eingang vereinbarter Anzahlungen voraus.
|
||||
3. Leistungsverzögerungen aufgrund höherer Gewalt, Streiks, Aussperrungen, behördlicher Anordnungen oder unvorhersehbarer, von E-TIB nicht zu vertretender Erschwernisse (z. B. extreme Witterungsverhältnisse, unvorhergesehene Bodenklassen wie massiver Fels, unvollständige oder fehlerhafte Spartenpläne des Netzbetreibers) verlängern die Ausführungsfristen um die Dauer der Behinderung zzgl. einer angemessenen Anlaufzeit.
|
||||
4. Führen unvorhergesehene geologische Hindernisse oder Abweichungen der tatsächlichen Bodenklasse von den vertraglich zugrunde gelegten Bodenklassen zu einem erheblichen Mehraufwand (z. B. bei Horizontalspülbohrungen), ist E-TIB berechtigt, eine angemessene Anpassung der Vergütung sowie der Ausführungsfristen zu verlangen.
|
||||
|
||||
## 4. Haftung
|
||||
Wir haften für Schäden nur bei Vorsatz oder grober Fahrlässigkeit. Die Haftung für leichte Fahrlässigkeit ist ausgeschlossen, sofern keine wesentlichen Vertragspflichten verletzt wurden.
|
||||
## 4. Abnahme, Gefahrübergang und Gewährleistung
|
||||
1. Die Abnahme der Leistungen erfolgt unverzüglich nach Fertigstellung. Auf Verlangen von E-TIB sind auch in sich abgeschlossene Teile der Leistung (z. B. fertiggestellte Kabelgräben oder einzelne Bohrabschnitte) separat abzunehmen (Teilabnahme).
|
||||
2. Nimmt der Kunde die Leistung nicht innerhalb von 12 Werktagen nach schriftlicher Anzeige der Fertigstellung ab, oder nimmt er die Leistung in Benutzung (z. B. durch Inbetriebnahme der verlegten Leitungen), gilt die Abnahme nach Ablauf dieser Frist als erfolgt.
|
||||
3. Die Gefahr des zufälligen Untergangs und der zufälligen Verschlechterung geht mit der Abnahme bzw. Teilabnahme auf den Kunden über.
|
||||
4. E-TIB leistet Gewähr nach den gesetzlichen Bestimmungen, soweit nachfolgend nichts anderes vereinbart ist. Die Verjährungsfrist für Mängelansprüche bei Bauwerken und damit zusammenhängenden Planungs- und Überwachungsleistungen beträgt 5 Jahre ab Abnahme. Bei sonstigen Werkleistungen beträgt sie 1 Jahr ab Abnahme.
|
||||
5. Offensichtliche Mängel müssen E-TIB unverzüglich, spätestens jedoch innerhalb von 5 Werktagen nach Abnahme, schriftlich angezeigt werden. Spätere Mängelrügen sind ausgeschlossen.
|
||||
|
||||
## 5. Gerichtsstand
|
||||
Gerichtsstand für alle Streitigkeiten aus dem Vertragsverhältnis ist Cottbus, sofern der Kunde Kaufmann ist.
|
||||
## 5. Preise, Abschlagszahlungen und Zahlungsbedingungen
|
||||
1. Alle Preise verstehen sich in Euro netto zuzüglich der jeweils gültigen gesetzlichen Umsatzsteuer.
|
||||
2. E-TIB ist berechtigt, dem Baufortschritt entsprechend angemessene Abschlagszahlungen (z. B. nach erbrachten Trassenmetern, Bohrmetern oder abgeschlossenen Planungsphasen) in Rechnung zu stellen. Diese sind innerhalb von 7 Tagen ab Rechnungsdatum ohne Abzug zur Zahlung fällig.
|
||||
3. Schlussrechnungen sind innerhalb von 14 Tagen nach Rechnungsstellung und Abnahme ohne Abzug zur Zahlung fällig.
|
||||
4. Bei Zahlungsverzug des Kunden ist E-TIB berechtigt, Verzugszinsen in gesetzlicher Höhe (derzeit 9 Prozentpunkte über dem jeweiligen Basiszinssatz) zu verlangen. Zudem behält sich E-TIB das Recht vor, weitere Arbeiten bis zur vollständigen Begleichung der fälligen Forderungen einzustellen (Leistungsverweigerungsrecht).
|
||||
|
||||
## 6. Eigentumsvorbehalt
|
||||
1. E-TIB behält sich das Eigentum an allen gelieferten und eingebauten Materialien, Kabeln, Rohren und sonstigen Komponenten (Vorbehaltsware) bis zur vollständigen Erfüllung sämtlicher Forderungen aus dem jeweiligen Vertrag vor.
|
||||
2. Der Kunde ist verpflichtet, die Vorbehaltsware pfleglich zu behandeln und E-TIB unverzüglich über Zugriffe Dritter (z. B. Pfändungen) schriftlich zu informieren.
|
||||
3. Bei Verbindung oder Vermischung der Vorbehaltsware mit anderen Gegenständen erwirbt E-TIB Miteigentum an der neuen Sache im Verhältnis des Rechnungswertes der Vorbehaltsware zum Wert der anderen verbundenen Gegenstände zum Zeitpunkt der Verbindung.
|
||||
|
||||
## 7. Haftung und Haftungsbeschränkungen
|
||||
1. E-TIB haftet unbeschränkt bei Vorsatz, grober Fahrlässigkeit sowie bei Verletzung von Leben, Körper oder Gesundheit.
|
||||
2. Bei leicht fahrlässiger Verletzung wesentlicher Vertragspflichten (Kardinalpflichten) ist die Haftung von E-TIB auf den vertragstypischen, vorhersehbaren Schaden begrenzt. Wesentliche Vertragspflichten sind solche, deren Erfüllung die ordnungsgemäße Durchführung des Vertrages überhaupt erst ermöglicht und auf deren Einhaltung der Kunde regelmäßig vertrauen darf.
|
||||
3. Die Haftung für leichte Fahrlässigkeit bei der Verletzung nicht wesentlicher Vertragspflichten ist ausgeschlossen.
|
||||
4. E-TIB haftet nicht für Schäden, die auf unvollständigen, veralteten oder ungenauen Spartenplänen des Kunden oder der örtlichen Netzbetreiber beruhen, sofern E-TIB die Unvollständigkeit nicht vorsätzlich oder grob fahrlässig hätte erkennen müssen.
|
||||
5. Die vorstehenden Haftungsbeschränkungen gelten auch zugunsten der Angestellten, Vertreter und Erfüllungsgehilfen von E-TIB.
|
||||
|
||||
## 8. Gerichtsstand und Schlussbestimmungen
|
||||
1. Es gilt das Recht der Bundesrepublik Deutschland unter Ausschluss des UN-Kaufrechts (CISG).
|
||||
2. Erfüllungsort für alle Lieferungen und Leistungen sowie Zahlungsort ist der Sitz von E-TIB in Guben.
|
||||
3. Ausschließlicher Gerichtsstand für alle Streitigkeiten aus oder im Zusammenhang mit diesem Vertragsverhältnis ist Cottbus, sofern der Kunde Kaufmann, juristische Person des öffentlichen Rechts oder ein öffentlich-rechtliches Sondervermögen ist. E-TIB ist jedoch berechtigt, den Kunden auch an dessen allgemeinem Gerichtsstand zu verklagen.
|
||||
4. Sollte eine Bestimmung dieser AGB unwirksam oder undurchführbar sein oder werden, so wird dadurch die Wirksamkeit der übrigen Bestimmungen nicht berührt. Die unwirksame Bestimmung ist durch eine wirksame Bestimmung zu ersetzen, die dem wirtschaftlichen Zweck der unwirksamen Bestimmung am nächsten kommt.
|
||||
|
||||
@@ -29,6 +29,8 @@ layout: "fullBleed"
|
||||
Mit unserem spezialisierten Gerätepark beherrschen wir die unterschiedlichsten Verfahren. Beim **Horizontalspülbohrverfahren (HDD)** steuern wir den Bohrkopf punktgenau unter der Erde hindurch. Wir realisieren dabei Bohrlängen von bis zu 250 Metern am Stück und können Schutzrohre mit einem Durchmesser von bis zu 400 mm einziehen.
|
||||
|
||||
Für kürzere Distanzen, wie beispielsweise schnelle und schonende Hausanschlüsse, setzen wir unsere **Erdraketen** (Bodenverdrängungsverfahren) ein. Hierbei erreichen wir Durchörterungslängen von bis zu 15 Metern für Rohre bis 160 mm Durchmesser – ideal um Vorgärten und Einfahrten komplett unversehrt zu lassen.
|
||||
|
||||
<DeepDrillAnimation />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
|
||||
@@ -143,5 +143,7 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
||||
]}
|
||||
/>
|
||||
|
||||
<ScaleOfImpact />
|
||||
|
||||
<HomeReferencesSlider />
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ layout: "fullBleed"
|
||||
Durch unsere langjährige Erfahrung erkennen wir Problemstellungen frühzeitig und erarbeiten für unsere Auftraggeber im Vorfeld sichere und zufriedenstellende Lösungen. Die E-TIB GmbH hat sich voll und ganz auf den Kabelleitungstiefbau spezialisiert.
|
||||
|
||||
Wir bieten Ihnen das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand – mit eigenem, hochmodernem Maschinenpark und festangestelltem Fachpersonal aus der Region. In direkter Zusammenarbeit mit unserem Partnerbüro, der E-TIB Ingenieurgesellschaft mbH, können wir zudem frühzeitig Planungsfehler ausschließen und selbst die komplexesten Dokumentationsanforderungen souverän erfüllen.
|
||||
|
||||
<DataGridPulse />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
|
||||
@@ -24,73 +24,73 @@ layout: "fullBleed"
|
||||
{
|
||||
id: "region",
|
||||
title: "Spannende Bauvorhaben",
|
||||
description: "Einsatz bei interessanten regionalen und überregionalen Projekten.",
|
||||
description: "Einsatz bei regionalen und überregionalen Bauvorhaben.",
|
||||
icon: "region"
|
||||
},
|
||||
{
|
||||
id: "contract",
|
||||
title: "Unbefristeter Vertrag",
|
||||
description: "Sicherer Arbeitsplatz durch einen unbefristeten Arbeitsvertrag in Vollzeit.",
|
||||
description: "Sicherer, unbefristeter Arbeitsvertrag in Vollzeit.",
|
||||
icon: "contract"
|
||||
},
|
||||
{
|
||||
id: "money",
|
||||
title: "Faire Vergütung",
|
||||
description: "Leistungsbezogene, überdurchschnittliche und faire Bezahlung.",
|
||||
description: "Leistungsbezogene und faire Vergütung für Ihre Arbeit.",
|
||||
icon: "money"
|
||||
},
|
||||
{
|
||||
id: "allowance",
|
||||
title: "Tägliche Auslöse",
|
||||
description: "Wir zahlen eine steuerfreie Auslöse von bis zu 30,00 EUR pro Arbeitstag.",
|
||||
description: "Auslöse von bis zu 30,00 EUR pro Arbeitstag.",
|
||||
icon: "allowance"
|
||||
},
|
||||
{
|
||||
id: "vacation",
|
||||
title: "31 Tage Urlaub",
|
||||
description: "31 Tage Jahresurlaub sowie zusätzliches Urlaubsgeld zur Erholung.",
|
||||
description: "31 Tage Jahresurlaub sowie zusätzliches Urlaubsgeld.",
|
||||
icon: "vacation"
|
||||
},
|
||||
{
|
||||
id: "clothes",
|
||||
title: "Premium Arbeitskleidung",
|
||||
description: "Hochwertige Engelbert-Strauss Arbeitskleidung wird komplett gestellt.",
|
||||
title: "Marken-Arbeitskleidung",
|
||||
description: "Bereitstellung von hochwertiger Engelbert-Strauss Arbeitskleidung.",
|
||||
icon: "clothes"
|
||||
},
|
||||
{
|
||||
id: "card",
|
||||
title: "Zusatzzahlungen",
|
||||
description: "Monatliche Boni über eine persönliche MasterCard.",
|
||||
description: "Monatliche Zusatzzahlungen über eine persönliche MasterCard.",
|
||||
icon: "card"
|
||||
},
|
||||
{
|
||||
id: "retirement",
|
||||
title: "Altersvorsorge",
|
||||
description: "Attraktive Zuzahlung im Rahmen der betrieblichen Altersvorsorge.",
|
||||
description: "Zuzahlung im Rahmen der betrieblichen Altersvorsorge.",
|
||||
icon: "retirement"
|
||||
},
|
||||
{
|
||||
id: "education",
|
||||
title: "Weiterbildung",
|
||||
description: "Individuelle Entwicklungs- und Aufstiegsmöglichkeiten im Unternehmen.",
|
||||
description: "Individuelle Weiterbildungs- und Entwicklungsmöglichkeiten.",
|
||||
icon: "education"
|
||||
},
|
||||
{
|
||||
id: "car",
|
||||
title: "Firmenfahrzeug",
|
||||
description: "Stellung eines Fahrzeugs zur betrieblichen Nutzung und für Baustellen.",
|
||||
description: "Stellung eines Fahrzeugs zur betrieblichen Nutzung und Fahrten zur Baustelle.",
|
||||
icon: "car"
|
||||
},
|
||||
{
|
||||
id: "team",
|
||||
title: "Intensive Einarbeitung",
|
||||
description: "Professionelles Onboarding in einem hilfsbereiten und engagierten Team.",
|
||||
description: "Entsprechende Einarbeitung in einem engagierten Team.",
|
||||
icon: "team"
|
||||
},
|
||||
{
|
||||
id: "family",
|
||||
title: "Familiäres Klima",
|
||||
description: "Starker Zusammenhalt durch regelmäßige betrieblich organisierte Events.",
|
||||
description: "Familiäres Betriebsklima mit betrieblich organisierten Veranstaltungen.",
|
||||
icon: "family"
|
||||
}
|
||||
]}
|
||||
|
||||
@@ -19,9 +19,9 @@ layout: "fullBleed"
|
||||
showFairs={true}
|
||||
showJobs={false}
|
||||
fairs={[
|
||||
{ name: 'Intersolar München', date: '2024 / 2025', type: 'Messe', location: 'München' },
|
||||
{ name: 'Windenergietage Linstow', date: '2024 / 2025', type: 'Fachkongress', location: 'Linstow' },
|
||||
{ name: 'Kabel-Workshop Wiesbaden', date: '2024 / 2025', type: 'Fachmesse', location: 'Wiesbaden' }
|
||||
{ name: 'Intersolar München', date: '23. - 25. Juni 2026', type: 'Messe', location: 'München', url: 'https://www.intersolar.de/' },
|
||||
{ name: 'Windenergietage Linstow', date: '10. - 12. November 2026', type: 'Fachkongress', location: 'Linstow', url: 'https://www.windenergietage.de/' },
|
||||
{ name: 'Werkstatt Kabel 2026', date: '24. - 25. November 2026', type: 'Fachtagung', location: 'Wiesbaden' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -52,9 +52,9 @@ layout: "fullBleed"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CompanyTimeline />
|
||||
<InteractiveGermanyMap isHero={true} badge="Bundesweite Präsenz" title="Infrastruktur für ein ganzes Land" description="Seit 2023 haben wir in über 100 Projekten quer durch Deutschland bewiesen, dass wir Infrastrukturprojekte jeder Größenordnung verlässlich umsetzen." />
|
||||
|
||||
<InteractiveGermanyMap />
|
||||
<CompanyTimeline />
|
||||
|
||||
<div id="struktur">
|
||||
<HomeSubCompanyTiles
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
title: "Terms and Conditions"
|
||||
date: "2024-03-20"
|
||||
excerpt: "The General Terms and Conditions of E-TIB GmbH for our services and deliveries."
|
||||
---
|
||||
|
||||
# Terms and Conditions (T&C)
|
||||
|
||||
## 1. Scope
|
||||
These General Terms and Conditions apply to all contracts, deliveries, and other services provided by E-TIB GmbH to its customers.
|
||||
|
||||
## 2. Conclusion of Contract
|
||||
A contract is only formed upon our written order confirmation or by the execution of the order.
|
||||
|
||||
## 3. Prices and Payment Terms
|
||||
All prices are in Euro plus the statutory value-added tax. Payments are to be made within 14 days of the invoice date without any deduction.
|
||||
|
||||
## 4. Liability
|
||||
We are liable for damages only in cases of intent or gross negligence. Liability for slight negligence is excluded, provided that no essential contractual obligations have been violated.
|
||||
|
||||
## 5. Jurisdiction
|
||||
The place of jurisdiction for all disputes arising from the contractual relationship is Cottbus, provided the customer is a merchant.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Trenchless Line Laying"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Precise horizontal directional drilling and earth rockets in all soil classes for surface-friendly and trenchless line laying."
|
||||
excerpt: "Precise horizontal directional drilling and earth rockets in all soil classes for surface-friendly and efficient line laying."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,15 +12,32 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
When traditional open-cut excavation is restricted by space, ecological standards, or cost constraints, trenchless engineering becomes the superior choice. We safeguard the environment while driving down surface restoration costs.
|
||||
</blockquote>
|
||||
|
||||
## Maximum Efficiency, Minimal Impact
|
||||
|
||||
Trenchless utility laying represents the pinnacle of smart infrastructure deployment. This methodology demonstrates its absolute superiority when navigating busy transportation corridors, active rail tracks, highways, or sensitive ecological waterways. By keeping traffic flowing and preserving the natural topography, we completely eliminate the need to break open expensive paved surfaces or asphalt.
|
||||
|
||||
## HDD Directional Drilling & Pneumatic Earth Rockets
|
||||
|
||||
Armed with a state-of-the-art, specialized fleet of drilling rigs, we master complex subterranean conditions. Utilizing the **Horizontal Directional Drilling (HDD)** method, we guide the drill head with centimeter-level accuracy beneath obstacles. This enables us to achieve continuous drilling lengths of up to 250 meters in a single run, pulling in high-durability protective conduits with diameters up to 400 mm.
|
||||
|
||||
For localized, rapid deployments—such as residential service connections—we deploy high-speed **pneumatic earth rockets** (soil displacement). This method allows us to complete under-crossings of up to 15 meters for conduits up to 160 mm in diameter, ensuring pristine driveways, manicured gardens, and pedestrian walks remain completely undisturbed.
|
||||
|
||||
<DeepDrillAnimation />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Procedures"
|
||||
title="Efficient & Environmentally Friendly"
|
||||
title="Our Drilling Technology in Detail"
|
||||
descriptionParagraphs={[
|
||||
"If classic trench construction is out of the question for reasons of space, environment, or cost, we rely on **state-of-the-art trenchless techniques**.",
|
||||
"This not only protects nature but also significantly saves on restoration costs for complex surfaces."
|
||||
"We select the exactly matching drilling method for each soil class and local condition to guarantee maximum economic efficiency."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
@@ -32,29 +49,33 @@ layout: "fullBleed"
|
||||
[
|
||||
"Max. drilling length: 250 m",
|
||||
"Max. diameter: 400mm pipe",
|
||||
"Complex crossing of railways, highways & water bodies"
|
||||
"Precision control and steerability",
|
||||
"Crossing of railways, highways & water bodies"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "erdrakete",
|
||||
title: "Earth Rocket",
|
||||
title: "Soil Displacement (Earth Rocket)",
|
||||
icon: "hdd",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Max. crossing length: 15 m",
|
||||
"Max. diameter: 160mm pipe",
|
||||
"Fast house connections without trenches"
|
||||
"Fast house connections without trenches",
|
||||
"No surface opening necessary"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Completed Drilling Projects" description="Successful HDD drilling and trenchless cable laying under the most complex conditions." />
|
||||
|
||||
<CallToAction
|
||||
title="Looking for a trenchless solution?"
|
||||
text="We would be happy to advise you on the possibilities of horizontal directional drilling for your project."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Cable Civil Engineering in Telecommunications"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Future-proof infrastructure through competent cable civil engineering for fiber optic and telecommunications networks."
|
||||
excerpt: "Future-proof infrastructure through competent cable civil engineering for fiber optic and telecommunications networks (FTTX)."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,15 +12,30 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC01129.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
Modern life depends entirely on seamless energy and ultra-fast connectivity. By building robust, high-performance physical networks, we lay the groundwork for a truly connected future.
|
||||
</blockquote>
|
||||
|
||||
## The Digital Backbone: Precision-Driven Fiber Expansion
|
||||
|
||||
High-speed fiber optics and next-generation telecommunications networks demand a zero-tolerance approach to execution quality. At E-TIB, we mobilize highly trained technical teams and an advanced, company-owned fleet of machinery to deliver flawless broadband expansion across the country.
|
||||
|
||||
## FTTX Deployment: A Seamless, End-to-End Solution
|
||||
|
||||
As a trusted, tier-one partner for leading telecommunications and grid providers, we cover the entire physical lifecycle of network creation. From excavating precise cable and utility trenches to laying complete, high-capacity conduit paths, we manage every phase of civil engineering. We also handle the installation and positioning of strategic multi-function enclosures (MFG) at critical network hub points.
|
||||
|
||||
Our expertise goes far beyond standard civil works. We perform the specialized pulling and pneumatic blowing of highly sensitive fiber optic and telecommunication cables over expansive, complex distances. Combining this with state-of-the-art splicing, testing, and cable assembly, we ensure your digital infrastructure operates with maximum throughput, security, and long-term reliability.
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Service Portfolio"
|
||||
title="Backbone of Digitalization"
|
||||
title="Complete Fiber Optic Expansion"
|
||||
descriptionParagraphs={[
|
||||
"Fast internet and state-of-the-art telecommunications networks require a **smooth and professional network expansion**.",
|
||||
"With state-of-the-art machinery and specialized assembly teams, E-TIB ensures the highest quality in fiber optic expansion."
|
||||
"From the empty conduit route to the finished fiber optic assembly - we deliver turnkey telecommunication infrastructure at the highest level."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
@@ -31,8 +46,9 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Production of cable and pipe trenches",
|
||||
"Production of empty conduit routes",
|
||||
"Setting up multi-function enclosures"
|
||||
"Production of complete empty conduit routes",
|
||||
"Setting up multi-function enclosures (MFG)",
|
||||
"Network node connection"
|
||||
]
|
||||
]
|
||||
},
|
||||
@@ -44,17 +60,20 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Pulling of TK/FO cables",
|
||||
"Blowing in of fiber optic cables",
|
||||
"Cable assembly TK/FO"
|
||||
"Pneumatic blowing in of fiber optic cables",
|
||||
"Cable assembly TK/FO",
|
||||
"Splicing and measuring work"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Completed Broadband Projects" description="We have already successfully realized hundreds of kilometers of FTTX networks. Discover our references." />
|
||||
|
||||
<CallToAction
|
||||
title="Are you planning an FTTX expansion?"
|
||||
text="We are happy to advise you on all aspects of FTTX and fiber optic expansion."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -114,6 +114,7 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
||||
description: "Installation of medium and low voltage cables",
|
||||
tag: "Energy",
|
||||
size: "large",
|
||||
href: "/en/cable-civil-engineering",
|
||||
image: { url: "/assets/photos/DSC01123.JPG", alt: "Cable Construction" }
|
||||
},
|
||||
{
|
||||
@@ -121,6 +122,7 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
||||
description: "Horizontal directional drilling for trenchless installation",
|
||||
tag: "Innovation",
|
||||
size: "medium",
|
||||
href: "/en/drilling-technology",
|
||||
image: { url: "/assets/photos/DSC08653.JPG", alt: "Drilling Technology" }
|
||||
},
|
||||
{
|
||||
@@ -128,15 +130,19 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
||||
description: "Route planning and approval procedures",
|
||||
tag: "Precision",
|
||||
size: "small",
|
||||
href: "/en/planning",
|
||||
image: { url: "/assets/photos/DJI_0048.JPG", alt: "Planning" }
|
||||
},
|
||||
{
|
||||
title: "Surveying",
|
||||
description: "Digital as-built documentation",
|
||||
tag: "Digital",
|
||||
size: "accent"
|
||||
size: "accent",
|
||||
href: "/en/surveying"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<ScaleOfImpact />
|
||||
|
||||
<HomeReferencesSlider />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Cable Civil Engineering in Medium and Low Voltage"
|
||||
title: "Cable Civil Engineering"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Professional cable civil engineering for medium and low voltage networks: From the construction of cable routes to cable assemblies and street lighting."
|
||||
excerpt: "Professional cable civil engineering for medium and low voltage networks. We guarantee maximum safety standards and seamless documentation for your project."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,15 +12,34 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
While underground grid construction is expanding rapidly, execution quality varies widely. We counter this with elite craftsmanship, strict safety protocols, and ironclad documentation—ensuring your infrastructure project remains an asset, not a liability.
|
||||
</blockquote>
|
||||
|
||||
## Precision over Compromise: Preventing Infrastructure Failures
|
||||
|
||||
Modern civil engineering is plagued by low-cost contractors who routinely **compromise on safety margins and technical documentation**. The repercussions are severe: catastrophic utility strikes (power, water, gas, telecom), costly project delays, and massive budget overruns. What begins as a low-bid shortcut quickly turns into a financial and legal nightmare.
|
||||
|
||||
Furthermore, public or subsidized infrastructure projects require meticulously structured, standard-compliant documentation. Without flawless records, the release of critical funding and subsidies is regularly delayed or denied.
|
||||
|
||||
## Integrated Excellence from a Single Source
|
||||
|
||||
At E-TIB, we leverage decades of collective expertise to anticipate structural and geological bottlenecks long before the first shovel hits the ground. Our specialization in cable civil engineering guarantees that every deployment runs with absolute precision.
|
||||
|
||||
We deliver turnkey cable routes entirely in-house. Our operations are powered by a cutting-edge fleet of heavy machinery and staffed exclusively by highly trained, permanent regional professionals. Working in close synergy with our sister firm, **E-TIB Ingenieurgesellschaft mbH**, we eliminate planning oversights at the inception phase and effortlessly handle the most complex GIS and administrative documentation requirements in the industry.
|
||||
|
||||
<DataGridPulse />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Service Portfolio"
|
||||
title="Everything for your network"
|
||||
title="Everything for Your Network"
|
||||
descriptionParagraphs={[
|
||||
"Our services cover the **complete spectrum** for the construction of modern cable routes and energy infrastructure.",
|
||||
"We offer the highest precision, modern machines, and certified experts for every project."
|
||||
"We offer the highest precision, modern machines and certified experts for every civil engineering project - from cable laying to transformer stations."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
@@ -31,31 +50,34 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Production of cable and pipe trenches",
|
||||
"Cable laying from 0.4 kV - 110 kV",
|
||||
"Cable assembly 0.4 kV - 30 kV"
|
||||
"Cable laying from 0.4 kV to 110 kV",
|
||||
"Cable assembly from 0.4 kV to 30 kV",
|
||||
"Cable plowing works for fast laying"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "anlagen",
|
||||
title: "Systems & Lighting",
|
||||
title: "Systems & Infrastructure",
|
||||
icon: "energy",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Construction of transformer stations",
|
||||
"Establishment of grounding systems",
|
||||
"Establishment of complex grounding systems",
|
||||
"Erection of street lighting",
|
||||
"Switching operations"
|
||||
"Certified switching operations"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Completed Civil Engineering Projects" description="We have laid hundreds of kilometers of cables. Take a look at our reference projects." />
|
||||
|
||||
<CallToAction
|
||||
title="Do you have a specific project?"
|
||||
text="We would be happy to advise you on all questions regarding cable civil engineering and line laying."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -100,6 +100,6 @@ layout: "fullBleed"
|
||||
title="Speculative Application (m/f/d)"
|
||||
description="Can you imagine taking on a long-term challenge in our company but can't find a suitable job offer? Apply speculatively."
|
||||
ctaLabel="To the contact form"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
theme="dark"
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@ layout: "fullBleed"
|
||||
description="As a full-service partner in the field of cable civil engineering, we cover the entire spectrum of modern infrastructure projects."
|
||||
videoUrl="/assets/videos/web/hero-bohrung.mp4"
|
||||
linkText="Get in Touch"
|
||||
linkHref="/en/kontakt"
|
||||
linkHref="/en/contact"
|
||||
secondaryCtaLabel="View References"
|
||||
secondaryCtaHref="/en/#referenzen"
|
||||
/>
|
||||
@@ -20,14 +20,14 @@ layout: "fullBleed"
|
||||
badge="Core Competencies"
|
||||
title="Precision & Performance"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
items={[
|
||||
{
|
||||
title: "Cable Construction",
|
||||
description: "Classic trench construction and professional laying of medium and low voltage cables.",
|
||||
tag: "Energy",
|
||||
size: "large",
|
||||
href: "/en/kabeltiefbau",
|
||||
href: "/en/cable-civil-engineering",
|
||||
image: { url: "/assets/photos/DSC01123.JPG", alt: "Cable Construction" }
|
||||
},
|
||||
{
|
||||
@@ -35,7 +35,7 @@ layout: "fullBleed"
|
||||
description: "Horizontal directional drilling (HDD) for trenchless, surface-friendly installation.",
|
||||
tag: "Innovation",
|
||||
size: "medium",
|
||||
href: "/en/bohrtechnik",
|
||||
href: "/en/drilling-technology",
|
||||
image: { url: "/assets/photos/DSC08653.JPG", alt: "Drilling Technology" }
|
||||
},
|
||||
{
|
||||
@@ -43,7 +43,7 @@ layout: "fullBleed"
|
||||
description: "Expansion of future-proof broadband networks (FTTX).",
|
||||
tag: "Communication",
|
||||
size: "small",
|
||||
href: "/en/glasfaser",
|
||||
href: "/en/fiber-optics",
|
||||
image: { url: "/assets/photos/DSC01129.JPG", alt: "Fiber Optics" }
|
||||
},
|
||||
{
|
||||
@@ -51,7 +51,7 @@ layout: "fullBleed"
|
||||
description: "Route planning, approval procedures, and precise GIS documentation.",
|
||||
tag: "Digital",
|
||||
size: "accent",
|
||||
href: "/en/planung"
|
||||
href: "/en/planning"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@ layout: "fullBleed"
|
||||
description="Get to know the E-TIB Group at the leading industry trade fairs."
|
||||
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
|
||||
linkText="Get in Touch"
|
||||
linkHref="/en/kontakt"
|
||||
linkHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div id="termine">
|
||||
@@ -19,9 +19,9 @@ layout: "fullBleed"
|
||||
showFairs={true}
|
||||
showJobs={false}
|
||||
fairs={[
|
||||
{ name: 'Intersolar Munich', date: '2024 / 2025', type: 'Fair', location: 'Munich' },
|
||||
{ name: 'Wind Energy Days Linstow', date: '2024 / 2025', type: 'Specialist Congress', location: 'Linstow' },
|
||||
{ name: 'Cable Workshop Wiesbaden', date: '2024 / 2025', type: 'Trade Fair', location: 'Wiesbaden' }
|
||||
{ name: 'Intersolar Munich', date: 'June 23–25, 2026', type: 'Fair', location: 'Munich', url: 'https://www.intersolar.de/' },
|
||||
{ name: 'Wind Energy Days Linstow', date: 'November 10–12, 2026', type: 'Specialist Congress', location: 'Linstow', url: 'https://www.windenergietage.de/' },
|
||||
{ name: 'Werkstatt Kabel 2026', date: 'November 24–25, 2026', type: 'Symposium', location: 'Wiesbaden' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Planning and Consulting"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Professional planning and consulting in the field of energy and telecommunications networks: route planning, approval procedures, and project review."
|
||||
excerpt: "Professional planning and consulting in the field of energy and telecommunications networks: route planning, approval procedures and project control from a single source."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,51 +12,68 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DJI_0048.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
We offer our clients unified, end-to-end project orchestration—from raw feasibility mapping to technical execution and grid connection for utility-scale renewable energy assets.
|
||||
</blockquote>
|
||||
|
||||
## The Foundation of Project Viability
|
||||
|
||||
A rigorous engineering layout is the cornerstone of every successful infrastructure initiative. Many large-scale utility projects encounter critical delays or structural failures simply because the nuances of subterranean logistics and regulatory approvals were underestimated. By bridging the gap between design and physical field operations, we proactively mitigate risk, eliminating friction at critical operational interfaces.
|
||||
|
||||
Whether executing fast-paced broadband fiber rollouts or designing complex grid connections for gigawatt-scale wind and solar farms, precision is mandatory. Our engineering teams perform comprehensive on-site route audits, map potential utility conflicts early, and guarantee all localized regulatory, environmental, and municipal permits are prepared and filed seamlessly.
|
||||
|
||||
## Elite Engineering & Technical Advisory
|
||||
|
||||
In strategic partnership with **E-TIB Ingenieurgesellschaft mbH**, we guide your development through every single phase of the project lifecycle. From initial corridor scouting and route optimization to obtaining formal municipal approvals and producing final, field-ready execution plans—we provide the strategic oversight and specialized engineering supervision needed to ensure a flawless handover.
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Engineering Services"
|
||||
title="Foundation for Success"
|
||||
title="Our Planning Services"
|
||||
descriptionParagraphs={[
|
||||
"Solid planning is the foundation of every successful construction project. Avoid expensive planning errors and benefit from our comprehensive expertise.",
|
||||
"Our partner engineering firm **E-TIB Ingenieurgesellschaft mbH** supports us in the smooth implementation of complex network expansion projects."
|
||||
"Minimize risks and interfaces. We combine planning know-how with practical civil engineering expertise."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
id: "planung",
|
||||
title: "Planning",
|
||||
title: "Route & Approval Planning",
|
||||
icon: "planning",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Structural planning",
|
||||
"Approval planning",
|
||||
"Execution planning",
|
||||
"Structural planning (route optimization)",
|
||||
"Approval planning & authority management",
|
||||
"Detailed execution planning",
|
||||
"Consent planning"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "beratung",
|
||||
title: "Consulting & Management",
|
||||
title: "Control & Supervision",
|
||||
icon: "planning",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Tendering & awarding",
|
||||
"Construction supervision",
|
||||
"Project review",
|
||||
"Feasibility studies"
|
||||
"Tendering & awarding (AVA)",
|
||||
"Specialist construction supervision",
|
||||
"Project review & controlling",
|
||||
"Sound feasibility studies"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Planning in Practice" description="Successfully planned and accompanied infrastructure projects of our group." />
|
||||
|
||||
<CallToAction
|
||||
title="Do you need planning security?"
|
||||
text="Let's talk about your project. We are at your side with help and advice."
|
||||
text="Let's talk about your project. We are at your side with our engineering knowledge."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -18,7 +18,7 @@ layout: "fullBleed"
|
||||
{
|
||||
id: "danny-joseph",
|
||||
name: "Danny Joseph",
|
||||
position: "Management",
|
||||
position: "Managing Director",
|
||||
email: "d.joseph@e-tib.com",
|
||||
phone: "+49 1520 7230518",
|
||||
image: "/assets/photos/team/danny.jpg"
|
||||
@@ -86,5 +86,5 @@ layout: "fullBleed"
|
||||
title="Become part of our team"
|
||||
text="We are always looking for motivated talents for exciting infrastructure projects."
|
||||
buttonText="View open positions"
|
||||
buttonLink="/en/karriere"
|
||||
buttonLink="/en/career"
|
||||
/>
|
||||
|
||||
59
content/en/terms.mdx
Normal file
59
content/en/terms.mdx
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: "Terms and Conditions"
|
||||
date: "2026-05-28"
|
||||
excerpt: "The General Terms and Conditions of E-TIB GmbH for our services and deliveries."
|
||||
---
|
||||
|
||||
# Terms and Conditions (T&C)
|
||||
|
||||
## 1. Scope and Conclusion of Contract
|
||||
1. These General Terms and Conditions (T&C) apply exclusively to all contracts, deliveries, and other services (including planning, engineering, cable civil engineering, horizontal directional drilling, and fiber optic installation services) provided by E-TIB GmbH (hereinafter referred to as "E-TIB") to its contractual partners (hereinafter referred to as the "Customer").
|
||||
2. These T&C apply exclusively in B2B transactions. They are directed solely at entrepreneurs within the meaning of Section 14 of the German Civil Code (BGB), legal entities under public law, or special funds under public law.
|
||||
3. Any conflicting, deviating, or supplementary terms of the Customer shall not become part of the contract unless E-TIB expressly agrees to their validity in writing.
|
||||
4. A contract is only formed upon written order confirmation by E-TIB or by the actual commencement of execution of the services by E-TIB.
|
||||
|
||||
## 2. Scope of Services and Customer Cooperation
|
||||
1. The exact scope of the services to be provided by E-TIB results from the respective offer or the written order confirmation and the specifications agreed upon therein.
|
||||
2. The Customer is obligated to support E-TIB fully and at its own expense in the provision of the services. This includes in particular:
|
||||
- Providing all documents, plans (in particular utility and pipeline plans), permits, and official approvals required for planning and execution in good time before the start of construction.
|
||||
- Providing accurate and binding instruction on local conditions as well as precise marking and surveying of all underground cables, pipes, ducts, and other obstacles running in the work area.
|
||||
- Ensuring free and unhindered access to the construction site and providing any necessary storage and setup areas.
|
||||
- Providing construction water and electricity in the required quantity and quality at the workplaces at the Customer's expense.
|
||||
3. If the Customer fails to cooperate or provides incorrect information (especially regarding the location of underground utilities), it shall be liable to E-TIB for all resulting delays, additional costs, and damages. In this case, E-TIB is released from liability for damage to utilities that were not drawn or drawn incorrectly.
|
||||
|
||||
## 3. Execution Deadlines, Obstruction, and Construction Delays
|
||||
1. Agreed execution deadlines and dates are only binding if they have been expressly confirmed as fixed in writing by E-TIB.
|
||||
2. Compliance with deadlines is subject to the timely fulfillment of all cooperation duties by the Customer and the receipt of agreed down payments.
|
||||
3. Delays in performance due to force majeure, strikes, lockouts, official orders, or unpredictable difficulties for which E-TIB is not responsible (e.g., extreme weather conditions, unforeseen ground classes such as solid rock, incomplete or incorrect utility plans from the network operator) shall extend the execution periods by the duration of the obstruction plus a reasonable startup time.
|
||||
4. If unforeseen geological obstacles or deviations in the actual soil class from the contractually assumed soil classes result in a significant additional effort (e.g., in horizontal directional drilling), E-TIB is entitled to demand a reasonable adjustment of the remuneration as well as the execution deadlines.
|
||||
|
||||
## 4. Acceptance, Transfer of Risk, and Warranty
|
||||
1. Acceptance of the services shall take place immediately after completion. At the request of E-TIB, self-contained parts of the service (e.g., completed cable trenches or individual drilling sections) must be accepted separately (partial acceptance).
|
||||
2. If the Customer does not accept the service within 12 working days after written notification of completion, or if the Customer puts the service into use (e.g., by operating the laid cables), acceptance shall be deemed to have taken place after this period has expired.
|
||||
3. The risk of accidental loss and accidental deterioration shall pass to the Customer upon acceptance or partial acceptance.
|
||||
4. E-TIB provides a warranty in accordance with statutory regulations, unless otherwise agreed below. The limitation period for claims for defects in construction works and related planning and monitoring services is 5 years from acceptance. For other work services, it is 1 year from acceptance.
|
||||
5. Obvious defects must be reported to E-TIB in writing immediately, but no later than 5 working days after acceptance. Later claims for defects are excluded.
|
||||
|
||||
## 5. Prices, Progress Payments, and Payment Terms
|
||||
1. All prices are in Euro net plus the applicable statutory value-added tax.
|
||||
2. E-TIB is entitled to invoice reasonable progress payments in accordance with the progress of construction (e.g., per completed route meter, drilling meter, or completed planning phase). These are due for payment without deduction within 7 days from the invoice date.
|
||||
3. Final invoices are due for payment without deduction within 14 days after invoicing and acceptance.
|
||||
4. In the event of default of payment by the Customer, E-TIB is entitled to demand default interest in the statutory amount (currently 9 percentage points above the respective base interest rate). In addition, E-TIB reserves the right to suspend further work until full payment of the outstanding demands (right to refuse performance).
|
||||
|
||||
## 6. Retention of Title
|
||||
1. E-TIB retains ownership of all delivered and installed materials, cables, pipes, and other components (reserved goods) until full satisfaction of all claims under the respective contract.
|
||||
2. The Customer is obligated to treat the reserved goods with care and to inform E-TIB immediately in writing of any third-party access (e.g., pledges).
|
||||
3. In the event of combining or mixing the reserved goods with other items, E-TIB shall acquire co-ownership of the new item in the proportion of the invoice value of the reserved goods to the value of the other combined items at the time of combination.
|
||||
|
||||
## 7. Liability and Limitations of Liability
|
||||
1. E-TIB is liable without limitation in cases of intent, gross negligence, as well as for injury to life, body, or health.
|
||||
2. In the event of a slightly negligent breach of material contractual obligations (cardinal obligations), E-TIB's liability shall be limited to the contract-typical, foreseeable damage. Material contractual obligations are those whose fulfillment makes the proper execution of the contract possible in the first place and on whose compliance the Customer may regularly rely.
|
||||
3. Liability for slight negligence in the breach of non-essential contractual obligations is excluded.
|
||||
4. E-TIB is not liable for damages resulting from incomplete, outdated, or inaccurate utility plans provided by the Customer or local network operators, unless E-TIB should have recognized the incompleteness in a grossly negligent or intentional manner.
|
||||
5. The above limitations of liability also apply in favor of E-TIB's employees, representatives, and vicarious agents.
|
||||
|
||||
## 8. Place of Jurisdiction and Final Provisions
|
||||
1. The law of the Federal Republic of Germany shall apply, to the exclusion of the UN Convention on Contracts for the International Sale of Goods (CISG).
|
||||
2. The place of performance for all deliveries and services, as well as the place of payment, is E-TIB's registered office in Guben.
|
||||
3. The exclusive place of jurisdiction for all disputes arising from or in connection with this contract is Cottbus, provided that the Customer is a merchant, a legal entity under public law, or a special fund under public law. E-TIB is, however, also entitled to sue the Customer at its general place of jurisdiction.
|
||||
4. Should any provision of these T&C be or become invalid or unenforceable, this shall not affect the validity of the remaining provisions. The invalid provision shall be replaced by a valid provision that comes closest to the economic purpose of the invalid provision.
|
||||
@@ -11,7 +11,7 @@ layout: "fullBleed"
|
||||
description="Since 2015, we have been growing continuously to meet the demands of the energy transition and digitalization. As a corporate group, we pool specialized expertise under one roof."
|
||||
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
|
||||
linkText="Open Positions"
|
||||
linkHref="/en/karriere"
|
||||
linkHref="/en/career"
|
||||
secondaryCtaLabel="Discover Company"
|
||||
secondaryCtaHref="#structure"
|
||||
/>
|
||||
@@ -52,50 +52,9 @@ layout: "fullBleed"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CompanyTimeline
|
||||
badge="Our History"
|
||||
title="Milestones of Development"
|
||||
milestones={[
|
||||
{
|
||||
date: '16.12.2015',
|
||||
year: '2015',
|
||||
title: 'Foundation of E-TIB GmbH',
|
||||
desc: 'Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling.',
|
||||
iconName: 'Building2'
|
||||
},
|
||||
{
|
||||
date: '04.02.2019',
|
||||
year: '2019',
|
||||
title: 'Foundation of E-TIB Ingenieurgesellschaft',
|
||||
desc: 'Approval and execution planning, complex crossings, and grid connection planning.',
|
||||
iconName: 'Compass'
|
||||
},
|
||||
{
|
||||
date: '14.11.2019',
|
||||
year: '2019',
|
||||
title: 'Foundation of E-TIB Verwaltung GmbH',
|
||||
desc: 'Central services, acquisition, leasing, and management of real estate and machinery.',
|
||||
iconName: 'Layers'
|
||||
},
|
||||
{
|
||||
date: '21.10.2025',
|
||||
year: '2025',
|
||||
title: 'Foundation of E-TIB Bohrtechnik GmbH',
|
||||
desc: 'Specialization in precise horizontal directional drilling in all soil classes.',
|
||||
iconName: 'ArrowDownToLine'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<InteractiveGermanyMap isHero={true} badge="Nationwide Presence" title="Infrastructure for an entire country" description="Since 2023, we have proven in over 100 projects across Germany that we reliably implement infrastructure projects of any scale." />
|
||||
|
||||
<InteractiveGermanyMap
|
||||
badge="Operating Areas"
|
||||
title={<>Nationwide<br/>at your service.</>}
|
||||
description="From our strategic locations in Guben and Bülstedt, we control and realize complex infrastructure projects across the entire federal territory."
|
||||
stats={[
|
||||
{ value: '100', suffix: '%', label: 'Nationwide Reach' },
|
||||
{ value: '2', suffix: '+', label: 'Operating Locations' }
|
||||
]}
|
||||
/>
|
||||
<CompanyTimeline />
|
||||
|
||||
<div id="structure">
|
||||
<HomeSubCompanyTiles
|
||||
@@ -136,5 +95,5 @@ layout: "fullBleed"
|
||||
title="Ready for Your Project?"
|
||||
description="We are always looking for new challenges and strong partners. Contact us for a non-binding consultation on your project."
|
||||
ctaLabel="Contact Us Now"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Surveying and Documentation"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Precise surveying, staking out, and digital documentation of line routes to ensure the highest safety and quality standards."
|
||||
excerpt: "Precise GIS surveying, staking out and digital documentation of line routes to ensure the highest construction and safety standards."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,27 +12,45 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
Leveraging ultra-precise, state-of-the-art GNSS/GPS technology and proprietary app integrations, we automate the surveying process to deliver seamless, real-time documentation.
|
||||
</blockquote>
|
||||
|
||||
## Centimeter Precision Prevents Costly Operations
|
||||
|
||||
Regulatory documentation mandates and safety benchmarks are expanding rapidly. Traditional civil engineering contractors frequently struggle to meet these highly complex data requirements—especially under strict public funding frameworks. Neglecting or incorrectly mapping underground routes inevitably triggers major roadblocks during project handovers, audit reviews, and long-term asset management.
|
||||
|
||||
Recognizing this critical bottleneck early, we pro-actively integrated advanced geodetic surveying and digital mapping directly into our core operations.
|
||||
|
||||
## Cutting-Edge Digital Twin & GIS Records
|
||||
|
||||
Using advanced GNSS/GPS devices and in-house developed digital workflows, we capture the precise physical reality of every conduit and utility in real-time. This automated process generates a highly accurate digital twin of your underground infrastructure.
|
||||
|
||||
Beyond classic surveying, staking, and as-built mapping, we construct fully integrated geodatabases for transparent project accounting. We also provide complete, immersive 360° photo and video documentation of the construction area before and after ground-breaking. This ensures you maintain full transparency, legal compliance, and total control over your physical assets.
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Precision"
|
||||
title="Highest Safety Standards"
|
||||
title="Digital Safety Standards"
|
||||
descriptionParagraphs={[
|
||||
"Increasing documentation requirements and strict safety standards demand **the highest precision on the construction site**.",
|
||||
"With state-of-the-art technology, we ensure seamless and legally compliant recording of all lines. This is the best protection against unpleasant surprises during construction acceptance."
|
||||
"We do not only deliver a clean route, but also the perfect data. Legally compliant, digital and precise to the centimeter."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
id: "vermessung",
|
||||
title: "Geodesy",
|
||||
title: "Classic Geodesy",
|
||||
icon: "survey",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Staking out of line routes",
|
||||
"Surveying of line routes",
|
||||
"GPS-supported terrain recording"
|
||||
"Staking out of line routes with centimeter precision",
|
||||
"Surveying of existing routes and new lines",
|
||||
"Highly precise GPS-supported terrain recording",
|
||||
"Automated app-based measuring procedures"
|
||||
]
|
||||
]
|
||||
},
|
||||
@@ -44,17 +62,20 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Creation of databases for project accounting",
|
||||
"360° photo/video recording of the construction site",
|
||||
"Integration into geographic information systems (GIS)"
|
||||
"360° photo and video recording of the construction field",
|
||||
"Integration and processing for Geographic Information Systems (GIS)",
|
||||
"Audit-proof inventory plans"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Documented Projects" description="We have precisely surveyed and digitally documented hundreds of kilometers of routes." />
|
||||
|
||||
<CallToAction
|
||||
title="Are you facing high documentation requirements?"
|
||||
text="Contact us for professional surveying and documentation services."
|
||||
text="Contact us for professional surveying and GIS services."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -21,44 +21,69 @@ layout: "fullBleed"
|
||||
{
|
||||
title: 'ISO 14001:2015',
|
||||
description: 'Environmental Management System',
|
||||
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 14001 Umweltmanagement.pdf',
|
||||
pdfUrl: '/assets/certificates/iso14001.pdf',
|
||||
type: 'iso',
|
||||
date: '14.12.2023',
|
||||
},
|
||||
{
|
||||
title: 'ISO 9001:2015',
|
||||
description: 'Quality Management System',
|
||||
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 9001 Qualitätsmanagement.pdf',
|
||||
pdfUrl: '/assets/certificates/iso9001.pdf',
|
||||
type: 'iso',
|
||||
date: '14.12.2023',
|
||||
},
|
||||
{
|
||||
title: 'DIN EN ISO 45001:2018',
|
||||
description: 'Occupational Health and Safety',
|
||||
pdfUrl: '/assets/certificates/Zertifizierung DIN EN ISO 45001 bis 05122028.pdf',
|
||||
pdfUrl: '/assets/certificates/iso45001.pdf',
|
||||
type: 'iso',
|
||||
date: '05.12.2025',
|
||||
},
|
||||
{
|
||||
title: 'Exemption Certificate',
|
||||
description: 'According to § 48 b EStG',
|
||||
pdfUrl: '/assets/certificates/240209_Freistellungsbescheinigung § 48 b.pdf',
|
||||
pdfUrl: '/assets/certificates/freistellung.pdf',
|
||||
type: 'tax',
|
||||
date: '09.02.2024',
|
||||
},
|
||||
{
|
||||
title: 'Proof § 13b UStG',
|
||||
description: 'Tax liability of the service recipient',
|
||||
pdfUrl: '/assets/certificates/240209_Nachweis § 13 b.pdf',
|
||||
pdfUrl: '/assets/certificates/nachweis13b.pdf',
|
||||
type: 'tax',
|
||||
date: '09.02.2024',
|
||||
},
|
||||
{
|
||||
title: 'Certificate in Tax Matters',
|
||||
description: 'Tax Office Certificate',
|
||||
pdfUrl: '/assets/certificates/250213_Bescheinigung in Steuersachen.pdf',
|
||||
pdfUrl: '/assets/certificates/bescheinigung.pdf',
|
||||
type: 'tax',
|
||||
date: '13.02.2025',
|
||||
},
|
||||
{
|
||||
title: 'IHK Clearance Certificate',
|
||||
description: 'Chamber of Industry and Commerce',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'HWK Clearance Certificate',
|
||||
description: 'Chamber of Crafts',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'Commercial Register Extract',
|
||||
description: 'District Court Cottbus',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'Trade Registration',
|
||||
description: 'City of Guben',
|
||||
type: 'general',
|
||||
},
|
||||
{
|
||||
title: 'Central Trade Register',
|
||||
description: 'Information from the Register',
|
||||
type: 'general',
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
BIN
data/Mengen Homepage.docx
Normal file
BIN
data/Mengen Homepage.docx
Normal file
Binary file not shown.
BIN
data/Orte 2016 - 2022.docx
Normal file
BIN
data/Orte 2016 - 2022.docx
Normal file
Binary file not shown.
BIN
data/Orte 2023.docx
Normal file
BIN
data/Orte 2023.docx
Normal file
Binary file not shown.
BIN
data/Orte 2024.docx
Normal file
BIN
data/Orte 2024.docx
Normal file
Binary file not shown.
14
data/Orte 2024.txt
Normal file
14
data/Orte 2024.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
Windpark, 48366 Laer
|
||||
PV-Anlage, 89434 Blindheim OT Wolpertstetten
|
||||
Windpark, 24893 Taarstedt
|
||||
Windpark, 24891 Schnarup-Thumby
|
||||
Windpark, 23818 Neuengörs
|
||||
PV-Anlage, 15326 Petershagen
|
||||
Windpark, 16866 Gumtow OT Schrepkow
|
||||
PV-Anlage, 14641 Nauen
|
||||
PV-Anlage, 23619 Badendorf
|
||||
PV-Anlage, 14959 Wiesenhagen
|
||||
PV-Anlage, 17089 Bartow
|
||||
PV-Anlage, 15345 Prötzel OT Sternebeck
|
||||
Windpark, 15848 Beeskow
|
||||
PV-Anlage, 15328 Gorgast
|
||||
BIN
data/Orte 2025.docx
Normal file
BIN
data/Orte 2025.docx
Normal file
Binary file not shown.
9
data/Orte 2025.txt
Normal file
9
data/Orte 2025.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
PV-Anlage, 18334 Dettmannsdorf
|
||||
PV-Anlage, 59320 Ennigerloh-Oelde
|
||||
PV-Anlage, 93176 Beratzhausen
|
||||
Windpark, 16845 Neustadt Dosse
|
||||
PV-Anlage, 16845 Stüdenitz-Schönermark
|
||||
Windpark, 48346 Ostbevern
|
||||
Windpark, 27804 Berne
|
||||
Windpark, 17166 Dalkendorf
|
||||
Windpark, 29575 Altenmedingen
|
||||
BIN
data/Orte 2026.docx
Normal file
BIN
data/Orte 2026.docx
Normal file
Binary file not shown.
@@ -40,6 +40,8 @@ services:
|
||||
NPM_TOKEN: ${NPM_TOKEN:-}
|
||||
CI: "true"
|
||||
WATCHPACK_POLLING: "true"
|
||||
ports:
|
||||
- "3001:3001"
|
||||
volumes:
|
||||
- .:/app
|
||||
- etib_node_modules:/app/node_modules
|
||||
|
||||
@@ -110,6 +110,9 @@ services:
|
||||
networks:
|
||||
default:
|
||||
name: ${PROJECT_NAME:-e-tib}-internal
|
||||
ipam:
|
||||
config:
|
||||
- subnet: ${INTERNAL_SUBNET:-10.199.9.0/24}
|
||||
infra:
|
||||
external: true
|
||||
|
||||
|
||||
854
lib/map-data.ts
854
lib/map-data.ts
@@ -1,7 +1,7 @@
|
||||
export interface Location {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'hq' | 'branch' | 'project';
|
||||
type: 'hq' | 'branch' | 'project' | 'minor_node';
|
||||
x: number;
|
||||
y: number;
|
||||
description: string;
|
||||
@@ -16,7 +16,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Guben (Hauptsitz)',
|
||||
type: 'hq',
|
||||
x: 85,
|
||||
y: 48,
|
||||
y: 41.1,
|
||||
description: 'E-TIB GmbH Holding & Bohrtechnik GmbH',
|
||||
details: ['Zentrale Steuerung', 'Kabelleitungstiefbau', 'Maschinenpark'],
|
||||
},
|
||||
@@ -25,7 +25,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Bülstedt (Ingenieurgesellschaft)',
|
||||
type: 'branch',
|
||||
x: 37,
|
||||
y: 25,
|
||||
y: 25.33,
|
||||
description: 'E-TIB Ingenieurgesellschaft mbH',
|
||||
details: ['Planung & Projektierung', 'Vermessung', 'Dokumentation'],
|
||||
},
|
||||
@@ -34,7 +34,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Spreewald (FTTC)',
|
||||
type: 'project',
|
||||
x: 81,
|
||||
y: 42,
|
||||
y: 36.99,
|
||||
description: 'Breitbandausbau FTTC',
|
||||
href: '/referenzen/spreewald',
|
||||
},
|
||||
@@ -43,7 +43,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Bomlitz (FTTH)',
|
||||
type: 'project',
|
||||
x: 43,
|
||||
y: 30,
|
||||
y: 28.76,
|
||||
description: '88km Leitungstrasse (Tiefbau/HDD)',
|
||||
href: '/referenzen/bomlitz',
|
||||
},
|
||||
@@ -52,7 +52,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Forst (Kabeltrasse)',
|
||||
type: 'project',
|
||||
x: 87,
|
||||
y: 52,
|
||||
y: 43.85,
|
||||
description: 'Neubau MS-Kabeltrasse',
|
||||
href: '/referenzen/forst',
|
||||
},
|
||||
@@ -61,7 +61,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Eisenhüttenstadt (Kabel)',
|
||||
type: 'project',
|
||||
x: 86,
|
||||
y: 44,
|
||||
y: 38.36,
|
||||
description: 'Kabelverbindung U30 - T10',
|
||||
href: '/referenzen/eisenhuettenstadt',
|
||||
},
|
||||
@@ -70,7 +70,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Görne (PV-Anlage)',
|
||||
type: 'project',
|
||||
x: 65,
|
||||
y: 35,
|
||||
y: 32.19,
|
||||
description: 'Einspeisung PV-Anlage (MS-Kabel)',
|
||||
href: '/referenzen/goerne',
|
||||
},
|
||||
@@ -79,8 +79,844 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Börde (FTTH-Ausbau)',
|
||||
type: 'project',
|
||||
x: 55,
|
||||
y: 38,
|
||||
y: 34.24,
|
||||
description: '109km Leitungstrasse für Breitband',
|
||||
href: '/referenzen/boerde',
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export const minorLocations: Location[] = [
|
||||
{
|
||||
id: 'breddin-16845',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 63.88,
|
||||
y: 29.36,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'bad-liebenwerda-04924',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 73.76,
|
||||
y: 46.48,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'hünfelden-65597',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 29.35,
|
||||
y: 61.68,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'zachow-19376',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 68.62,
|
||||
y: 34.44,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'schnarup-thumby-24891',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 41.96,
|
||||
y: 7.31,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'bartow-17089',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 73.39,
|
||||
y: 17.66,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'regesbostel-21649',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 42.09,
|
||||
y: 23.17,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'bomlitz-29699',
|
||||
name: 'Breitbandausbau / FTTx, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 42.22,
|
||||
y: 29.09,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'berzhahn-56459',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 28.1,
|
||||
y: 58.9,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'oschersleben-39387',
|
||||
name: 'Breitbandausbau / FTTx, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 55.74,
|
||||
y: 40.14,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'jessen-06917',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 70.21,
|
||||
y: 42.8,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dettmannsdorf-18334',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 66.38,
|
||||
y: 14.04,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'werneck-97440',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Main-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 45.73,
|
||||
y: 65.8,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'badendorf-23619',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 49.91,
|
||||
y: 17.02,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'hordorf-39387',
|
||||
name: 'Breitbandausbau / FTTx, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 55.17,
|
||||
y: 40.57,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'neißemünde-15898',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.95,
|
||||
y: 39.9,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'wiesenhagen-14959',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 72.47,
|
||||
y: 38.31,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'gersdorf-09355',
|
||||
name: 'Netzanbindung Windenergie, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 67.92,
|
||||
y: 56.13,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nauen-14641',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 68.95,
|
||||
y: 32.81,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'eggersdorf-15374',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.59,
|
||||
y: 40.82,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'guben-03172',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.55,
|
||||
y: 41.12,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'beratzhausen-93176',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 60.46,
|
||||
y: 76.77,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'felm-24161',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 45.58,
|
||||
y: 10.19,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'neukieritzsch-04575',
|
||||
name: 'Breitbandausbau / FTTx, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 65.5,
|
||||
y: 51.2,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'oberbarnim-15377',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 79.52,
|
||||
y: 33.09,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'berne-27804',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 32.1,
|
||||
y: 25.85,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dossow-16909',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 66.44,
|
||||
y: 26.41,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'krempe-25361',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.7,
|
||||
y: 17.42,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'lübars-39291',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 73.45,
|
||||
y: 32.78,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'taarstedt-24893',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 42.4,
|
||||
y: 8.17,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'rom-19372',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 61.55,
|
||||
y: 22.35,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'bosbüll-25899',
|
||||
name: 'PV-Infrastrukturprojekt, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 35.36,
|
||||
y: 5,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'schwarzhofen-92447',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 64.76,
|
||||
y: 73.46,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'göhlsdorf-14797',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 68.78,
|
||||
y: 35.98,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'zehdenick-16792',
|
||||
name: 'Trassenbau Hochspannung, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 73.35,
|
||||
y: 28.02,
|
||||
description: 'power',
|
||||
},
|
||||
{
|
||||
id: 'osterburg-39606',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.63,
|
||||
y: 30.48,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'holzheim-89438',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 49.58,
|
||||
y: 84.44,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'adelschlag-85111',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 55.23,
|
||||
y: 80.23,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'hoyerswerda-02977',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 80.8,
|
||||
y: 47.74,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'niederhummel-85416',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 60.98,
|
||||
y: 85.4,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'löffingen-79843',
|
||||
name: 'PV-Infrastrukturprojekt, Region Schwarzwald / Bodensee',
|
||||
type: 'minor_node',
|
||||
x: 31.06,
|
||||
y: 92.31,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'holste-27729',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 35.35,
|
||||
y: 23.24,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'lasbek-23847',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 48.1,
|
||||
y: 18.71,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'bernau-bei-berlin-16321',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 75.39,
|
||||
y: 31.83,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'gorgast-15328',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 83.44,
|
||||
y: 33.39,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'neustadt-dosse-16845',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 65.82,
|
||||
y: 29.83,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'halle-06132',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 61.88,
|
||||
y: 47.76,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'letschin-15324',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 82.02,
|
||||
y: 32.11,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'sallgast-03238',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 77.65,
|
||||
y: 45.69,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'rodleben-dessau-roßlau-06861',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 63.69,
|
||||
y: 41.69,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'gerbstedt-06347',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 58.88,
|
||||
y: 45.36,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'elsterwerda-04910',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 74.88,
|
||||
y: 47.26,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'magdeburg-39106',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.01,
|
||||
y: 38.7,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'staßfurt-39443',
|
||||
name: 'BESS-Anbindung, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 58.97,
|
||||
y: 41.85,
|
||||
description: 'battery',
|
||||
},
|
||||
{
|
||||
id: 'alsleben-06425',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.15,
|
||||
y: 44.01,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nienbüttel-25596',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.06,
|
||||
y: 14.97,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'werneuchen-16356',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 76.31,
|
||||
y: 32.58,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'stüdenitz-schönermark-16845',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 64.43,
|
||||
y: 29.18,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'gumtow-16866',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 64.12,
|
||||
y: 28.2,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nöbdenitz-04626',
|
||||
name: 'Breitbandausbau / FTTx, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 64.59,
|
||||
y: 54.41,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'kröpelin-18236',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 60.24,
|
||||
y: 14.56,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'laer-48366',
|
||||
name: 'Netzanbindung Windenergie, Region Münsterland',
|
||||
type: 'minor_node',
|
||||
x: 22.68,
|
||||
y: 39.78,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'ostbevern-48346',
|
||||
name: 'Netzanbindung Windenergie, Region Münsterland',
|
||||
type: 'minor_node',
|
||||
x: 26.64,
|
||||
y: 39.84,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'schwarmstedt-29690',
|
||||
name: 'Breitbandausbau / FTTx, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 41.89,
|
||||
y: 31.93,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'prötzel-15345',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 78.84,
|
||||
y: 32.5,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'blindheim-89434',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 50.23,
|
||||
y: 82.72,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'großräschen-01983',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 78.82,
|
||||
y: 45.64,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'wolkenstein-09429',
|
||||
name: 'Netzanbindung Windenergie, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 71.1,
|
||||
y: 57.44,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'fronhausen-35112',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 34.1,
|
||||
y: 56.83,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dammfleth-25554',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 39.62,
|
||||
y: 16.66,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'langwedel-24631',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 44.46,
|
||||
y: 12.6,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'jerchel-14715',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 56.22,
|
||||
y: 34.63,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'beeskow-15848',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 80.9,
|
||||
y: 38.43,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'perleberg-19348',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 60.84,
|
||||
y: 26.25,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'gilching-82205',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 55.84,
|
||||
y: 89.45,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'bodenwöhr-92439',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 64.41,
|
||||
y: 74.55,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'görne-14728',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 66.34,
|
||||
y: 31.66,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'agethorst-25560',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.21,
|
||||
y: 15.07,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'ansbach-91522',
|
||||
name: 'Netzanbindung Windenergie, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 49.8,
|
||||
y: 74.59,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'schinne-39579',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.69,
|
||||
y: 32.23,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'neuengörs-23818',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 48.33,
|
||||
y: 16.63,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'denkendorf-85095',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 57.48,
|
||||
y: 79.18,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'lindhorst-31698',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 38.98,
|
||||
y: 35.98,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'rohrenfels-86701',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 54.89,
|
||||
y: 82.18,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'ingoldingen-88456',
|
||||
name: 'PV-Infrastrukturprojekt, Region Schwarzwald / Bodensee',
|
||||
type: 'minor_node',
|
||||
x: 42.75,
|
||||
y: 90.62,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'lichtenau-33165',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 35.64,
|
||||
y: 45.44,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nochten-02943',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.03,
|
||||
y: 47.68,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'dalkendorf-17166',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 66.23,
|
||||
y: 17.67,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'schopfheim-79650',
|
||||
name: 'Netzanbindung Windenergie, Region Schwarzwald / Bodensee',
|
||||
type: 'minor_node',
|
||||
x: 26.98,
|
||||
y: 94.96,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'lunow-stolzenhagen-16248',
|
||||
name: 'Trassenbau Hochspannung, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 79.88,
|
||||
y: 28.84,
|
||||
description: 'power',
|
||||
},
|
||||
{
|
||||
id: 'petershagen-15326',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 36.26,
|
||||
y: 35.79,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'krevese-39606',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.37,
|
||||
y: 30.29,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'seckeritz-17440',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 76.91,
|
||||
y: 15.43,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'tempelfelde-16230',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 76.55,
|
||||
y: 31.59,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'altenmedingen-29575',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 50.28,
|
||||
y: 26.09,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'neiße-malxetal-03159',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.5,
|
||||
y: 45.15,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'eisenhüttenstadt-15890',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.27,
|
||||
y: 38.7,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'groß-pankow-16928',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 62.41,
|
||||
y: 26.45,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dahlhausen-16909',
|
||||
name: 'PV-Infrastrukturprojekt, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 22.21,
|
||||
y: 50.39,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'gnarrenburg-27442',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 36.74,
|
||||
y: 23.17,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'greding-91171',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 56.38,
|
||||
y: 77.43,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'scheeßel-27383',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.7,
|
||||
y: 25.88,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'egling-an-der-paar-86492',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 53.41,
|
||||
y: 88.51,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'ellingen-91792',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 52.92,
|
||||
y: 77.37,
|
||||
description: 'pv',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface ReferenceFrontmatter {
|
||||
dateString?: string;
|
||||
featuredImage?: string | null;
|
||||
location: string;
|
||||
category?: string | string[];
|
||||
}
|
||||
|
||||
export interface ReferenceData {
|
||||
|
||||
148
messages/de.json
148
messages/de.json
@@ -9,7 +9,12 @@
|
||||
"ueber-uns": "ueber-uns",
|
||||
"agb": "agb",
|
||||
"start": "start",
|
||||
"messen": "messen"
|
||||
"messen": "messen",
|
||||
"kabeltiefbau": "kabeltiefbau",
|
||||
"glasfaser": "glasfaser",
|
||||
"bohrtechnik": "bohrtechnik",
|
||||
"planung": "planung",
|
||||
"vermessung": "vermessung"
|
||||
},
|
||||
"products": {},
|
||||
"categories": {}
|
||||
@@ -97,7 +102,7 @@
|
||||
"office": "Hauptsitz Guben",
|
||||
"address": "Gewerbestraße 22\n03172 Guben\nDeutschland",
|
||||
"phone": "+49 (0) 3561 / 68577 33",
|
||||
"email": "d.joseph@e-tib.com"
|
||||
"email": "info@e-tib.com"
|
||||
},
|
||||
"hours": {
|
||||
"title": "Öffnungszeiten",
|
||||
@@ -146,7 +151,15 @@
|
||||
"hero": {
|
||||
"title": "DIE EXPERTEN FÜR <green>KABELTIEFBAU</green>",
|
||||
"subtitle": "Wir helfen beim Ausbau der Energiekabelnetze für eine grüne Zukunft.",
|
||||
"cta": "Jetzt anfragen"
|
||||
"cta": "Jetzt anfragen",
|
||||
"searchPlaceholder": "Projekt beschreiben oder Kabel suchen...",
|
||||
"ask": "Fragen",
|
||||
"placeholder1": "Querschnittsberechnung für 110kV Trasse",
|
||||
"placeholder2": "Wie schwer ist NAYY 4x150?",
|
||||
"placeholder3": "Ich plane einen Solarpark, was brauche ich?",
|
||||
"placeholder4": "Unterschied zwischen N2XSY und NAY2XSY?",
|
||||
"placeholder5": "Mittelspannungskabel für Windkraftanlage",
|
||||
"placeholder6": "Welches Aluminiumkabel für 20kV?"
|
||||
},
|
||||
"video": {
|
||||
"title": "Vom ersten Spatenstich bis zum Netzanschluss – wir bauen die Infrastruktur von morgen."
|
||||
@@ -157,7 +170,28 @@
|
||||
"overview": "Übersicht",
|
||||
"needHelp": "Brauchen Sie Unterstützung?",
|
||||
"supportTeamAvailable": "Unser Expertenteam steht Ihnen für alle Fragen rund um Ihr Infrastrukturprojekt zur Verfügung.",
|
||||
"contactUs": "Kontaktieren Sie uns"
|
||||
"contactUs": "Kontaktieren Sie uns",
|
||||
"nextProjectTitle": "Ihr nächstes Projekt?",
|
||||
"nextProjectDesc": "Lassen Sie uns gemeinsam herausragende Infrastruktur bauen.",
|
||||
"client": "Kunde",
|
||||
"period": "Zeitraum"
|
||||
},
|
||||
"CallToAction": {
|
||||
"title": "Bereit für Ihr Projekt?",
|
||||
"description": "Wir suchen stets nach neuen Herausforderungen und starken Partnern. Kontaktieren Sie uns für eine unverbindliche Beratung zu Ihrem Vorhaben.",
|
||||
"ctaLabel": "Jetzt Kontakt aufnehmen",
|
||||
"ctaHref": "/de/kontakt"
|
||||
},
|
||||
"JobListingBlock": {
|
||||
"fairsTitle": "Nächste Messetermine",
|
||||
"emptyStateMessage": "Aktuell sind alle Positionen besetzt. Senden Sie uns gerne eine Initiativbewerbung!",
|
||||
"emptyStateLinkText": "Jetzt Kontakt aufnehmen",
|
||||
"emptyStateLinkHref": "/de/kontakt",
|
||||
"date": "Datum",
|
||||
"location": "Location",
|
||||
"booth": "Stand",
|
||||
"viewDetails": "Details ansehen",
|
||||
"title": "Aktuelle Stellenangebote"
|
||||
},
|
||||
"Error": {
|
||||
"title": "Hoppla!",
|
||||
@@ -188,5 +222,109 @@
|
||||
"technical": "Technik",
|
||||
"sustainability": "Nachhaltigkeit"
|
||||
}
|
||||
},
|
||||
"ReferencesSlider": {
|
||||
"badge": "Ausgewählte Projekte",
|
||||
"title": "Referenzen & Erfolge",
|
||||
"description": "Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabeltiefbau, Bohrtechnik und Netzinfrastruktur.",
|
||||
"ctaLabel": "Alle Referenzen ansehen"
|
||||
},
|
||||
"CompanyTimeline": {
|
||||
"badge": "Unsere Geschichte",
|
||||
"title": "Meilensteine der Entwicklung",
|
||||
"milestones": {
|
||||
"2015": {
|
||||
"title": "Gründung E-TIB GmbH",
|
||||
"desc": "Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen."
|
||||
},
|
||||
"2025": {
|
||||
"title": "Gründung E-TIB Bohrtechnik GmbH",
|
||||
"desc": "Spezialisierung auf präzise Horizontalspülbohrungen in allen Bodenklassen."
|
||||
},
|
||||
"2019_ing": {
|
||||
"title": "Gründung E-TIB Ingenieurgesellschaft",
|
||||
"desc": "Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung."
|
||||
},
|
||||
"2019_verw": {
|
||||
"title": "Gründung E-TIB Verwaltung GmbH",
|
||||
"desc": "Zentrale Dienste, Erwerb, Vermietung, Verpachtung und Verwaltung von Immobilien und Maschinen."
|
||||
}
|
||||
}
|
||||
},
|
||||
"InteractiveGermanyMap": {
|
||||
"types": {
|
||||
"hq": "Hauptsitz",
|
||||
"branch": "Niederlassung",
|
||||
"project": "Projekt",
|
||||
"reference": "Referenz"
|
||||
},
|
||||
"descriptions": {
|
||||
"wind": "Errichtung Leitungstrassen & Netzanbindung für Windpark-Infrastruktur.",
|
||||
"pv": "Bau von Mittelspannungstrassen zur Einspeisung der PV-Anlage.",
|
||||
"fiber": "Komplexer FTTx Breitbandausbau inkl. LWL-Montage & Tiefbau.",
|
||||
"power": "110kV Hochspannungstrasse Erdkabelverlegung.",
|
||||
"battery": "Infrastrukturausbau für BESS (Batteriespeicher).",
|
||||
"default": "Komplexes Infrastrukturprojekt (Tiefbau & HDD)."
|
||||
},
|
||||
"features": {
|
||||
"wind": "Netzanbindung Windpark",
|
||||
"pv": "Einspeisung PV-Anlage",
|
||||
"fiber": "FTTx Infrastruktur",
|
||||
"power": "110kV Trasse",
|
||||
"battery": "BESS Anschluss",
|
||||
"default": "Infrastrukturprojekt",
|
||||
"method": "Tiefbau & Spülbohrung (HDD)"
|
||||
},
|
||||
"learnMore": "Zum Projekt"
|
||||
},
|
||||
"TeamGrid": {
|
||||
"badge": "Persönliche Beratung",
|
||||
"title": "Ihre Ansprechpartner",
|
||||
"subtitle": "Sprechen Sie direkt mit unseren Experten für Ihr regionales Projekt.",
|
||||
"management": "Geschäftsführung",
|
||||
"branchETIB": "E-TIB GmbH",
|
||||
"branchIng": "Ingenieurgesellschaft",
|
||||
"branchBohr": "Bohrtechnik"
|
||||
},
|
||||
"AISearch": {
|
||||
"loadingTexts": [
|
||||
"Durchsuche das Kabelhandbuch... 📖",
|
||||
"Frage den Senior-Ingenieur... 👴🔧",
|
||||
"Frage ChatGPTs Cousin 2. Grades... 🤖"
|
||||
],
|
||||
"thinking": "Denkt nach...",
|
||||
"errorStatus": "Fehler aufgetreten",
|
||||
"online": "Online",
|
||||
"copyChat": "Chat kopieren",
|
||||
"copyChatTitle": "gesamten Chat kopieren",
|
||||
"copied": "Kopiert",
|
||||
"close": "Schließen",
|
||||
"howCanIHelp": "Wie kann ich helfen?",
|
||||
"helpDescription": "Beschreibe dein Projekt, frag nach bestimmten Kabeln, oder nenne mir deine Anforderungen.",
|
||||
"prompts": [
|
||||
"Windpark 33kV Verkabelung",
|
||||
"NYCWY 4x185",
|
||||
"Erdkabel für Solarpark"
|
||||
],
|
||||
"you": "Du",
|
||||
"copyMessage": "Nachricht kopieren",
|
||||
"recommendedProducts": "Empfohlene Produkte",
|
||||
"errorTitle": "Da ist was schiefgelaufen 😬",
|
||||
"tryAgain": "Nochmal versuchen",
|
||||
"placeholder": "Nachricht eingeben...",
|
||||
"send": "Nachricht senden",
|
||||
"footerShortcuts": "Enter zum Senden · Esc zum Schließen",
|
||||
"footerPrivacy": "🛡️ DSGVO-konform · EU-Server",
|
||||
"timeoutError": "Anfrage hat zu lange gedauert. Bitte versuche es erneut.",
|
||||
"genericError": "Ein Fehler ist aufgetreten."
|
||||
},
|
||||
"ReferenceDetail": {
|
||||
"backToOverview": "Zurück zur Übersicht",
|
||||
"projectReference": "Projektreferenz",
|
||||
"location": "Ort",
|
||||
"client": "Auftraggeber",
|
||||
"period": "Zeitraum",
|
||||
"scopeTitle": "Leistungsumfang & Projektbeschreibung",
|
||||
"viewAll": "Alle Referenzen ansehen"
|
||||
}
|
||||
}
|
||||
}
|
||||
148
messages/en.json
148
messages/en.json
@@ -9,7 +9,12 @@
|
||||
"about-us": "ueber-uns",
|
||||
"terms": "agb",
|
||||
"start": "start",
|
||||
"events": "messen"
|
||||
"trade-fairs": "messen",
|
||||
"cable-civil-engineering": "kabeltiefbau",
|
||||
"fiber-optics": "glasfaser",
|
||||
"drilling-technology": "bohrtechnik",
|
||||
"planning": "planung",
|
||||
"surveying": "vermessung"
|
||||
},
|
||||
"products": {},
|
||||
"categories": {}
|
||||
@@ -97,7 +102,7 @@
|
||||
"office": "Headquarters Guben",
|
||||
"address": "Gewerbestraße 22\n03172 Guben\nGermany",
|
||||
"phone": "+49 (0) 3561 / 68577 33",
|
||||
"email": "d.joseph@e-tib.com"
|
||||
"email": "info@e-tib.com"
|
||||
},
|
||||
"hours": {
|
||||
"title": "Opening Hours",
|
||||
@@ -146,7 +151,15 @@
|
||||
"hero": {
|
||||
"title": "THE EXPERTS FOR <green>UNDERGROUND CABLE ENGINEERING</green>",
|
||||
"subtitle": "We help expanding the energy cable networks for a green future.",
|
||||
"cta": "Request now"
|
||||
"cta": "Request now",
|
||||
"searchPlaceholder": "Describe project or search for cables...",
|
||||
"ask": "Ask",
|
||||
"placeholder1": "Cross-section calculation for 110kV route",
|
||||
"placeholder2": "How heavy is NAYY 4x150?",
|
||||
"placeholder3": "I'm planning a solar park, what do I need?",
|
||||
"placeholder4": "Difference between N2XSY and NAY2XSY?",
|
||||
"placeholder5": "Medium voltage cable for wind turbine",
|
||||
"placeholder6": "Which aluminum cable for 20kV?"
|
||||
},
|
||||
"video": {
|
||||
"title": "From the first spade cut to the grid connection – we build the infrastructure of tomorrow."
|
||||
@@ -157,7 +170,28 @@
|
||||
"overview": "Overview",
|
||||
"needHelp": "Need support?",
|
||||
"supportTeamAvailable": "Our team of experts is available to answer all your questions regarding your infrastructure project.",
|
||||
"contactUs": "Contact Us"
|
||||
"contactUs": "Contact Us",
|
||||
"nextProjectTitle": "Your next project?",
|
||||
"nextProjectDesc": "Let's build outstanding infrastructure together.",
|
||||
"client": "Client",
|
||||
"period": "Period"
|
||||
},
|
||||
"CallToAction": {
|
||||
"title": "Ready for your project?",
|
||||
"description": "We are always looking for new challenges and strong partners. Contact us for a non-binding consultation about your project.",
|
||||
"ctaLabel": "Contact us now",
|
||||
"ctaHref": "/en/contact"
|
||||
},
|
||||
"JobListingBlock": {
|
||||
"fairsTitle": "Upcoming Trade Fairs",
|
||||
"emptyStateMessage": "All positions are currently filled. Feel free to send us an unsolicited application!",
|
||||
"emptyStateLinkText": "Contact us now",
|
||||
"emptyStateLinkHref": "/en/contact",
|
||||
"date": "Date",
|
||||
"location": "Location",
|
||||
"booth": "Booth",
|
||||
"viewDetails": "View details",
|
||||
"title": "Current Job Openings"
|
||||
},
|
||||
"Error": {
|
||||
"title": "Oops!",
|
||||
@@ -188,5 +222,109 @@
|
||||
"technical": "Technical",
|
||||
"sustainability": "Sustainability"
|
||||
}
|
||||
},
|
||||
"ReferencesSlider": {
|
||||
"badge": "Selected Projects",
|
||||
"title": "References & Successes",
|
||||
"description": "A selection of our successfully completed projects in underground cable engineering, drilling technology, and grid infrastructure.",
|
||||
"ctaLabel": "View All References"
|
||||
},
|
||||
"CompanyTimeline": {
|
||||
"badge": "Our History",
|
||||
"title": "Milestones of Development",
|
||||
"milestones": {
|
||||
"2015": {
|
||||
"title": "Foundation of E-TIB GmbH",
|
||||
"desc": "Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling."
|
||||
},
|
||||
"2025": {
|
||||
"title": "Foundation of E-TIB Bohrtechnik GmbH",
|
||||
"desc": "Specialization in precise horizontal directional drilling in all soil classes."
|
||||
},
|
||||
"2019_ing": {
|
||||
"title": "Foundation of E-TIB Ingenieurgesellschaft",
|
||||
"desc": "Permit and execution planning, complex crossings, and grid connection planning."
|
||||
},
|
||||
"2019_verw": {
|
||||
"title": "Foundation of E-TIB Verwaltung GmbH",
|
||||
"desc": "Central services, acquisition, leasing, and management of real estate and machinery."
|
||||
}
|
||||
}
|
||||
},
|
||||
"InteractiveGermanyMap": {
|
||||
"types": {
|
||||
"hq": "Headquarters",
|
||||
"branch": "Branch",
|
||||
"project": "Project",
|
||||
"reference": "Reference"
|
||||
},
|
||||
"descriptions": {
|
||||
"wind": "Construction of cable routes & grid connection for wind park infrastructure.",
|
||||
"pv": "Construction of medium-voltage routes for feeding PV systems.",
|
||||
"fiber": "Complex FTTx broadband expansion incl. fiber optic assembly & civil engineering.",
|
||||
"power": "110kV high-voltage underground cable laying.",
|
||||
"battery": "Infrastructure expansion for BESS (battery storage).",
|
||||
"default": "Complex infrastructure project (civil engineering & HDD)."
|
||||
},
|
||||
"features": {
|
||||
"wind": "Grid connection wind park",
|
||||
"pv": "Feeding PV system",
|
||||
"fiber": "FTTx Infrastructure",
|
||||
"power": "110kV Route",
|
||||
"battery": "BESS Connection",
|
||||
"default": "Infrastructure project",
|
||||
"method": "Civil engineering & HDD"
|
||||
},
|
||||
"learnMore": "Learn more"
|
||||
},
|
||||
"TeamGrid": {
|
||||
"badge": "Personal Consultation",
|
||||
"title": "Your Contacts",
|
||||
"subtitle": "Speak directly with our experts for your regional project.",
|
||||
"management": "Management",
|
||||
"branchETIB": "E-TIB GmbH",
|
||||
"branchIng": "Engineering Company",
|
||||
"branchBohr": "Drilling Technology"
|
||||
},
|
||||
"AISearch": {
|
||||
"loadingTexts": [
|
||||
"Searching the cable manual... 📖",
|
||||
"Asking the senior engineer... 👴🔧",
|
||||
"Asking ChatGPT's 2nd cousin... 🤖"
|
||||
],
|
||||
"thinking": "Thinking...",
|
||||
"errorStatus": "Error occurred",
|
||||
"online": "Online",
|
||||
"copyChat": "Copy chat",
|
||||
"copyChatTitle": "copy entire chat",
|
||||
"copied": "Copied",
|
||||
"close": "Close",
|
||||
"howCanIHelp": "How can I help?",
|
||||
"helpDescription": "Describe your project, ask for specific cables, or tell me your requirements.",
|
||||
"prompts": [
|
||||
"Wind park 33kV cabling",
|
||||
"NYCWY 4x185",
|
||||
"Underground cable for solar park"
|
||||
],
|
||||
"you": "You",
|
||||
"copyMessage": "Copy message",
|
||||
"recommendedProducts": "Recommended Products",
|
||||
"errorTitle": "Something went wrong 😬",
|
||||
"tryAgain": "Try again",
|
||||
"placeholder": "Enter message...",
|
||||
"send": "Send message",
|
||||
"footerShortcuts": "Enter to send · Esc to close",
|
||||
"footerPrivacy": "🛡️ GDPR compliant · EU servers",
|
||||
"timeoutError": "Request took too long. Please try again.",
|
||||
"genericError": "An error occurred."
|
||||
},
|
||||
"ReferenceDetail": {
|
||||
"backToOverview": "Back to Overview",
|
||||
"projectReference": "Project Reference",
|
||||
"location": "Location",
|
||||
"client": "Client",
|
||||
"period": "Period",
|
||||
"scopeTitle": "Scope & Project Description",
|
||||
"viewAll": "View all References"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,8 @@
|
||||
"@tailwindcss/cli": "^4.1.18",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/node": "^22.19.3",
|
||||
|
||||
110
pnpm-lock.yaml
generated
110
pnpm-lock.yaml
generated
@@ -44,7 +44,7 @@ importers:
|
||||
version: 10.47.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.4))
|
||||
'@types/recharts':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
|
||||
version: 2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
||||
axios:
|
||||
specifier: ^1.13.5
|
||||
version: 1.14.0(debug@4.4.3)
|
||||
@@ -116,7 +116,7 @@ importers:
|
||||
version: 10.1.0(@types/react@19.2.14)(react@19.2.4)
|
||||
recharts:
|
||||
specifier: ^3.7.0
|
||||
version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
|
||||
version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
||||
rehype-raw:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
@@ -181,6 +181,12 @@ importers:
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.19
|
||||
version: 0.5.19(tailwindcss@4.2.2)
|
||||
'@testing-library/dom':
|
||||
specifier: ^10.4.1
|
||||
version: 10.4.1
|
||||
'@testing-library/react':
|
||||
specifier: ^16.3.2
|
||||
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@types/geojson':
|
||||
specifier: ^7946.0.16
|
||||
version: 7946.0.16
|
||||
@@ -2936,6 +2942,25 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@testing-library/react@16.3.2':
|
||||
resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@testing-library/dom': ^10.0.0
|
||||
'@types/react': ^18.0.0 || ^19.0.0
|
||||
'@types/react-dom': ^18.0.0 || ^19.0.0
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0':
|
||||
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
|
||||
|
||||
@@ -2978,6 +3003,9 @@ packages:
|
||||
'@types/acorn@4.0.6':
|
||||
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
||||
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -3510,6 +3538,10 @@ packages:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@5.2.0:
|
||||
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ansi-styles@6.2.3:
|
||||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3523,6 +3555,9 @@ packages:
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
aria-query@5.3.0:
|
||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||
|
||||
aria-query@5.3.2:
|
||||
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4336,6 +4371,9 @@ packages:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
dom-accessibility-api@0.5.16:
|
||||
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
|
||||
@@ -5893,6 +5931,10 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
lz-string@1.5.0:
|
||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||
hasBin: true
|
||||
|
||||
maath@0.10.8:
|
||||
resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==}
|
||||
peerDependencies:
|
||||
@@ -6699,6 +6741,10 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-format@27.5.1:
|
||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
|
||||
prismjs@1.29.0:
|
||||
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -6820,6 +6866,9 @@ packages:
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-is@17.0.2:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
|
||||
react-leaflet@4.2.1:
|
||||
resolution: {integrity: sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==}
|
||||
peerDependencies:
|
||||
@@ -7502,8 +7551,8 @@ packages:
|
||||
third-party-web@0.26.7:
|
||||
resolution: {integrity: sha512-buUzX4sXC4efFX6xg2bw6/eZsCUh8qQwSavC4D9HpONMFlRbcHhD8Je5qwYdCpViR6q0qla2wPP+t91a2vgolg==}
|
||||
|
||||
third-party-web@0.29.0:
|
||||
resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==}
|
||||
third-party-web@0.29.2:
|
||||
resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==}
|
||||
|
||||
thread-stream@4.0.0:
|
||||
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
||||
@@ -9809,7 +9858,7 @@ snapshots:
|
||||
'@paulirish/trace_engine@0.0.53':
|
||||
dependencies:
|
||||
legacy-javascript: 0.0.1
|
||||
third-party-web: 0.29.0
|
||||
third-party-web: 0.29.2
|
||||
|
||||
'@pdf-lib/standard-fonts@1.0.0':
|
||||
dependencies:
|
||||
@@ -10822,6 +10871,27 @@ snapshots:
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 4.2.2
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
'@babel/runtime': 7.29.2
|
||||
'@types/aria-query': 5.0.4
|
||||
aria-query: 5.3.0
|
||||
dom-accessibility-api: 0.5.16
|
||||
lz-string: 1.5.0
|
||||
picocolors: 1.1.1
|
||||
pretty-format: 27.5.1
|
||||
|
||||
'@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@testing-library/dom': 10.4.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
||||
|
||||
'@turbo/darwin-64@2.9.3':
|
||||
@@ -10853,6 +10923,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.2
|
||||
@@ -10998,9 +11070,9 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/recharts@2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)':
|
||||
'@types/recharts@2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)':
|
||||
dependencies:
|
||||
recharts: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
|
||||
recharts: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- react
|
||||
@@ -11438,6 +11510,8 @@ snapshots:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ansi-styles@5.2.0: {}
|
||||
|
||||
ansi-styles@6.2.3: {}
|
||||
|
||||
arg@5.0.2: {}
|
||||
@@ -11448,6 +11522,10 @@ snapshots:
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-query@5.3.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
aria-query@5.3.2: {}
|
||||
|
||||
array-buffer-byte-length@1.0.2:
|
||||
@@ -12317,6 +12395,8 @@ snapshots:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
|
||||
dom-accessibility-api@0.5.16: {}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
@@ -14234,6 +14314,8 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
lz-string@1.5.0: {}
|
||||
|
||||
maath@0.10.8(@types/three@0.183.1)(three@0.183.2):
|
||||
dependencies:
|
||||
'@types/three': 0.183.1
|
||||
@@ -15338,6 +15420,12 @@ snapshots:
|
||||
|
||||
prettier@3.8.1: {}
|
||||
|
||||
pretty-format@27.5.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 17.0.2
|
||||
|
||||
prismjs@1.29.0: {}
|
||||
|
||||
prismjs@1.30.0: {}
|
||||
@@ -15501,6 +15589,8 @@ snapshots:
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@17.0.2: {}
|
||||
|
||||
react-leaflet@4.2.1(leaflet@1.9.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@react-leaflet/core': 2.1.0(leaflet@1.9.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -15553,7 +15643,7 @@ snapshots:
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
recharts@3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1):
|
||||
recharts@3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1):
|
||||
dependencies:
|
||||
'@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)
|
||||
clsx: 2.1.1
|
||||
@@ -15563,7 +15653,7 @@ snapshots:
|
||||
immer: 10.2.0
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-is: 16.13.1
|
||||
react-is: 17.0.2
|
||||
react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1)
|
||||
reselect: 5.1.1
|
||||
tiny-invariant: 1.3.3
|
||||
@@ -16409,7 +16499,7 @@ snapshots:
|
||||
|
||||
third-party-web@0.26.7: {}
|
||||
|
||||
third-party-web@0.29.0: {}
|
||||
third-party-web@0.29.2: {}
|
||||
|
||||
thread-stream@4.0.0:
|
||||
dependencies:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user