Compare commits
93 Commits
2b79d18a93
...
v2.2.13-rc
| Author | SHA1 | Date | |
|---|---|---|---|
| e2a29b41e9 | |||
| 62727a7231 | |||
| 3a6189e0d8 | |||
| 01e2fdbb7d | |||
| fa7182d351 | |||
| e0d43f7f5f | |||
| d09b576137 | |||
| 04599827f9 | |||
| 5368f1a64d | |||
| 241c97e346 | |||
| 3334e327bf | |||
| 145bff90b6 | |||
| d2bf48fc60 | |||
| 5e7ee84dc9 | |||
| b42e53495d | |||
| 8813ed3910 | |||
| 1d3c6a0f70 | |||
| 8fb3b96bd3 | |||
| 67dc56d3ee | |||
| e5933bad69 | |||
| 11b9e4630b | |||
| a47d378521 | |||
| 30047cb1c4 | |||
| 394ac5a19c | |||
| d63f196693 | |||
| 6092f1d827 | |||
| 766c385704 | |||
| 6c9d2b6832 | |||
| 7a9d4d7f18 | |||
| 0b9929c2ec | |||
| aa0dfdf331 | |||
| 4c9028f58b | |||
| 9de5f2938b | |||
| de41a0e29d | |||
| 6689dd3013 | |||
| 31fbcb32dd | |||
| 68eca73df2 | |||
| 3bb6eb3447 | |||
| a3c732dd13 | |||
| aaa1a8793c | |||
| 233509db67 | |||
| 17a807c48b | |||
| d8634efa9a | |||
| 0f2aa500e9 | |||
| 2c465bc16b | |||
| 68717bf0ce | |||
| 6444281c6f | |||
| 4b98fa9b4c | |||
| fa4385af4c | |||
| d98eb20430 | |||
| 3cfe8ff379 | |||
| 2abcd386f0 | |||
| d7ff6a3415 | |||
| d8f5d9bfc6 | |||
| f81a85fdd7 | |||
| 4968315ebf | |||
| eaaa3b7251 | |||
| bbc8716da6 | |||
| f4d65452a6 | |||
| 1b2e0793d3 | |||
| b378bb168f | |||
| 9134d6b070 | |||
| 0105c8c9a3 | |||
| 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 |
8
.env
8
.env
@@ -1,5 +1,5 @@
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# KLZ Cables - Environment Configuration
|
# E-TIB GmbH - Environment Configuration
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Copy this file to .env for local development
|
# Copy this file to .env for local development
|
||||||
# For production, use .env.production as a template
|
# For production, use .env.production as a template
|
||||||
@@ -20,14 +20,14 @@ NEXT_PUBLIC_RECORD_MODE_ENABLED=true
|
|||||||
# Analytics (Umami)
|
# Analytics (Umami)
|
||||||
# ────────────────────────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
# Optional: Leave empty to disable analytics
|
# Optional: Leave empty to disable analytics
|
||||||
UMAMI_WEBSITE_ID=
|
UMAMI_WEBSITE_ID=d773ea10-a3b3-4ccf-9024-987e14c4d669
|
||||||
UMAMI_API_ENDPOINT=https://analytics.infra.mintel.me
|
UMAMI_API_ENDPOINT=https://analytics.infra.mintel.me
|
||||||
|
|
||||||
# ────────────────────────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
# Error Tracking (GlitchTip/Sentry)
|
# Error Tracking (GlitchTip/Sentry)
|
||||||
# ────────────────────────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
# Optional: Leave empty to disable error tracking
|
# Optional: Leave empty to disable error tracking
|
||||||
SENTRY_DSN=
|
SENTRY_DSN=https://dcb81958-dbf2-4a3d-b422-875f4672c14b@glitchtip.infra.mintel.me/5
|
||||||
|
|
||||||
# ────────────────────────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
# Email Configuration (SMTP)
|
# Email Configuration (SMTP)
|
||||||
@@ -37,7 +37,7 @@ MAIL_HOST=smtp.eu.mailgun.org
|
|||||||
MAIL_PORT=587
|
MAIL_PORT=587
|
||||||
MAIL_USERNAME=
|
MAIL_USERNAME=
|
||||||
MAIL_PASSWORD=
|
MAIL_PASSWORD=
|
||||||
MAIL_FROM=KLZ Cables <noreply@e-tib.com>
|
MAIL_FROM=E-TIB GmbH <noreply@mg.mintel.me>
|
||||||
MAIL_RECIPIENTS=info@e-tib.com
|
MAIL_RECIPIENTS=info@e-tib.com
|
||||||
|
|
||||||
# ────────────────────────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ env:
|
|||||||
PUPPETEER_SKIP_DOWNLOAD: "true"
|
PUPPETEER_SKIP_DOWNLOAD: "true"
|
||||||
COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com"
|
COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com"
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: deploy-pipeline
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -64,10 +61,17 @@ jobs:
|
|||||||
echo "Pruning Buildkit inside container: $container"
|
echo "Pruning Buildkit inside container: $container"
|
||||||
docker exec -i "$container" buildctl prune --all || true
|
docker exec -i "$container" buildctl prune --all || true
|
||||||
done
|
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..."
|
echo "Purging old build layers, unused volumes and images..."
|
||||||
docker builder prune -a -f || true
|
docker builder prune -a -f || true
|
||||||
docker volume prune -f || true
|
docker volume prune -f || true
|
||||||
docker system prune -a -f || true
|
docker system prune -a -f --volumes || true
|
||||||
echo "=== System Disk Usage After Prune ==="
|
echo "=== System Disk Usage After Prune ==="
|
||||||
df -h || true
|
df -h || true
|
||||||
|
|
||||||
@@ -139,6 +143,13 @@ jobs:
|
|||||||
echo "short_sha=$SHORT_SHA"
|
echo "short_sha=$SHORT_SHA"
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: 🚦 Priority Gate
|
||||||
|
env:
|
||||||
|
GITEA_PAT: ${{ secrets.GITEA_PAT }}
|
||||||
|
CURRENT_RUN_ID: ${{ github.run_id }}
|
||||||
|
CURRENT_TARGET: ${{ steps.determine.outputs.target }}
|
||||||
|
run: bash scripts/priority-gate.sh
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
# JOB 2: QA (Lint, Typecheck, Test)
|
# JOB 2: QA (Lint, Typecheck, Test)
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -213,28 +224,33 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: bash scripts/registry-auth.sh
|
run: bash scripts/registry-auth.sh
|
||||||
|
|
||||||
- name: 🐳 Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: 🔐 Registry Login
|
- name: 🔐 Registry Login
|
||||||
run: echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
run: echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
- name: 🏗️ Build and Push
|
- name: 🏗️ Build and Push (Native Docker Buildkit)
|
||||||
uses: docker/build-push-action@v5
|
env:
|
||||||
with:
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
context: .
|
NEXT_PUBLIC_TARGET: ${{ needs.prepare.outputs.target }}
|
||||||
push: true
|
NEXT_PUBLIC_APP_VERSION: ${{ needs.prepare.outputs.image_tag }}
|
||||||
provenance: false
|
UMAMI_WEBSITE_ID: ${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
|
||||||
platforms: linux/amd64
|
UMAMI_API_ENDPOINT: ${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
||||||
build-args: |
|
NPM_TOKEN: ${{ steps.auth.outputs.working_token }}
|
||||||
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
|
NPM_DOMAIN: ${{ steps.auth.outputs.working_domain }}
|
||||||
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
|
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
|
||||||
NEXT_PUBLIC_APP_VERSION=${{ needs.prepare.outputs.image_tag }}
|
run: |
|
||||||
UMAMI_WEBSITE_ID=${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
|
echo "$NPM_TOKEN" > npm_token.txt
|
||||||
UMAMI_API_ENDPOINT=${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
DOCKER_BUILDKIT=1 docker build \
|
||||||
NPM_TOKEN=${{ steps.auth.outputs.working_token }}
|
--secret id=NPM_TOKEN,src=npm_token.txt \
|
||||||
NPM_DOMAIN=${{ steps.auth.outputs.working_domain }}
|
--build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
||||||
tags: registry.infra.mintel.me/mintel/e-tib.com:${{ needs.prepare.outputs.image_tag }}
|
--build-arg NEXT_PUBLIC_TARGET="$NEXT_PUBLIC_TARGET" \
|
||||||
secrets: |
|
--build-arg NEXT_PUBLIC_APP_VERSION="$IMAGE_TAG" \
|
||||||
"NPM_TOKEN=${{ steps.auth.outputs.working_token }}"
|
--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
|
# JOB 4: Deploy
|
||||||
@@ -294,6 +310,19 @@ jobs:
|
|||||||
|
|
||||||
GATEKEEPER_ORIGIN="${NEXT_PUBLIC_BASE_URL}/gatekeeper"
|
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 "# Generated by CI - $TARGET"
|
||||||
echo "IMAGE_TAG=$IMAGE_TAG"
|
echo "IMAGE_TAG=$IMAGE_TAG"
|
||||||
@@ -330,6 +359,7 @@ jobs:
|
|||||||
echo "COMPOSE_PROFILES=$COMPOSE_PROFILES"
|
echo "COMPOSE_PROFILES=$COMPOSE_PROFILES"
|
||||||
echo "AUTH_MIDDLEWARE=$AUTH_MIDDLEWARE"
|
echo "AUTH_MIDDLEWARE=$AUTH_MIDDLEWARE"
|
||||||
echo "AUTH_MIDDLEWARE_UNPROTECTED=$AUTH_MIDDLEWARE_UNPROTECTED"
|
echo "AUTH_MIDDLEWARE_UNPROTECTED=$AUTH_MIDDLEWARE_UNPROTECTED"
|
||||||
|
echo "INTERNAL_SUBNET=$INTERNAL_SUBNET"
|
||||||
} > .env.deploy
|
} > .env.deploy
|
||||||
|
|
||||||
- name: 🚀 SSH Deploy
|
- name: 🚀 SSH Deploy
|
||||||
@@ -337,6 +367,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
ENV_FILE: ${{ needs.prepare.outputs.env_file }}
|
ENV_FILE: ${{ needs.prepare.outputs.env_file }}
|
||||||
run: |
|
run: |
|
||||||
|
SLUG=$(echo "${{ github.ref_name }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
|
echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
@@ -349,7 +380,6 @@ jobs:
|
|||||||
elif [[ "$TARGET" == "staging" ]]; then
|
elif [[ "$TARGET" == "staging" ]]; then
|
||||||
SITE_DIR="/home/deploy/sites/staging.e-tib.com"
|
SITE_DIR="/home/deploy/sites/staging.e-tib.com"
|
||||||
else
|
else
|
||||||
SLUG=$(echo "${{ github.ref_name }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
|
||||||
SITE_DIR="/home/deploy/sites/branch.e-tib.com/$SLUG"
|
SITE_DIR="/home/deploy/sites/branch.e-tib.com/$SLUG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -357,6 +387,8 @@ jobs:
|
|||||||
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
|
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
|
||||||
scp docker-compose.yml root@alpha.mintel.me:$SITE_DIR/docker-compose.yml
|
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 "docker rm -f \$(docker ps -a -q --filter name=${SLUG}-etib-) 2>/dev/null || true"
|
||||||
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 && 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' 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"
|
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' up -d --remove-orphans"
|
||||||
|
|||||||
BIN
.tmp_vikunja/design_reference.png
Normal file
BIN
.tmp_vikunja/design_reference.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
BIN
.tmp_vikunja/mengen_2016_2018.docx
Normal file
BIN
.tmp_vikunja/mengen_2016_2018.docx
Normal file
Binary file not shown.
BIN
.tmp_vikunja/mengen_2019_2021.docx
Normal file
BIN
.tmp_vikunja/mengen_2019_2021.docx
Normal file
Binary file not shown.
21
.tmp_vikunja/mengen_2019_2021.txt
Normal file
21
.tmp_vikunja/mengen_2019_2021.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
2019
|
||||||
|
Offener Tiefbau 32445 Meter
|
||||||
|
HDD 8505Meter
|
||||||
|
Kabelverlegung 87468 Meter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2020
|
||||||
|
Offener Tiefbau 31305 Meter
|
||||||
|
HDD 9050 Meter
|
||||||
|
Kabelverlegung 93556 Meter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2021
|
||||||
|
Offener Tiefbau 38756 Meter
|
||||||
|
HDD 10957 Meter
|
||||||
|
Kabelverlegung 110127 Meter
|
||||||
|
|
||||||
BIN
.tmp_vikunja/mengen_2022.docx
Normal file
BIN
.tmp_vikunja/mengen_2022.docx
Normal file
Binary file not shown.
8
.tmp_vikunja/mengen_2022.txt
Normal file
8
.tmp_vikunja/mengen_2022.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
2022
|
||||||
|
Offener Tiefbau 41445 Meter
|
||||||
|
HDD 9505Meter
|
||||||
|
Kabelverlegung 102468 Meter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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/41698960411f6bf6-manifest.json
Normal file
1
.turbo/41698960411f6bf6-manifest.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{".turbo/turbo-test.log":{"size":2776,"mtime_nanos":1781469605328811961,"mode":420,"is_dir":false}},"order":[".turbo/turbo-test.log"]}
|
||||||
1
.turbo/41698960411f6bf6-meta.json
Normal file
1
.turbo/41698960411f6bf6-meta.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"41698960411f6bf6","duration":4168,"sha":"a3c732dd133b1b8261b19cf64b3bf684e269e839","dirty_hash":"1ef3b75af417f41468b9558671572e75e0fc055f9dbe6e805cfa6f59b1cddf70"}
|
||||||
BIN
.turbo/41698960411f6bf6.tar.zst
Normal file
BIN
.turbo/41698960411f6bf6.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/bb46018d4a7bc516-manifest.json
Normal file
1
.turbo/bb46018d4a7bc516-manifest.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{".turbo/turbo-lint.log":{"size":12987,"mtime_nanos":1781469609469080321,"mode":420,"is_dir":false}},"order":[".turbo/turbo-lint.log"]}
|
||||||
1
.turbo/bb46018d4a7bc516-meta.json
Normal file
1
.turbo/bb46018d4a7bc516-meta.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"bb46018d4a7bc516","duration":8307,"sha":"a3c732dd133b1b8261b19cf64b3bf684e269e839","dirty_hash":"1ef3b75af417f41468b9558671572e75e0fc055f9dbe6e805cfa6f59b1cddf70"}
|
||||||
BIN
.turbo/bb46018d4a7bc516.tar.zst
Normal file
BIN
.turbo/bb46018d4a7bc516.tar.zst
Normal file
Binary file not shown.
1
.turbo/eb4eeeb717c1af0c-manifest.json
Normal file
1
.turbo/eb4eeeb717c1af0c-manifest.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"files":{".turbo/turbo-typecheck.log":{"size":85,"mtime_nanos":1781469608726900998,"mode":420,"is_dir":false}},"order":[".turbo/turbo-typecheck.log"]}
|
||||||
1
.turbo/eb4eeeb717c1af0c-meta.json
Normal file
1
.turbo/eb4eeeb717c1af0c-meta.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hash":"eb4eeeb717c1af0c","duration":7565,"sha":"a3c732dd133b1b8261b19cf64b3bf684e269e839","dirty_hash":"1ef3b75af417f41468b9558671572e75e0fc055f9dbe6e805cfa6f59b1cddf70"}
|
||||||
BIN
.turbo/eb4eeeb717c1af0c.tar.zst
Normal file
BIN
.turbo/eb4eeeb717c1af0c.tar.zst
Normal file
Binary file not shown.
@@ -1,119 +1,5 @@
|
|||||||
|
|
||||||
|
> e-tib-nextjs@2.2.12 lint /Volumes/Alpha SSD/Coding/e-tib.com
|
||||||
> e-tib-nextjs@2.2.12 lint /Volumes/Alpha SSD/Coding/e-tib.com
|
> eslint .
|
||||||
> eslint .
|
|
||||||
|
ELIFECYCLE Command failed.
|
||||||
|
|
||||||
/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
|
|
||||||
67: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
|
|
||||||
91: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
|
|
||||||
77: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:77:5
|
|
||||||
75 |
|
|
||||||
76 | useEffect(() => {
|
|
||||||
> 77 | setIsMounted(true);
|
|
||||||
| ^^^^^^^^^^^^ Avoid calling setState() directly within an effect
|
|
||||||
78 | }, []);
|
|
||||||
79 |
|
|
||||||
80 | 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:46 warning 'ArrowUpRight' is defined but never used @typescript-eslint/no-unused-vars
|
|
||||||
147:23 warning 'isNearRight' is assigned a value but never used @typescript-eslint/no-unused-vars
|
|
||||||
|
|
||||||
/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 a missing dependency: 'HERO_PLACEHOLDERS'. Either include it 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
|
|
||||||
99: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/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
|
|
||||||
|
|
||||||
✖ 38 problems (0 errors, 38 warnings)
|
|
||||||
0 errors and 2 warnings potentially fixable with the `--fix` option.
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,55 @@
|
|||||||
|
|
||||||
|
> e-tib-nextjs@2.2.12 test /Volumes/Alpha SSD/Coding/e-tib.com
|
||||||
> e-tib-nextjs@2.2.12 test /Volumes/Alpha SSD/Coding/e-tib.com
|
> vitest run --passWithNoTests
|
||||||
> vitest run --passWithNoTests
|
|
||||||
|
|
||||||
[?25l
|
[1m[46m RUN [49m[22m [36mv4.1.2 [39m[90m/Volumes/Alpha SSD/Coding/e-tib.com[39m
|
||||||
RUN v4.1.2 /Volumes/Alpha SSD/Coding/e-tib.com
|
|
||||||
|
(node:64961) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
|
||||||
[?2026h
|
(Use `node --trace-deprecation ...` to show where the warning was created)
|
||||||
❯ lib/mail/mailer.test.ts [queued]
|
[32m✓[39m tests/mdx-links.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 29[2mms[22m[39m
|
||||||
|
[32m✓[39m tests/homepage-and-competence-content.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 14[2mms[22m[39m
|
||||||
Test Files 0 passed (2)
|
[31m❯[39m tests/legal-pages.test.ts [2m([22m[2m2 tests[22m[2m | [22m[31m1 failed[39m[2m)[22m[32m 9[2mms[22m[39m
|
||||||
Tests 0 passed (0)
|
[32m✓[39m should contain highly professional B2B sections in German AGB[32m 5[2mms[22m[39m
|
||||||
Start at 11:47:51
|
[31m [31m×[31m should contain professional corresponding B2B sections in English Terms[39m[32m 2[2mms[22m[39m
|
||||||
Duration 504ms
|
[32m✓[39m tests/task-12-links.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
||||||
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K ✓ lib/mail/mailer.test.ts (1 test) 2ms
|
[32m✓[39m tests/messen-content.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
||||||
|
[32m✓[39m lib/mail/mailer.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 7[2mms[22m[39m
|
||||||
❯ lib/env.test.ts 0/6
|
[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.
|
||||||
Test Files 1 passed (2)
|
|
||||||
Tests 1 passed (7)
|
[32m✓[39m components/layout/LanguageSwitcher.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 25[2mms[22m[39m
|
||||||
Start at 11:47:51
|
[32m✓[39m components/search/AISearchResults.test.tsx [2m([22m[2m1 test[22m[2m)[22m[32m 52[2mms[22m[39m
|
||||||
Duration 605ms
|
[90mstderr[2m | components/blocks/ReferencesSlider.test.tsx[2m > [22m[2mReferencesSlider TDD[2m > [22m[2mrenders reference tiles correctly
|
||||||
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K ✓ lib/env.test.ts (6 tests) 3ms
|
[22m[39mReceived `true` for a non-boolean attribute `fill`.
|
||||||
|
|
||||||
Test Files 2 passed (2)
|
If you want to write it to the DOM, pass a string instead: fill="true" or fill={value.toString()}.
|
||||||
Tests 7 passed (7)
|
|
||||||
Start at 11:47:51
|
[32m✓[39m components/blocks/ReferencesSlider.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
||||||
Duration 672ms (transform 113ms, setup 0ms, import 222ms, tests 5ms, environment 813ms)
|
[32m✓[39m lib/env.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
||||||
|
[90mstdout[2m | app/[locale]/referenzen/[slug]/page.test.tsx
|
||||||
[?25h
|
[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 26[2mms[22m[39m
|
||||||
|
|
||||||
|
[31m⎯⎯⎯⎯⎯⎯⎯[39m[1m[41m Failed Tests 1 [49m[22m[31m⎯⎯⎯⎯⎯⎯⎯[39m
|
||||||
|
|
||||||
|
[41m[1m FAIL [22m[49m tests/legal-pages.test.ts[2m > [22mLegal Pages Content Validation[2m > [22mshould contain professional corresponding B2B sections in English Terms
|
||||||
|
[31m[1mError[22m: ENOENT: no such file or directory, open '/Volumes/Alpha SSD/Coding/e-tib.com/content/en/terms.mdx'[39m
|
||||||
|
[36m [2m❯[22m tests/legal-pages.test.ts:[2m25:24[22m[39m
|
||||||
|
[90m 23|[39m
|
||||||
|
[90m 24|[39m it('should contain professional corresponding B2B sections in Englis…
|
||||||
|
[90m 25|[39m [35mconst[39m content [33m=[39m fs[33m.[39m[34mreadFileSync[39m(enTermsPath[33m,[39m [32m'utf8'[39m)[33m;[39m
|
||||||
|
[90m |[39m [31m^[39m
|
||||||
|
[90m 26|[39m
|
||||||
|
[90m 27|[39m [34mexpect[39m(content)[33m.[39m[34mtoContain[39m([32m'title: "Terms and Conditions"'[39m)[33m;[39m
|
||||||
|
|
||||||
|
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯[22m[39m
|
||||||
|
|
||||||
|
|
||||||
|
[2m Test Files [22m [1m[31m1 failed[39m[22m[2m | [22m[1m[32m10 passed[39m[22m[90m (11)[39m
|
||||||
|
[2m Tests [22m [1m[31m1 failed[39m[22m[2m | [22m[1m[32m26 passed[39m[22m[90m (27)[39m
|
||||||
|
[2m Start at [22m 14:54:33
|
||||||
|
[2m Duration [22m 5.94s[2m (transform 5.27s, setup 0ms, import 9.43s, tests 222ms, environment 13.80s)[22m
|
||||||
|
|
||||||
|
ELIFECYCLE Test failed. See above for more details.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
|
> e-tib-nextjs@2.2.12 typecheck /Volumes/Alpha SSD/Coding/e-tib.com
|
||||||
> e-tib-nextjs@2.2.12 typecheck /Volumes/Alpha SSD/Coding/e-tib.com
|
> tsc --noEmit
|
||||||
> tsc --noEmit
|
|
||||||
|
ELIFECYCLE Command failed.
|
||||||
|
|||||||
25
INSTRUCTIONS.md
Normal file
25
INSTRUCTIONS.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Antigravity Instructions & Project Rules
|
||||||
|
|
||||||
|
This document contains critical project-specific rules for the e-tib.com codebase. Antigravity must read and follow these rules at all times.
|
||||||
|
|
||||||
|
## 1. Bilingual Site (German & English) - STRICT
|
||||||
|
- **Always update BOTH languages!** The website is bilingual.
|
||||||
|
- When changing content, adding pages, or updating text, you MUST always check and update both `content/de/` and `content/en/` simultaneously.
|
||||||
|
- When adding new components with text, ensure they are built to support both languages or update the respective translation files/mechanisms. Do NOT just hardcode German text and forget the English version.
|
||||||
|
|
||||||
|
## 2. No Dummy Data / No Hallucinations
|
||||||
|
- Do not invent or hallucinate placeholder text (e.g., "Nachweis liegt vor" for certificates that don't exist).
|
||||||
|
- Only implement what is explicitly requested or what matches provided assets/data.
|
||||||
|
- If data is missing, ask the user instead of inventing placeholders.
|
||||||
|
|
||||||
|
## 3. Git Workflow & RC Tagging
|
||||||
|
- When instructed to "commit and tag" (or similar), always follow this process:
|
||||||
|
1. Add and commit the changes with conventional commits (`feat:`, `fix:`, `chore:`, etc.).
|
||||||
|
2. Check the latest tag using `git describe --tags --abbrev=0`.
|
||||||
|
3. Increment the RC (Release Candidate) number of the current version (e.g., if latest is `v2.2.13-rc19`, the new tag is `v2.2.13-rc20`).
|
||||||
|
4. Create the tag: `git tag vX.Y.Z-rcN`.
|
||||||
|
5. Push the commit and the tag: `git push origin main --tags`.
|
||||||
|
|
||||||
|
## 4. Work Exclusively on the Correct Branch
|
||||||
|
- Follow the `USER_GLOBAL` directive: If on `main`, create a branch unless explicitly told to work on `main`.
|
||||||
|
- For e-tib.com, often small fixes are done on `main` if the user commands it ("auf main arbeiten"), but always verify context.
|
||||||
BIN
after-login.png
Normal file
BIN
after-login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
@@ -10,7 +10,6 @@ import TrackedLink from '@/components/analytics/TrackedLink';
|
|||||||
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||||
import { getAllReferences } from '@/lib/references';
|
import { getAllReferences } from '@/lib/references';
|
||||||
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
||||||
|
|
||||||
// Import components used in MDX
|
// Import components used in MDX
|
||||||
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
|
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
|
||||||
import { SubCompanyTiles as HomeSubCompanyTiles } from '@/components/blocks/SubCompanyTiles';
|
import { SubCompanyTiles as HomeSubCompanyTiles } from '@/components/blocks/SubCompanyTiles';
|
||||||
@@ -162,7 +161,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
|||||||
...loc,
|
...loc,
|
||||||
featuredImage: ref.frontmatter.featuredImage,
|
featuredImage: ref.frontmatter.featuredImage,
|
||||||
details: [
|
details: [
|
||||||
ref.frontmatter.client || 'Kunde',
|
ref.frontmatter.client || t('client'),
|
||||||
ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear().toString(),
|
ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear().toString(),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -187,7 +186,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
|||||||
<div className={`flex flex-col min-h-screen ${isFullBleed ? '' : 'bg-white'}`}>
|
<div className={`flex flex-col min-h-screen ${isFullBleed ? '' : 'bg-white'}`}>
|
||||||
{/* Generic Hero Section (only for standard pages) */}
|
{/* Generic Hero Section (only for standard pages) */}
|
||||||
{!isFullBleed && (
|
{!isFullBleed && (
|
||||||
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
<section key={`hero-section-${slug}`} className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
||||||
<div className="absolute inset-0 opacity-20 pointer-events-none">
|
<div className="absolute inset-0 opacity-20 pointer-events-none">
|
||||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { TransitionProvider } from '@/components/providers/TransitionProvider';
|
|||||||
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
|
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
|
||||||
import { InitialLoader } from '@/components/providers/InitialLoader';
|
import { InitialLoader } from '@/components/providers/InitialLoader';
|
||||||
import { CorporateBackground } from '@/components/decorations/CorporateBackground';
|
import { CorporateBackground } from '@/components/decorations/CorporateBackground';
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
display: 'swap',
|
display: 'swap',
|
||||||
@@ -93,13 +92,18 @@ export default async function Layout(props: {
|
|||||||
children: [
|
children: [
|
||||||
{ label: safeLocale === 'de' ? 'Firma' : 'Company', url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}` },
|
{ label: safeLocale === 'de' ? 'Firma' : 'Company', url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}` },
|
||||||
{ label: safeLocale === 'de' ? 'Unser Team' : 'Our Team', url: `/${safeLocale}/team` },
|
{ label: safeLocale === 'de' ? 'Unser Team' : 'Our Team', url: `/${safeLocale}/team` },
|
||||||
{ label: safeLocale === 'de' ? 'Zertifikate' : 'Certificates', url: `/${safeLocale}/zertifikate` }
|
{ label: safeLocale === 'de' ? 'Zertifikate' : 'Certificates', url: `/${safeLocale}/zertifikate` },
|
||||||
|
{ label: safeLocale === 'de' ? 'Standorte' : 'Locations', url: `/${safeLocale}/standorte` }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: safeLocale === 'de' ? 'Referenzen' : 'References',
|
label: safeLocale === 'de' ? 'Referenzen' : 'References',
|
||||||
url: `/${safeLocale}/referenzen`
|
url: `/${safeLocale}/referenzen`
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: safeLocale === 'de' ? 'Karriere' : 'Career',
|
||||||
|
url: `/${safeLocale}/${safeLocale === 'de' ? 'karriere' : 'career'}`
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: safeLocale === 'de' ? 'Messen' : 'Events',
|
label: safeLocale === 'de' ? 'Messen' : 'Events',
|
||||||
url: `/${safeLocale}/${await mapFileSlugToTranslated('messen', safeLocale)}`
|
url: `/${safeLocale}/${await mapFileSlugToTranslated('messen', safeLocale)}`
|
||||||
@@ -123,6 +127,14 @@ export default async function Layout(props: {
|
|||||||
'Error',
|
'Error',
|
||||||
'StandardPage',
|
'StandardPage',
|
||||||
'Brochure',
|
'Brochure',
|
||||||
|
'JobListingBlock',
|
||||||
|
'CallToAction',
|
||||||
|
'InteractiveGermanyMap',
|
||||||
|
'ReferencesSlider',
|
||||||
|
'CompanyTimeline',
|
||||||
|
'TeamGrid',
|
||||||
|
'AISearch',
|
||||||
|
'GrowthChart',
|
||||||
];
|
];
|
||||||
const clientMessages: Record<string, any> = {};
|
const clientMessages: Record<string, any> = {};
|
||||||
for (const key of clientKeys) {
|
for (const key of clientKeys) {
|
||||||
|
|||||||
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 { notFound } from 'next/navigation';
|
||||||
import { Container, Badge, Heading } from '@/components/ui';
|
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 { Metadata } from 'next';
|
||||||
import { getReferenceBySlug, getAllReferences } from '@/lib/references';
|
import { getReferenceBySlug, getAllReferences } from '@/lib/references';
|
||||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||||
@@ -57,6 +57,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
|||||||
|
|
||||||
setRequestLocale(locale);
|
setRequestLocale(locale);
|
||||||
const reference = await getReferenceBySlug(slug, locale);
|
const reference = await getReferenceBySlug(slug, locale);
|
||||||
|
const t = await getTranslations('ReferenceDetail');
|
||||||
|
|
||||||
if (!reference) {
|
if (!reference) {
|
||||||
notFound();
|
notFound();
|
||||||
@@ -66,22 +67,48 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
|||||||
const mdxComponents = {
|
const mdxComponents = {
|
||||||
Heading,
|
Heading,
|
||||||
h1: (props: any) => <Heading level={1} size="2" className="hidden" {...props} />, // Hidden because Hero handles H1
|
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} />,
|
h2: (props: any) => <Heading level={2} size="3" className="mt-16 mb-6 border-b border-neutral-100 pb-4 text-neutral-dark" {...props} />,
|
||||||
h3: (props: any) => <Heading level={3} size="4" className="mt-12 mb-4 text-primary" {...props} />,
|
h3: (props: any) => <Heading level={3} size="4" className="mt-12 mb-4 text-primary" {...props} />,
|
||||||
h4: (props: any) => <Heading level={4} size="5" className="mt-8 mb-4 uppercase tracking-widest text-neutral-500" {...props} />,
|
h4: (props: any) => <Heading level={4} size="5" className="mt-8 mb-4 uppercase tracking-widest text-neutral-500" {...props} />,
|
||||||
p: (props: any) => <p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl" {...props} />,
|
p: (props: any) => <p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl" {...props} />,
|
||||||
ul: (props: any) => <ul className="grid grid-cols-1 md:grid-cols-2 gap-4 my-12" {...props} />,
|
ul: (props: any) => <ul className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6 my-12" {...props} />,
|
||||||
ol: (props: any) => <ol className="list-decimal pl-6 mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl" {...props} />,
|
ol: (props: any) => <ol className="list-decimal pl-6 mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl" {...props} />,
|
||||||
li: (props: any) => (
|
li: (props: any) => {
|
||||||
<li className="bg-neutral-50 p-6 rounded-2xl border border-neutral-100 flex items-start gap-4 hover:shadow-md transition-shadow group" {...props}>
|
// Smart Metric Card Logic
|
||||||
<div className="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center shrink-0 mt-0.5 group-hover:bg-primary/20 transition-colors">
|
let text = '';
|
||||||
<svg className="w-3.5 h-3.5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
if (typeof props.children === 'string') {
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
text = props.children;
|
||||||
</svg>
|
} else if (Array.isArray(props.children)) {
|
||||||
</div>
|
text = props.children.map((c: any) => typeof c === 'string' ? c : '').join('');
|
||||||
<span className="text-neutral-700 font-semibold leading-snug">{props.children}</span>
|
}
|
||||||
</li>
|
|
||||||
),
|
const match = text.match(/^([\d.,]+)\s*(m|Satz|Stück|km|kV|Fasern)\b(.*)/);
|
||||||
|
|
||||||
|
if (match && typeof props.children === 'string') {
|
||||||
|
const [, value, unit, rest] = match;
|
||||||
|
return (
|
||||||
|
<li className="bg-white p-6 md:p-8 rounded-3xl border border-neutral-100 flex flex-col justify-center hover:shadow-[0_20px_40px_-15px_rgba(0,0,0,0.05)] hover:border-primary/30 transition-all duration-500 group hover:-translate-y-1 relative overflow-hidden">
|
||||||
|
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-neutral-50 to-transparent rounded-bl-full -z-10 group-hover:from-primary/5 transition-colors duration-500" />
|
||||||
|
<div className="flex items-baseline gap-2 mb-3">
|
||||||
|
<span className="text-4xl md:text-5xl font-bold font-heading text-primary tracking-tight">{value}</span>
|
||||||
|
<span className="text-lg md:text-xl font-bold text-neutral-400">{unit}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-neutral-600 font-semibold text-sm md:text-base leading-snug">{rest.replace(/^[\s-]*\s/, '')}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="bg-neutral-50 p-6 rounded-3xl border border-neutral-100 flex items-start gap-4 hover:shadow-md transition-all duration-300 group hover:-translate-y-1">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center shrink-0 mt-0.5 group-hover:bg-primary/20 transition-colors">
|
||||||
|
<svg className="w-4 h-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span className="text-neutral-700 font-semibold leading-relaxed">{props.children}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
},
|
||||||
a: (props: any) => <a className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold" {...props} />,
|
a: (props: any) => <a className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold" {...props} />,
|
||||||
strong: (props: any) => <strong className="font-bold text-neutral-900" {...props} />,
|
strong: (props: any) => <strong className="font-bold text-neutral-900" {...props} />,
|
||||||
blockquote: (props: any) => (
|
blockquote: (props: any) => (
|
||||||
@@ -94,62 +121,62 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen bg-white">
|
<div className="flex flex-col min-h-screen bg-white">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="bg-[#050B14] text-white pt-40 pb-16 md:pt-48 md:pb-20 min-h-[35vh] flex flex-col justify-end relative overflow-hidden">
|
<section className="bg-[#050B14] text-white pt-40 pb-16 md:pt-52 md:pb-24 min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
||||||
{reference.frontmatter.featuredImage && (
|
{reference.frontmatter.featuredImage && (
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
<Image
|
<Image
|
||||||
src={reference.frontmatter.featuredImage}
|
src={reference.frontmatter.featuredImage}
|
||||||
alt={reference.frontmatter.title}
|
alt={reference.frontmatter.title}
|
||||||
fill
|
fill
|
||||||
className="object-cover opacity-25 mix-blend-luminosity scale-105"
|
className="object-cover opacity-30 mix-blend-luminosity scale-105"
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-[#050B14] via-[#050B14]/60 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-[#050B14] via-[#050B14]/70 to-transparent" />
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-[#050B14] via-transparent to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-r from-[#050B14] via-[#050B14]/40 to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute inset-0 opacity-20 z-0">
|
<div className="absolute inset-0 opacity-20 z-0 pointer-events-none">
|
||||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/30 via-transparent to-transparent" />
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/30 via-transparent to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
<Container className="relative z-10">
|
<Container className="relative z-10">
|
||||||
<div className="mb-12">
|
<div className="mb-12">
|
||||||
<TrackedLink
|
<TrackedLink
|
||||||
href={`/${locale}/referenzen`}
|
href={`/${locale}/referenzen`}
|
||||||
className="inline-flex items-center gap-2 text-white/60 hover:text-white transition-colors"
|
className="inline-flex items-center gap-2 text-white/60 hover:text-white transition-colors uppercase tracking-widest text-xs font-bold"
|
||||||
eventProperties={{ location: 'reference_back_btn' }}
|
eventProperties={{ location: 'reference_back_btn' }}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-5 h-5" />
|
<ChevronLeft className="w-4 h-4" />
|
||||||
Zurück zur Übersicht
|
{t('backToOverview')}
|
||||||
</TrackedLink>
|
</TrackedLink>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-4xl">
|
<div className="max-w-5xl">
|
||||||
<Badge variant="accent" className="mb-4 md:mb-6">
|
<Badge variant="accent" className="mb-6">
|
||||||
Projektreferenz
|
{t('projectReference')}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Heading level={1} variant="white" className="mb-8 leading-tight">
|
<Heading level={1} variant="white" className="mb-10 leading-[1.1] md:text-6xl lg:text-7xl drop-shadow-lg">
|
||||||
{reference.frontmatter.title}
|
{reference.frontmatter.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-6 mt-12 md:mt-16">
|
||||||
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-start gap-4 p-6 md:p-8 bg-white/5 rounded-3xl border border-white/10 backdrop-blur-md transition-all hover:bg-white/10">
|
||||||
<MapPin className="w-8 h-8 text-primary shrink-0" />
|
<MapPin className="w-8 h-8 text-primary shrink-0 opacity-80" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Ort</p>
|
<p className="text-xs text-white/50 mb-1.5 uppercase tracking-widest font-bold">{t('location')}</p>
|
||||||
<p className="font-semibold text-lg">{reference.frontmatter.location}</p>
|
<p className="font-semibold text-lg md:text-xl text-white/90">{reference.frontmatter.location}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-start gap-4 p-6 md:p-8 bg-white/5 rounded-3xl border border-white/10 backdrop-blur-md transition-all hover:bg-white/10">
|
||||||
<Briefcase className="w-8 h-8 text-primary shrink-0" />
|
<Briefcase className="w-8 h-8 text-primary shrink-0 opacity-80" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Auftraggeber</p>
|
<p className="text-xs text-white/50 mb-1.5 uppercase tracking-widest font-bold">{t('client')}</p>
|
||||||
<p className="font-semibold text-lg">{reference.frontmatter.client}</p>
|
<p className="font-semibold text-lg md:text-xl text-white/90 line-clamp-2">{reference.frontmatter.client}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
<div className="flex items-start gap-4 p-6 md:p-8 bg-white/5 rounded-3xl border border-white/10 backdrop-blur-md transition-all hover:bg-white/10">
|
||||||
<Calendar className="w-8 h-8 text-primary shrink-0" />
|
<Calendar className="w-8 h-8 text-primary shrink-0 opacity-80" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">Zeitraum</p>
|
<p className="text-xs text-white/50 mb-1.5 uppercase tracking-widest font-bold">{t('period')}</p>
|
||||||
<p className="font-semibold text-lg">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p>
|
<p className="font-semibold text-lg md:text-xl text-white/90">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -160,7 +187,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
|||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
<Container className="py-16 md:py-24">
|
<Container className="py-16 md:py-24">
|
||||||
<div className="max-w-4xl mx-auto">
|
<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">
|
<div className="w-full">
|
||||||
<MDXRemote source={reference.content} components={mdxComponents} />
|
<MDXRemote source={reference.content} components={mdxComponents} />
|
||||||
@@ -173,7 +200,7 @@ export default async function ReferenceDetail(props: { params: Promise<{ locale:
|
|||||||
eventProperties={{ location: 'reference_bottom_back_btn' }}
|
eventProperties={{ location: 'reference_bottom_back_btn' }}
|
||||||
>
|
>
|
||||||
<span className="relative z-10 flex items-center justify-center gap-2">
|
<span className="relative z-10 flex items-center justify-center gap-2">
|
||||||
Alle Referenzen ansehen
|
{t('viewAll')}
|
||||||
</span>
|
</span>
|
||||||
<ButtonOverlay variant="primary" />
|
<ButtonOverlay variant="primary" />
|
||||||
</TrackedLink>
|
</TrackedLink>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server';
|
|||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { getAllReferences } from '@/lib/references';
|
import { getAllReferences } from '@/lib/references';
|
||||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||||
import { MapPin, Calendar, Briefcase, ArrowUpRight } from 'lucide-react';
|
import { MapPin, Calendar, Briefcase } from 'lucide-react';
|
||||||
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||||
import { SITE_URL } from '@/lib/schema';
|
import { SITE_URL } from '@/lib/schema';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
@@ -76,9 +76,14 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
|||||||
<InteractiveGermanyMap
|
<InteractiveGermanyMap
|
||||||
isHero={true}
|
isHero={true}
|
||||||
badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'}
|
badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'}
|
||||||
title={locale === 'en' ? <>Successfully realized<br/>projects.</> : <>Erfolgreich umgesetzte<br/>Projekte.</>}
|
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.'}
|
description={locale === 'en' ? 'From broadband expansion to complex 110kV lines: A selection of our nationwide projects where we have created infrastructure for the future.' : 'Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben.'}
|
||||||
locations={enrichedLocations}
|
locations={enrichedLocations}
|
||||||
|
hideStandorte={true}
|
||||||
|
stats={[
|
||||||
|
{ value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },
|
||||||
|
{ value: enrichedLocations.length.toString(), suffix: '+', label: locale === 'en' ? 'Completed Projects' : 'Abgeschlossene Projekte' }
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
@@ -88,11 +93,8 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
|||||||
<TrackedLink
|
<TrackedLink
|
||||||
key={ref.slug}
|
key={ref.slug}
|
||||||
href={`/${locale}/referenzen/${ref.slug}`}
|
href={`/${locale}/referenzen/${ref.slug}`}
|
||||||
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-2"
|
eventProperties={{ location: 'reference_list_card', slug: ref.slug }}
|
||||||
eventProperties={{
|
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 cursor-pointer block"
|
||||||
location: 'referenzen_grid',
|
|
||||||
target_slug: ref.slug,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col h-full relative">
|
<div className="flex flex-col h-full relative">
|
||||||
{/* Image Section */}
|
{/* Image Section */}
|
||||||
@@ -102,14 +104,14 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
|||||||
src={ref.frontmatter.featuredImage}
|
src={ref.frontmatter.featuredImage}
|
||||||
alt={ref.frontmatter.title}
|
alt={ref.frontmatter.title}
|
||||||
fill
|
fill
|
||||||
className="object-cover group-hover:scale-105 transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%] group-hover:grayscale-0"
|
className="object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%]"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="absolute inset-0 bg-[#f4f4f5] flex items-center justify-center">
|
<div className="absolute inset-0 bg-[#f4f4f5] flex items-center justify-center">
|
||||||
<Image src="/assets/logo.png" alt="E-TIB Logo" width={80} height={80} className="opacity-20 grayscale" />
|
<Image src="/assets/logo.png" alt="E-TIB Logo" width={80} height={80} className="opacity-20 grayscale" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute inset-0 bg-black/5 group-hover:bg-transparent transition-colors duration-500" />
|
<div className="absolute inset-0 bg-black/5 transition-colors duration-500" />
|
||||||
|
|
||||||
{/* Location Badge */}
|
{/* Location Badge */}
|
||||||
<div className="absolute top-4 left-4 z-10">
|
<div className="absolute top-4 left-4 z-10">
|
||||||
@@ -122,21 +124,21 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
|||||||
|
|
||||||
{/* Content Section */}
|
{/* Content Section */}
|
||||||
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
|
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
|
||||||
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 group-hover:text-primary transition-colors line-clamp-3 leading-[1.2]">
|
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 transition-colors line-clamp-3 leading-[1.2] group-hover:text-primary">
|
||||||
{ref.frontmatter.title}
|
{ref.frontmatter.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Technical Meta Data */}
|
{/* 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="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10">
|
||||||
<div className="min-w-0">
|
<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">
|
<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]" />
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<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">
|
<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]" />
|
<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>
|
<span className="line-clamp-2">{ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear()}</span>
|
||||||
@@ -144,11 +146,6 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Industrial Hover Indicator */}
|
|
||||||
<div className="absolute right-6 bottom-6 md:right-8 md:bottom-8 w-10 h-10 bg-neutral-50 border border-neutral-200 rounded flex items-center justify-center transform group-hover:bg-primary group-hover:border-primary transition-colors duration-300">
|
|
||||||
<ArrowUpRight className="w-5 h-5 text-neutral-dark group-hover:text-white transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform duration-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</TrackedLink>
|
</TrackedLink>
|
||||||
))}
|
))}
|
||||||
@@ -158,8 +155,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="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="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">
|
<div className="relative z-10 max-w-2xl">
|
||||||
<h3 className="text-2xl md:text-3xl font-bold mb-4">Ihr nächstes Projekt?</h3>
|
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('nextProjectTitle')}</h3>
|
||||||
<p className="text-lg text-white/70 mb-8">Lassen Sie uns gemeinsam herausragende Infrastruktur bauen.</p>
|
<p className="text-lg text-white/70 mb-8">{t('nextProjectDesc')}</p>
|
||||||
<TrackedLink
|
<TrackedLink
|
||||||
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
|
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
|
||||||
className={getButtonClasses('accent', 'lg')}
|
className={getButtonClasses('accent', 'lg')}
|
||||||
|
|||||||
187
app/[locale]/standorte/[slug]/page.tsx
Normal file
187
app/[locale]/standorte/[slug]/page.tsx
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
import { Container, Badge, Heading } from '@/components/ui';
|
||||||
|
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { standorteData, getStandortById } from '@/lib/standorte-data';
|
||||||
|
import { SITE_URL } from '@/lib/schema';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { MapPin, Phone, Mail, Navigation, CheckCircle2, ArrowUpRight } from 'lucide-react';
|
||||||
|
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||||
|
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||||
|
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{
|
||||||
|
locale: string;
|
||||||
|
slug: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
const locales = ['de', 'en'];
|
||||||
|
const params: { locale: string; slug: string }[] = [];
|
||||||
|
|
||||||
|
for (const locale of locales) {
|
||||||
|
for (const standort of standorteData) {
|
||||||
|
params.push({ locale, slug: standort.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||||
|
const { locale, slug } = await params;
|
||||||
|
if (locale !== 'de' && locale !== 'en') return {};
|
||||||
|
|
||||||
|
const standort = getStandortById(slug);
|
||||||
|
if (!standort) return {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `${standort.name} | E-TIB Gruppe`,
|
||||||
|
description: standort.description[locale],
|
||||||
|
alternates: {
|
||||||
|
canonical: `${SITE_URL}/${locale}/standorte/${slug}`,
|
||||||
|
languages: {
|
||||||
|
de: `${SITE_URL}/de/standorte/${slug}`,
|
||||||
|
en: `${SITE_URL}/en/standorte/${slug}`,
|
||||||
|
'x-default': `${SITE_URL}/en/standorte/${slug}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function StandortDetail(props: { params: Promise<{ locale: string; slug: string }> }) {
|
||||||
|
const { locale, slug } = await props.params;
|
||||||
|
const safeLocale = locale === 'de' ? 'de' : 'en';
|
||||||
|
setRequestLocale(safeLocale);
|
||||||
|
const t = await getTranslations('StandardPage');
|
||||||
|
|
||||||
|
const standort = getStandortById(slug);
|
||||||
|
if (!standort) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen bg-white">
|
||||||
|
{/* Generic Hero Section to match standard pages */}
|
||||||
|
<section key={`standort-hero-${slug}`} className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
||||||
|
<div className="absolute inset-0 opacity-20 pointer-events-none">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
|
||||||
|
</div>
|
||||||
|
<Container className="relative z-10">
|
||||||
|
<div className="max-w-4xl">
|
||||||
|
<Badge variant="accent" className="mb-4 md:mb-6 shadow-sm flex inline-flex items-center gap-2">
|
||||||
|
<MapPin className="w-4 h-4" />
|
||||||
|
{standort.type === 'hq' ? (safeLocale === 'de' ? 'Hauptsitz' : 'Headquarters') : (safeLocale === 'de' ? 'Niederlassung' : 'Branch')}
|
||||||
|
</Badge>
|
||||||
|
<Heading level={1} variant="white" className="mb-0 drop-shadow-sm">
|
||||||
|
{standort.name}
|
||||||
|
</Heading>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Main Content Area */}
|
||||||
|
<Container className="py-16 md:py-24">
|
||||||
|
{/* Excerpt/Lead paragraph */}
|
||||||
|
<div className="mb-16 max-w-4xl mx-auto">
|
||||||
|
<p className="text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic">
|
||||||
|
{standort.description[safeLocale]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main content in prose style */}
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<div className="prose prose-lg md:prose-xl prose-neutral max-w-none prose-headings:font-heading prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-4xl prose-h2:text-3xl prose-h2:mt-12 prose-h2:border-b prose-h2:border-neutral-200 prose-h2:pb-4 prose-h3:text-2xl prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:font-medium prose-a:underline prose-a:underline-offset-4 prose-a:decoration-primary/30 hover:prose-a:decoration-primary prose-strong:text-text-primary prose-strong:font-semibold prose-li:text-text-secondary prose-ul:list-disc prose-ol:list-decimal">
|
||||||
|
|
||||||
|
<img className="rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100" src={standort.image} alt={standort.name} />
|
||||||
|
|
||||||
|
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">{safeLocale === 'de' ? 'Unsere Leistungen vor Ort' : 'Our Local Services'}</h2>
|
||||||
|
<ul className="list-none mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl">
|
||||||
|
{standort.keyFeatures[safeLocale].map((feature, idx) => (
|
||||||
|
<li key={idx} className="relative pl-6 before:content-[''] before:absolute before:left-0 before:top-[0.6em] before:w-2 before:h-2 before:bg-primary/50 before:rounded-sm">
|
||||||
|
{feature}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">{safeLocale === 'de' ? 'Kontakt & Anfahrt' : 'Contact & Directions'}</h2>
|
||||||
|
|
||||||
|
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
|
||||||
|
<strong>{safeLocale === 'de' ? 'Adresse:' : 'Address:'}</strong><br/>
|
||||||
|
{standort.address.street}<br/>
|
||||||
|
{standort.address.city}
|
||||||
|
</p>
|
||||||
|
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
|
||||||
|
<TrackedLink
|
||||||
|
href={`https://maps.google.com/?q=${encodeURIComponent(`${standort.address.street}, ${standort.address.city}`)}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold flex items-center gap-1 w-fit"
|
||||||
|
eventProperties={{ location: 'standort_maps_link', slug: standort.id }}
|
||||||
|
>
|
||||||
|
{safeLocale === 'de' ? 'In Google Maps öffnen' : 'Open in Google Maps'}
|
||||||
|
<ArrowUpRight className="w-4 h-4 inline-block" />
|
||||||
|
</TrackedLink>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
|
||||||
|
<strong>{safeLocale === 'de' ? 'Telefon:' : 'Phone:'}</strong><br/>
|
||||||
|
<a href={`tel:${standort.contact.phone.replace(/[^0-9+]/g, '')}`} className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold">
|
||||||
|
{standort.contact.phone}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl">
|
||||||
|
<strong>Email:</strong><br/>
|
||||||
|
<a href={`mailto:${standort.contact.email}`} className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold">
|
||||||
|
{standort.contact.email}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{standort.gallery && standort.gallery.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h2 className="mt-16 mb-6 border-b border-neutral-100 pb-4">Galerie</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 not-prose my-12">
|
||||||
|
{standort.gallery.map((img, idx) => (
|
||||||
|
<div key={idx} className="relative aspect-[4/3] rounded-2xl overflow-hidden bg-neutral-100 border border-neutral-200 shadow-md hover:shadow-xl transition-shadow duration-300">
|
||||||
|
<Image src={img} alt={`Gallery ${idx + 1}`} fill className="object-cover transition-transform duration-700 hover:scale-105" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
{/* Support Section */}
|
||||||
|
<Container>
|
||||||
|
<div className="mt-12 mb-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group animate-slight-fade-in-from-bottom">
|
||||||
|
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
|
||||||
|
<div className="relative z-10 max-w-2xl">
|
||||||
|
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('needHelp')}</h3>
|
||||||
|
<p className="text-lg text-white/70 mb-8">{t('supportTeamAvailable')}</p>
|
||||||
|
<TrackedLink
|
||||||
|
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
|
||||||
|
className={getButtonClasses('accent', 'lg')}
|
||||||
|
eventProperties={{
|
||||||
|
location: 'standort_page_support_cta',
|
||||||
|
page_slug: slug,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
|
||||||
|
{t('contactUs')}
|
||||||
|
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<ButtonOverlay variant="accent" />
|
||||||
|
</TrackedLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
142
app/[locale]/standorte/page.tsx
Normal file
142
app/[locale]/standorte/page.tsx
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import { Container } from '@/components/ui';
|
||||||
|
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||||
|
import { MapPin, Navigation, ArrowUpRight } from 'lucide-react';
|
||||||
|
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||||
|
import { SITE_URL } from '@/lib/schema';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { standorteData, standorteLocations } from '@/lib/standorte-data';
|
||||||
|
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{
|
||||||
|
locale: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
|
||||||
|
const { locale } = await params;
|
||||||
|
const safeLocale = locale === 'de' ? 'de' : 'en';
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: safeLocale === 'en' ? 'Our Locations' : 'Unsere Standorte',
|
||||||
|
description: safeLocale === 'en' ? 'Our operational locations across Germany.' : 'Unsere operativen Standorte deutschlandweit.',
|
||||||
|
alternates: {
|
||||||
|
canonical: `${SITE_URL}/${safeLocale}/standorte`,
|
||||||
|
languages: {
|
||||||
|
de: `${SITE_URL}/de/standorte`,
|
||||||
|
en: `${SITE_URL}/en/standorte`,
|
||||||
|
'x-default': `${SITE_URL}/en/standorte`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function StandorteOverview(props: { params: Promise<{ locale: string }> }) {
|
||||||
|
const { locale } = await props.params;
|
||||||
|
const safeLocale = locale === 'de' ? 'de' : 'en';
|
||||||
|
setRequestLocale(safeLocale);
|
||||||
|
const t = await getTranslations('StandardPage');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24">
|
||||||
|
{/* Map Hero Section */}
|
||||||
|
<InteractiveGermanyMap
|
||||||
|
isHero={true}
|
||||||
|
locations={standorteLocations}
|
||||||
|
badge={safeLocale === 'en' ? 'Our Locations' : 'Unsere Standorte'}
|
||||||
|
title={safeLocale === 'en' ? 'Nationwide operational for you.' : 'Deutschlandweit für Sie im Einsatz.'}
|
||||||
|
description={safeLocale === 'en'
|
||||||
|
? 'From our strategic locations in Guben, Kirchheilingen and Bülstedt, we control and implement complex infrastructure projects nationwide.'
|
||||||
|
: 'Von unseren strategischen Standorten in Guben, Kirchheilingen und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Main Content Area */}
|
||||||
|
<Container className="relative z-20 mt-16 md:mt-24">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
|
||||||
|
{standorteData.map((standort) => (
|
||||||
|
<TrackedLink
|
||||||
|
key={standort.id}
|
||||||
|
href={`/${safeLocale}/standorte/${standort.id}`}
|
||||||
|
eventProperties={{ location: 'standorte_list_card', slug: standort.id }}
|
||||||
|
className="group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 cursor-pointer block"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col h-full relative">
|
||||||
|
{/* Image Section */}
|
||||||
|
<div className="relative h-64 w-full bg-[#050B14] overflow-hidden shrink-0 border-b border-neutral-100">
|
||||||
|
<Image
|
||||||
|
src={standort.image}
|
||||||
|
alt={standort.name}
|
||||||
|
fill
|
||||||
|
className="object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%] group-hover:scale-105 group-hover:grayscale-0"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-black/10 transition-colors duration-500" />
|
||||||
|
|
||||||
|
{/* Location Badge */}
|
||||||
|
<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" />
|
||||||
|
{standort.address.city}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Section */}
|
||||||
|
<div className="flex flex-col flex-grow p-6 md:p-8 bg-white">
|
||||||
|
<div className="flex items-start justify-between gap-4 mb-4">
|
||||||
|
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark transition-colors leading-[1.2] group-hover:text-primary">
|
||||||
|
{standort.name}
|
||||||
|
</h3>
|
||||||
|
<ArrowUpRight className="w-5 h-5 text-neutral-400 group-hover:text-primary transition-colors shrink-0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-neutral-500 text-sm leading-relaxed mb-8 line-clamp-3">
|
||||||
|
{standort.description[safeLocale]}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Meta Data */}
|
||||||
|
<div className="flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<span className="block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1">
|
||||||
|
{safeLocale === 'de' ? 'Adresse' : 'Address'}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight">
|
||||||
|
<Navigation className="w-3.5 h-3.5 text-primary shrink-0 mt-[2px]" />
|
||||||
|
<span className="line-clamp-2">{standort.address.street}, {standort.address.city}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TrackedLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Support Section */}
|
||||||
|
<div className="mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group">
|
||||||
|
<div className="absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3" />
|
||||||
|
<div className="relative z-10 max-w-2xl">
|
||||||
|
<h3 className="text-2xl md:text-3xl font-bold mb-4">{t('nextProjectTitle')}</h3>
|
||||||
|
<p className="text-lg text-white/70 mb-8">{t('nextProjectDesc')}</p>
|
||||||
|
<TrackedLink
|
||||||
|
href={`/${safeLocale}/${safeLocale === 'de' ? 'kontakt' : 'contact'}`}
|
||||||
|
className={getButtonClasses('accent', 'lg')}
|
||||||
|
eventProperties={{
|
||||||
|
location: 'standorte_support_cta',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark">
|
||||||
|
{t('contactUs')}
|
||||||
|
<span className="ml-2 transition-transform group-hover/btn:translate-x-1">
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<ButtonOverlay variant="accent" />
|
||||||
|
</TrackedLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ export async function sendContactFormAction(formData: FormData) {
|
|||||||
const confirmationHtml = await render(
|
const confirmationHtml = await render(
|
||||||
React.createElement(ConfirmationMessage, {
|
React.createElement(ConfirmationMessage, {
|
||||||
name,
|
name,
|
||||||
clientName: 'KLZ Cables',
|
clientName: 'E-TIB GmbH',
|
||||||
// brandColor: '#82ed20', // Optional: could be KLZ specific
|
// brandColor: '#82ed20', // Optional: could be KLZ specific
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hide-scrollbar {
|
.hide-scrollbar {
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
-ms-overflow-style: none !important; /* IE and Edge */
|
||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none !important; /* Firefox */
|
||||||
}
|
}
|
||||||
.hide-scrollbar::-webkit-scrollbar {
|
.hide-scrollbar::-webkit-scrollbar {
|
||||||
display: none; /* Chrome, Safari and Opera */
|
display: none !important; /* Chrome, Safari and Opera */
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes bg-pulse-15 {
|
@keyframes bg-pulse-15 {
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ const LeafletMap = dynamic(() => import('@/components/LeafletMap'), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface ContactMapProps {
|
interface ContactMapProps {
|
||||||
address: string;
|
address?: string;
|
||||||
lat: number;
|
lat?: number;
|
||||||
lng: number;
|
lng?: number;
|
||||||
|
locations?: Array<{name: string, address: string, lat: number, lng: number}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ContactMap({ address, lat, lng }: ContactMapProps) {
|
export default function ContactMap({ address, lat, lng, locations }: ContactMapProps) {
|
||||||
return <LeafletMap locations={[{ name: 'E-TIB Gruppe', address, lat, lng }]} />;
|
const mapLocations = locations || (address && lat && lng ? [{ name: 'E-TIB Gruppe', address, lat, lng }] : []);
|
||||||
|
return <LeafletMap locations={mapLocations} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function LeafletMap({ locations }: LeafletMapProps) {
|
|||||||
const firstLoc = locations[0];
|
const firstLoc = locations[0];
|
||||||
const map = L.map(mapRef.current, {
|
const map = L.map(mapRef.current, {
|
||||||
center: [firstLoc.lat, firstLoc.lng],
|
center: [firstLoc.lat, firstLoc.lng],
|
||||||
zoom: 6, // Zoom out to see both or more
|
zoom: locations.length === 1 ? 16 : 6,
|
||||||
scrollWheelZoom: false,
|
scrollWheelZoom: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ interface TrackedLinkProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
target?: string;
|
||||||
|
rel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +27,8 @@ export default function TrackedLink({
|
|||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
onClick,
|
onClick,
|
||||||
|
target,
|
||||||
|
rel,
|
||||||
}: TrackedLinkProps) {
|
}: TrackedLinkProps) {
|
||||||
const { trackEvent } = useAnalytics();
|
const { trackEvent } = useAnalytics();
|
||||||
|
|
||||||
@@ -41,7 +45,7 @@ export default function TrackedLink({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={href} className={className} onClick={handleClick}>
|
<Link href={href} className={className} onClick={handleClick} target={target} rel={rel}>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,12 +3,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export interface CallToActionProps {
|
export interface CallToActionProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
text?: string; // Alias for description
|
||||||
ctaLabel?: string;
|
ctaLabel?: string;
|
||||||
|
buttonText?: string; // Alias for ctaLabel
|
||||||
ctaHref?: string;
|
ctaHref?: string;
|
||||||
|
buttonLink?: string; // Alias for ctaHref
|
||||||
theme?: 'light' | 'dark';
|
theme?: 'light' | 'dark';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,14 +36,15 @@ const itemVariants = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CallToAction: React.FC<CallToActionProps> = ({
|
export const CallToAction: React.FC<CallToActionProps> = (props) => {
|
||||||
title = 'Bereit für Ihr Projekt?',
|
const { theme = 'light' } = props;
|
||||||
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'
|
|
||||||
}) => {
|
|
||||||
const isDark = theme === 'dark';
|
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 (
|
return (
|
||||||
<div className={`py-24 text-center ${isDark ? 'bg-primary-dark text-white' : 'bg-neutral-50 border-t border-neutral-100'}`}>
|
<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 {
|
interface Certificate {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
pdfUrl: string;
|
pdfUrl?: string;
|
||||||
type: 'iso' | 'tax' | 'general';
|
type: 'iso' | 'tax' | 'general';
|
||||||
date?: string;
|
date?: string;
|
||||||
}
|
}
|
||||||
@@ -29,45 +29,46 @@ const defaultCertificates: Certificate[] = [
|
|||||||
{
|
{
|
||||||
title: 'ISO 14001:2015',
|
title: 'ISO 14001:2015',
|
||||||
description: 'Umweltmanagementsystem',
|
description: 'Umweltmanagementsystem',
|
||||||
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 14001 Umweltmanagement.pdf',
|
pdfUrl: '/assets/certificates/iso14001.pdf',
|
||||||
type: 'iso',
|
type: 'iso',
|
||||||
date: '14.12.2023',
|
date: '14.12.2023',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ISO 9001:2015',
|
title: 'ISO 9001:2015',
|
||||||
description: 'Qualitätsmanagementsystem',
|
description: 'Qualitätsmanagementsystem',
|
||||||
pdfUrl: '/assets/certificates/231214_Zertifikat ISO 9001 Qualitätsmanagement.pdf',
|
pdfUrl: '/assets/certificates/iso9001.pdf',
|
||||||
type: 'iso',
|
type: 'iso',
|
||||||
date: '14.12.2023',
|
date: '14.12.2023',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'DIN EN ISO 45001:2018',
|
title: 'DIN EN ISO 45001:2018',
|
||||||
description: 'Arbeits- und Gesundheitsschutz',
|
description: 'Arbeits- und Gesundheitsschutz',
|
||||||
pdfUrl: '/assets/certificates/Zertifizierung DIN EN ISO 45001 bis 05122028.pdf',
|
pdfUrl: '/assets/certificates/iso45001.pdf',
|
||||||
type: 'iso',
|
type: 'iso',
|
||||||
date: '05.12.2025', // Assuming valid till
|
date: '05.12.2025',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Freistellungsbescheinigung',
|
title: 'Freistellungsbescheinigung',
|
||||||
description: 'Nach § 48 b EStG',
|
description: 'Nach § 48 b EStG',
|
||||||
pdfUrl: '/assets/certificates/240209_Freistellungsbescheinigung § 48 b.pdf',
|
pdfUrl: '/assets/certificates/freistellung.pdf',
|
||||||
type: 'tax',
|
type: 'tax',
|
||||||
date: '09.02.2024',
|
date: '09.02.2024',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Nachweis § 13b UStG',
|
title: 'Nachweis § 13b UStG',
|
||||||
description: 'Steuerschuldnerschaft des Leistungsempfängers',
|
description: 'Steuerschuldnerschaft des Leistungsempfängers',
|
||||||
pdfUrl: '/assets/certificates/240209_Nachweis § 13 b.pdf',
|
pdfUrl: '/assets/certificates/nachweis13b.pdf',
|
||||||
type: 'tax',
|
type: 'tax',
|
||||||
date: '09.02.2024',
|
date: '09.02.2024',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Bescheinigung in Steuersachen',
|
title: 'Bescheinigung in Steuersachen',
|
||||||
description: 'Zertifikat des Finanzamtes',
|
description: 'Zertifikat des Finanzamtes',
|
||||||
pdfUrl: '/assets/certificates/250213_Bescheinigung in Steuersachen.pdf',
|
pdfUrl: '/assets/certificates/bescheinigung.pdf',
|
||||||
type: 'tax',
|
type: 'tax',
|
||||||
date: '13.02.2025',
|
date: '13.02.2025',
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {
|
export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {
|
||||||
@@ -143,13 +144,17 @@ export function CertificatesBlock({ badge, title, description, certificates = de
|
|||||||
>
|
>
|
||||||
{certificates.map((cert, index) => {
|
{certificates.map((cert, index) => {
|
||||||
const isIso = cert.type === 'iso';
|
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 (
|
return (
|
||||||
<motion.a
|
<Wrapper
|
||||||
key={index}
|
key={index}
|
||||||
href={cert.pdfUrl}
|
{...wrapperProps as any}
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
variants={itemVariants}
|
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 ${
|
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
|
isIso
|
||||||
@@ -196,14 +201,22 @@ export function CertificatesBlock({ badge, title, description, certificates = de
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</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'
|
isIso ? 'text-primary-light' : 'text-primary'
|
||||||
}`}>
|
}`}>
|
||||||
<span>Download PDF</span>
|
{cert.pdfUrl ? (
|
||||||
<Download size={16} className="ml-2 group-hover:translate-y-0.5 transition-transform" />
|
<>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</motion.a>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -129,9 +129,10 @@ export const ContactSection: React.FC<ContactSectionProps> = (props) => {
|
|||||||
fallback={<div className="h-full w-full bg-neutral-medium animate-pulse" />}
|
fallback={<div className="h-full w-full bg-neutral-medium animate-pulse" />}
|
||||||
>
|
>
|
||||||
<ContactMap
|
<ContactMap
|
||||||
address="Gewerbestraße 22\n03172 Guben"
|
locations={[
|
||||||
lat={51.9547}
|
{ name: 'E-TIB GmbH (Hauptsitz)', address: 'Gewerbestraße 22\n03172 Guben', lat: 51.9547, lng: 14.7214 },
|
||||||
lng={14.7214}
|
{ name: 'E-TIB Bohrtechnik GmbH', address: 'Bahnhofstraße 180a\n99947 Kirchheilingen', lat: 51.1852, lng: 10.6865 }
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</motion.section>
|
</motion.section>
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import { useLocale } from 'next-intl';
|
|||||||
export function DataGridPulse() {
|
export function DataGridPulse() {
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full py-32 bg-[#050B14] overflow-hidden flex flex-col items-center border-y border-primary/20 -mx-[50vw] px-[50vw] mb-16">
|
<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 */}
|
{/* Blueprint grid background */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 opacity-20 pointer-events-none"
|
className="absolute inset-0 opacity-[0.03] pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: 'linear-gradient(#82ED20 1px, transparent 1px), linear-gradient(90deg, #82ED20 1px, transparent 1px)',
|
backgroundImage: 'linear-gradient(#117C61 1px, transparent 1px), linear-gradient(90deg, #117C61 1px, transparent 1px)',
|
||||||
backgroundSize: '40px 40px'
|
backgroundSize: '40px 40px'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -21,25 +21,25 @@ export function DataGridPulse() {
|
|||||||
{/* Pulsing horizontal lines (Data Flow) */}
|
{/* Pulsing horizontal lines (Data Flow) */}
|
||||||
<motion.div
|
<motion.div
|
||||||
animate={{ x: ["-100%", "100%"] }}
|
animate={{ x: ["-100%", "100%"] }}
|
||||||
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
|
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 to-transparent opacity-50 blur-[2px]"
|
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
|
<motion.div
|
||||||
animate={{ x: ["100%", "-100%"] }}
|
animate={{ x: ["100%", "-100%"] }}
|
||||||
transition={{ duration: 4, repeat: Infinity, ease: "linear" }}
|
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-white to-transparent opacity-30 blur-[2px]"
|
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-4xl mx-auto flex flex-col items-center text-center">
|
<div className="relative z-10 w-full max-w-2xl px-6 mx-auto flex flex-col items-center text-center">
|
||||||
<h3 className="text-sm font-bold uppercase tracking-widest text-primary mb-6">
|
<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'}
|
{locale === 'en' ? 'Network Expansion in Numbers' : 'Netzausbau in Zahlen'}
|
||||||
</h3>
|
</span>
|
||||||
|
|
||||||
<div className="bg-black/40 backdrop-blur-md p-12 md:p-16 rounded-3xl border border-white/10 shadow-2xl">
|
<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-6xl md:text-8xl font-black text-white tracking-tighter mb-4 leading-none" style={{ fontVariantNumeric: 'tabular-nums' }}>
|
<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-3xl md:text-5xl ml-2">m</span>
|
372.161<span className="text-primary text-2xl md:text-4xl ml-1 font-sans">m</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/70 text-lg md:text-xl font-medium max-w-lg mx-auto">
|
<p className="text-text-secondary text-base md:text-lg font-medium max-w-md mx-auto leading-relaxed">
|
||||||
{locale === 'en'
|
{locale === 'en'
|
||||||
? 'Cable routes laid since 2023. A massive infrastructure achievement, driven by our own machinery.'
|
? '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.'}
|
: 'Verlegte Kabeltrassen seit 2023. Eine massive Infrastrukturleistung, getragen von unserem eigenen Maschinenpark.'}
|
||||||
|
|||||||
@@ -2,12 +2,22 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export function GrowthChart() {
|
export function GrowthChart() {
|
||||||
|
const t = useTranslations('GrowthChart');
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{ year: '2023', hdd: 29436, kabel: 76727 },
|
{ year: '2016', offener: 5000, hdd: 1200, kabel: 16064 },
|
||||||
{ year: '2024', hdd: 28680, kabel: 131964 },
|
{ year: '2017', offener: 33401, hdd: 8363, kabel: 84675 },
|
||||||
{ year: '2025', hdd: 44655, kabel: 163470 },
|
{ year: '2018', offener: 30773, hdd: 10225, kabel: 108127 },
|
||||||
|
{ year: '2019', offener: 32445, hdd: 8505, kabel: 87468 },
|
||||||
|
{ year: '2020', offener: 31305, hdd: 9050, kabel: 93556 },
|
||||||
|
{ year: '2021', offener: 38756, hdd: 10957, kabel: 110127 },
|
||||||
|
{ year: '2022', offener: 41445, hdd: 9505, kabel: 102468 },
|
||||||
|
{ year: '2023', offener: 17908, hdd: 29436, kabel: 76727 },
|
||||||
|
{ year: '2024', offener: 30801, hdd: 28680, kabel: 131964 },
|
||||||
|
{ year: '2025', offener: 38166, hdd: 44655, kabel: 163470 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const maxVal = 163470; // Highest value for scaling
|
const maxVal = 163470; // Highest value for scaling
|
||||||
@@ -15,14 +25,15 @@ export function GrowthChart() {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full bg-white rounded-3xl p-8 md:p-12 shadow-sm border border-neutral-100 my-16">
|
<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">
|
<div className="mb-10">
|
||||||
<h3 className="text-3xl font-heading font-extrabold text-neutral-dark mb-2">Bewiesene Leistungsfähigkeit</h3>
|
<h3 className="text-3xl font-heading font-extrabold text-neutral-dark mb-2">{t('title')}</h3>
|
||||||
<p className="text-neutral-500 font-medium">Unsere verlässliche Baukapazität der letzten drei Jahre (in Metern)</p>
|
<p className="text-neutral-500 font-medium">{t('subtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{data.map((item, index) => {
|
{data.map((item, index) => {
|
||||||
const hddWidth = (item.hdd / maxVal) * 100;
|
|
||||||
const kabelWidth = (item.kabel / maxVal) * 100;
|
const kabelWidth = (item.kabel / maxVal) * 100;
|
||||||
|
const offenerWidth = (item.offener / maxVal) * 100;
|
||||||
|
const hddWidth = (item.hdd / maxVal) * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.year} className="relative">
|
<div key={item.year} className="relative">
|
||||||
@@ -40,7 +51,23 @@ export function GrowthChart() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
||||||
Kabel: {new Intl.NumberFormat('de-DE').format(item.kabel)}m
|
{t('kabel')}: {new Intl.NumberFormat('de-DE').format(item.kabel)}m
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Offener Tiefbau Bar */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex-1 bg-neutral-100 rounded-full h-5 overflow-hidden relative">
|
||||||
|
<motion.div
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
whileInView={{ width: `${offenerWidth}%` }}
|
||||||
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
|
transition={{ duration: 1.5, delay: 0.15 + 0.1 * index, ease: "easeOut" }}
|
||||||
|
className="absolute top-0 left-0 h-full bg-neutral-300 rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
||||||
|
{t('offener')}: {new Intl.NumberFormat('de-DE').format(item.offener)}m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -56,7 +83,7 @@ export function GrowthChart() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
||||||
HDD: {new Intl.NumberFormat('de-DE').format(item.hdd)}m
|
{t('hdd')}: {new Intl.NumberFormat('de-DE').format(item.hdd)}m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,14 +92,18 @@ export function GrowthChart() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 flex gap-6 border-t border-neutral-100 pt-6">
|
<div className="mt-8 flex flex-wrap gap-6 border-t border-neutral-100 pt-6">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 rounded-full bg-primary" />
|
<div className="w-3 h-3 rounded-full bg-primary" />
|
||||||
<span className="text-xs font-bold text-neutral-500 uppercase">Kabelverlegung</span>
|
<span className="text-xs font-bold text-neutral-500 uppercase">{t('kabel')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-neutral-300" />
|
||||||
|
<span className="text-xs font-bold text-neutral-500 uppercase">{t('offener')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 rounded-full bg-neutral-dark" />
|
<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>
|
<span className="text-xs font-bold text-neutral-500 uppercase">{t('hdd')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Badge, Container, Heading } from '@/components/ui';
|
import { Badge, Container, Heading } from '@/components/ui';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
@@ -39,13 +40,15 @@ const itemVariants = {
|
|||||||
export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
||||||
const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props;
|
const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props;
|
||||||
const bgSrc = backgroundImage?.sizes?.card?.url || backgroundImage?.url;
|
const bgSrc = backgroundImage?.sizes?.card?.url || backgroundImage?.url;
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={`relative min-h-[45vh] md:min-h-[55vh] flex items-center pt-32 pb-20 md:pt-36 md:pb-24 overflow-hidden bg-neutral-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`}
|
className={`relative min-h-[45vh] md:min-h-[55vh] flex items-center pt-24 pb-16 md:pt-36 md:pb-24 overflow-hidden bg-neutral-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`}
|
||||||
>
|
>
|
||||||
{bgSrc && (
|
{bgSrc && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
key={`hero-bg-${pathname}`}
|
||||||
className="absolute inset-0 z-0"
|
className="absolute inset-0 z-0"
|
||||||
initial={{ scale: 1.05, opacity: 0 }}
|
initial={{ scale: 1.05, opacity: 0 }}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
@@ -75,6 +78,7 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
|
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
|
||||||
<motion.div
|
<motion.div
|
||||||
|
key={`hero-content-${pathname}`}
|
||||||
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
|
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/Button';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
interface HeroVideoProps {
|
interface HeroVideoProps {
|
||||||
data?: any;
|
data?: any;
|
||||||
@@ -28,6 +29,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
const subtitle = props.subtitle || props.description || data?.subtitle || 'Wir verbinden Infrastruktur mit Präzision. Von Horizontalbohrungen bis zu komplexen Leitungsnetzen.';
|
const subtitle = props.subtitle || props.description || data?.subtitle || 'Wir verbinden Infrastruktur mit Präzision. Von Horizontalbohrungen bis zu komplexen Leitungsnetzen.';
|
||||||
const explicitNoVideo = props.videoUrl === 'none' || props.videoUrl === '';
|
const explicitNoVideo = props.videoUrl === 'none' || props.videoUrl === '';
|
||||||
const videoUrl = explicitNoVideo ? null : (props.videoUrl || data?.videoUrl || '/assets/dummy-hero.mp4');
|
const videoUrl = explicitNoVideo ? null : (props.videoUrl || data?.videoUrl || '/assets/dummy-hero.mp4');
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
const posterSrc = props.backgroundImage?.url || props.posterImage?.url || data?.posterImage?.url || "/assets/photos/DJI_0048.JPG";
|
const posterSrc = props.backgroundImage?.url || props.posterImage?.url || data?.posterImage?.url || "/assets/photos/DJI_0048.JPG";
|
||||||
const posterAlt = props.backgroundImage?.alt || data?.posterImage?.alt || "E-TIB Gruppe Baustelle";
|
const posterAlt = props.backgroundImage?.alt || data?.posterImage?.alt || "E-TIB Gruppe Baustelle";
|
||||||
@@ -35,19 +37,10 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
const ctaLabel = props.ctaLabel || props.linkText || data?.ctaLabel || 'Unternehmen entdecken';
|
const ctaLabel = props.ctaLabel || props.linkText || data?.ctaLabel || 'Unternehmen entdecken';
|
||||||
const ctaHref = props.ctaHref || props.linkHref || data?.ctaHref || '#unternehmen';
|
const ctaHref = props.ctaHref || props.linkHref || data?.ctaHref || '#unternehmen';
|
||||||
|
|
||||||
let currentLocale = 'de';
|
const currentLocale = pathname?.startsWith('/en') ? 'en' : 'de';
|
||||||
try {
|
|
||||||
// We can't unconditionally call hooks if they might fail outside context, but HeroVideo is usually in context.
|
|
||||||
// However, to be safe and avoid adding new imports, we can use window.location or default to 'de' if not available.
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
currentLocale = window.location.pathname.startsWith('/en') ? 'en' : 'de';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback to default locale if window/location access fails
|
|
||||||
}
|
|
||||||
|
|
||||||
const secondaryCtaLabel = props.secondaryCtaLabel || data?.secondaryCtaLabel || (currentLocale === 'de' ? 'Projekt anfragen' : 'Inquire Project');
|
const secondaryCtaLabel = props.secondaryCtaLabel || data?.secondaryCtaLabel || (currentLocale === 'de' ? 'Projekt anfragen' : 'Inquire Project');
|
||||||
const secondaryCtaHref = props.secondaryCtaHref || data?.secondaryCtaHref || `/${currentLocale}/${currentLocale === 'de' ? 'contact' : 'contact'}`;
|
const secondaryCtaHref = props.secondaryCtaHref || data?.secondaryCtaHref || `/${currentLocale}/contact`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-[100svh] flex items-center justify-center overflow-hidden bg-neutral-dark">
|
<div className="relative w-full h-[100svh] flex items-center justify-center overflow-hidden bg-neutral-dark">
|
||||||
@@ -56,7 +49,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
|
|
||||||
{videoUrl ? (
|
{videoUrl ? (
|
||||||
<video
|
<video
|
||||||
key={videoUrl}
|
key={`${pathname}-${videoUrl}`}
|
||||||
className="absolute inset-0 w-full h-full object-cover z-1 pointer-events-none filter contrast-125 saturate-110 brightness-90"
|
className="absolute inset-0 w-full h-full object-cover z-1 pointer-events-none filter contrast-125 saturate-110 brightness-90"
|
||||||
src={videoUrl}
|
src={videoUrl}
|
||||||
autoPlay
|
autoPlay
|
||||||
@@ -67,6 +60,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Image
|
<Image
|
||||||
|
key={`img-${pathname}-${posterSrc}`}
|
||||||
src={posterSrc}
|
src={posterSrc}
|
||||||
alt={posterAlt}
|
alt={posterAlt}
|
||||||
fill
|
fill
|
||||||
@@ -88,13 +82,14 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
|
|
||||||
<div className="container relative z-[4] text-center px-4">
|
<div className="container relative z-[4] text-center px-4">
|
||||||
<motion.div
|
<motion.div
|
||||||
|
key={`hero-content-${pathname}`}
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1] }}
|
transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1] }}
|
||||||
>
|
>
|
||||||
<h1
|
<h1
|
||||||
className="font-heading font-bold text-4xl md:text-6xl lg:text-8xl text-white mb-6 uppercase tracking-tight drop-shadow-lg"
|
className="font-heading font-bold text-4xl md:text-6xl lg:text-8xl text-white mb-6 uppercase tracking-tight drop-shadow-lg"
|
||||||
dangerouslySetInnerHTML={{ __html: title.replace('KABELTIEFBAU', '<span class="text-primary-light">KABELTIEFBAU</span>').replace(/\\n|\n/g, '<br />') }}
|
dangerouslySetInnerHTML={{ __html: title.replace(/KABELTIEFBAU|Kabeltiefbau/g, '<span class="text-primary-light">Kabeltiefbau</span>').replace(/KABELNETZBAU|Kabelnetzbau/g, '<span class="text-primary-light">Kabelnetzbau</span>').replace(/\\n|\n/g, '<br />') }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className="text-lg md:text-xl lg:text-2xl text-white/90 max-w-3xl mx-auto mb-12 drop-shadow">
|
<p className="text-lg md:text-xl lg:text-2xl text-white/90 max-w-3xl mx-auto mb-12 drop-shadow">
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ import React, { useState } from 'react';
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
|
import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter, usePathname } from 'next/navigation';
|
||||||
|
import Link from 'next/link';
|
||||||
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
||||||
import { Location, defaultLocations, minorLocations } from '@/lib/map-data';
|
import { Location, defaultLocations, minorLocations, projectLocations } from '@/lib/map-data';
|
||||||
|
import { standorteLocations } from '@/lib/standorte-data';
|
||||||
import { useLocale, useTranslations } from 'next-intl';
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
|
||||||
const allLocations = [...defaultLocations, ...minorLocations];
|
|
||||||
|
|
||||||
interface Stat {
|
interface Stat {
|
||||||
value: string;
|
value: string;
|
||||||
suffix?: string;
|
suffix?: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
href?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InteractiveGermanyMapProps {
|
interface InteractiveGermanyMapProps {
|
||||||
@@ -24,37 +25,50 @@ interface InteractiveGermanyMapProps {
|
|||||||
stats?: Stat[];
|
stats?: Stat[];
|
||||||
locations?: Location[];
|
locations?: Location[];
|
||||||
isHero?: boolean;
|
isHero?: boolean;
|
||||||
|
hideStandorte?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InteractiveGermanyMap({
|
export function InteractiveGermanyMap({
|
||||||
badge,
|
badge,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
stats = [
|
stats,
|
||||||
{ value: '100', suffix: '%', label: 'Überregionale Reichweite' },
|
locations = projectLocations,
|
||||||
{ value: '2', suffix: '+', label: 'Operative Standorte' },
|
isHero = false,
|
||||||
],
|
hideStandorte = false
|
||||||
locations = allLocations,
|
|
||||||
isHero = false
|
|
||||||
}: InteractiveGermanyMapProps) {
|
}: InteractiveGermanyMapProps) {
|
||||||
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
|
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const t = useTranslations('InteractiveGermanyMap');
|
const t = useTranslations('InteractiveGermanyMap');
|
||||||
const tStandard = useTranslations('StandardPage');
|
const tStandard = useTranslations('StandardPage');
|
||||||
|
|
||||||
|
const finalStats = stats || [
|
||||||
|
{ value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },
|
||||||
|
{ value: '3', suffix: '', label: locale === 'en' ? 'Operational Locations' : 'Operative Standorte', href: `/${locale}/standorte` },
|
||||||
|
];
|
||||||
|
|
||||||
const finalBadge = badge || tStandard('badge');
|
const finalBadge = badge || tStandard('badge');
|
||||||
// the map is mostly used with specific props, so we leave title & description to fallbacks if not provided
|
// 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 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 finalDescription = description || (locale === 'en' ? 'From our strategic locations in Guben, Kirchheilingen and Bülstedt, we control and implement complex infrastructure projects nationwide.' : 'Von unseren strategischen Standorten in Guben, Kirchheilingen und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.');
|
||||||
|
|
||||||
const hq = locations.find((l) => l.type === 'hq');
|
const finalLocations = React.useMemo(() => {
|
||||||
const branch = locations.find((l) => l.type === 'branch');
|
if (hideStandorte) return locations;
|
||||||
const projects = locations.filter((l) => l.type === 'project');
|
|
||||||
const minorNodes = locations.filter((l) => l.type === 'minor_node');
|
// Ensure the 3 standorte are always included, without duplicating if already present by ID
|
||||||
|
const base = [...locations];
|
||||||
|
standorteLocations.forEach(sl => {
|
||||||
|
if (!base.some(l => l.id === sl.id)) {
|
||||||
|
base.push(sl as Location);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return base;
|
||||||
|
}, [locations, hideStandorte]);
|
||||||
|
|
||||||
return (
|
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"}>
|
<div key={isHero ? `hero-map-${pathname}` : undefined} 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"}>
|
||||||
<div className={`bg-[#050B14] ${isHero ? 'pt-32 pb-16 md:pt-40 md:pb-24' : 'rounded-[2.5rem] md:rounded-[3.5rem] shadow-2xl border border-white/5'} overflow-visible relative group/map`}>
|
<div className={`bg-[#050B14] ${isHero ? 'pt-32 pb-16 md:pt-40 md:pb-24' : 'rounded-[2.5rem] md:rounded-[3.5rem] shadow-2xl border border-white/5'} overflow-visible relative group/map`}>
|
||||||
{/* Animated Border Glow */}
|
{/* Animated Border Glow */}
|
||||||
{!isHero && <AnimatedGlossyBorder opacity={0.7} className="z-30" />}
|
{!isHero && <AnimatedGlossyBorder opacity={0.7} className="z-30" />}
|
||||||
@@ -73,11 +87,11 @@ export function InteractiveGermanyMap({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isHero ? (
|
{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]">
|
<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}
|
{finalTitle}
|
||||||
</h1>
|
</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]">
|
<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}
|
{finalTitle}
|
||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
@@ -88,17 +102,32 @@ export function InteractiveGermanyMap({
|
|||||||
|
|
||||||
{/* Industrial Stats Grid */}
|
{/* Industrial Stats Grid */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<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">
|
const StatCard = () => (
|
||||||
<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={`bg-white/5 border border-white/10 rounded-2xl p-6 backdrop-blur-md relative overflow-hidden group h-full ${stat.href ? 'cursor-pointer' : ''}`}>
|
||||||
<div className="relative z-10">
|
<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="text-4xl font-extrabold text-white mb-1 flex items-baseline gap-1">
|
<div className="relative z-10 flex flex-col h-full justify-between">
|
||||||
{stat.value}<span className="text-xl text-primary">{stat.suffix}</span>
|
<div className="text-4xl font-extrabold text-white mb-1 flex items-baseline gap-1">
|
||||||
|
{stat.value}<span className="text-xl text-primary">{stat.suffix}</span>
|
||||||
|
</div>
|
||||||
|
<div className={`text-sm text-white/50 font-medium flex items-center ${stat.href ? 'group-hover:text-primary transition-colors' : ''}`}>
|
||||||
|
{stat.label}
|
||||||
|
{stat.href && <ArrowUpRight className="w-4 h-4 ml-1 opacity-0 -translate-y-1 translate-x-1 group-hover:opacity-100 group-hover:translate-y-0 group-hover:translate-x-0 transition-all duration-300" />}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-white/50 font-medium">{stat.label}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
|
||||||
|
return stat.href ? (
|
||||||
|
<Link key={i} href={stat.href} className="block group/link hover:-translate-y-1 transition-transform duration-300">
|
||||||
|
<StatCard />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div key={i}>
|
||||||
|
<StatCard />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -119,7 +148,7 @@ export function InteractiveGermanyMap({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Minor Location Markers (The 100+ generic projects) */}
|
{/* Minor Location Markers (The 100+ generic projects) */}
|
||||||
{locations.filter(l => l.type === 'minor_node').map((loc, idx) => (
|
{finalLocations.filter(l => l.type === 'minor_node' || l.type === 'project').map((loc, idx) => (
|
||||||
<div
|
<div
|
||||||
key={`minor-${idx}`}
|
key={`minor-${idx}`}
|
||||||
className="absolute z-10 group/minor cursor-pointer w-10 h-10 flex items-center justify-center"
|
className="absolute z-10 group/minor cursor-pointer w-10 h-10 flex items-center justify-center"
|
||||||
@@ -136,8 +165,8 @@ export function InteractiveGermanyMap({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Major & Reference Location Markers (HQ, Branch, Project) */}
|
{/* Major Location Markers (HQ, Branch) */}
|
||||||
{locations.filter(l => l.type !== 'minor_node').map((loc, idx) => {
|
{finalLocations.filter(l => l.type === 'hq' || l.type === 'branch').map((loc, idx) => {
|
||||||
const isHQ = loc.type === 'hq';
|
const isHQ = loc.type === 'hq';
|
||||||
const isBranch = loc.type === 'branch';
|
const isBranch = loc.type === 'branch';
|
||||||
const isActive = activeLocation?.id === loc.id;
|
const isActive = activeLocation?.id === loc.id;
|
||||||
@@ -150,7 +179,10 @@ export function InteractiveGermanyMap({
|
|||||||
onMouseEnter={() => setActiveLocation(loc)}
|
onMouseEnter={() => setActiveLocation(loc)}
|
||||||
onMouseLeave={() => setActiveLocation(null)}
|
onMouseLeave={() => setActiveLocation(null)}
|
||||||
onClick={() => {
|
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 */}
|
{/* Ping Animation for HQ / Branch */}
|
||||||
@@ -251,7 +283,7 @@ export function InteractiveGermanyMap({
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeLocation.href && (
|
{activeLocation.href && (activeLocation.type === 'hq' || activeLocation.type === 'branch') && (
|
||||||
<div className="inline-flex items-center text-xs font-bold text-primary">
|
<div className="inline-flex items-center text-xs font-bold text-primary">
|
||||||
{t('learnMore')}
|
{t('learnMore')}
|
||||||
<ArrowUpRight className="w-3.5 h-3.5 ml-1" />
|
<ArrowUpRight className="w-3.5 h-3.5 ml-1" />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Link from 'next/link';
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export interface JobListingBlockProps {
|
export interface JobListingBlockProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -45,20 +46,23 @@ const itemVariants = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const JobListingBlock = (props: JobListingBlockProps) => {
|
export const JobListingBlock = (props: JobListingBlockProps) => {
|
||||||
|
const t = useTranslations('JobListingBlock');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title,
|
|
||||||
showJobs = true,
|
showJobs = true,
|
||||||
showFairs,
|
showFairs,
|
||||||
fairsTitle = 'Nächste Messetermine',
|
fairsTitle = t('fairsTitle'),
|
||||||
fairs = [
|
fairs = [
|
||||||
{ name: 'Intersolar München', date: 'Termin folgt', type: 'Energie-Messe', location: 'Messe München' },
|
{ 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: 'Windenergietage Linstow', date: 'Termin folgt', type: 'Fachkongress', location: 'Linstow' },
|
||||||
{ name: 'Kabelwerkstatt Wiesbaden', date: 'Termin folgt', type: 'Fachmesse', location: 'Wiesbaden' }
|
{ name: 'Kabelwerkstatt Wiesbaden', date: 'Termin folgt', type: 'Fachmesse', location: 'Wiesbaden' }
|
||||||
],
|
],
|
||||||
emptyStateMessage = 'Aktuell sind alle Positionen besetzt. Senden Sie uns gerne eine Initiativbewerbung!',
|
emptyStateMessage = t('emptyStateMessage'),
|
||||||
emptyStateLinkText = 'Jetzt Kontakt aufnehmen',
|
emptyStateLinkText = t('emptyStateLinkText'),
|
||||||
emptyStateLinkHref = '/kontakt'
|
emptyStateLinkHref = t('emptyStateLinkHref')
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const title = props.title || t('title');
|
||||||
|
|
||||||
// Since Payload is removed, we use an empty array or static data
|
// Since Payload is removed, we use an empty array or static data
|
||||||
const jobs: any[] = [];
|
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>
|
<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>
|
||||||
<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>
|
<p className="text-white/90 font-medium">{messe.date}</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<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>
|
||||||
<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>
|
<p className="text-white/90 font-medium">{messe.location}</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<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>
|
||||||
<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>
|
<p className="text-primary-light font-bold text-sm">{messe.booth}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,7 +184,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
|||||||
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
|
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"
|
className="font-heading font-extrabold text-4xl text-neutral-dark mb-8"
|
||||||
>
|
>
|
||||||
{title || 'Aktuelle Stellenangebote'}
|
{title}
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
{jobs.length > 0 ? (
|
{jobs.length > 0 ? (
|
||||||
@@ -206,7 +210,7 @@ export const JobListingBlock = (props: JobListingBlockProps) => {
|
|||||||
<p className="text-text-secondary mt-1">{job.location}</p>
|
<p className="text-text-secondary mt-1">{job.location}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-primary font-bold group-hover:translate-x-2 transition-transform">
|
<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>
|
<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>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { ReferencesSlider, Reference } from './ReferencesSlider';
|
import { ReferencesSlider, Reference } from './ReferencesSlider';
|
||||||
import { NextIntlClientProvider } from 'next-intl';
|
|
||||||
|
|
||||||
// Mock next/link to render standard <a> tag
|
// Mock next/link to render standard <a> tag
|
||||||
vi.mock('next/link', () => ({
|
vi.mock('next/link', () => ({
|
||||||
@@ -52,45 +51,14 @@ describe('ReferencesSlider TDD', () => {
|
|||||||
expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();
|
expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('wraps each tile in a link to /referenzen/[slug]', () => {
|
it('renders each tile as a static block and NOT wrapped in a link', () => {
|
||||||
render(<ReferencesSlider references={mockReferences} />);
|
render(<ReferencesSlider references={mockReferences} />);
|
||||||
|
|
||||||
const links = screen.getAllByTestId('reference-tile-link');
|
const links = screen.queryAllByTestId('reference-tile-link');
|
||||||
expect(links).toHaveLength(2);
|
expect(links).toHaveLength(0);
|
||||||
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)', () => {
|
const tiles = screen.getAllByTestId('reference-tile');
|
||||||
render(<ReferencesSlider references={mockReferences} />);
|
expect(tiles).toHaveLength(2);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -92,11 +92,6 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
|||||||
dragDistanceRef.current = Math.abs(x - startXRef.current);
|
dragDistanceRef.current = Math.abs(x - startXRef.current);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTileClick = (e: React.MouseEvent) => {
|
|
||||||
if (dragDistanceRef.current > 10) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="referenzen" className="py-24 bg-neutral-dark text-white relative overflow-hidden">
|
<section id="referenzen" className="py-24 bg-neutral-dark text-white relative overflow-hidden">
|
||||||
@@ -137,11 +132,18 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
|||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`/${locale}/referenzen/${ref.slug}`}
|
href={`/${locale}/referenzen/${ref.slug}`}
|
||||||
onClick={handleTileClick}
|
data-testid="reference-tile"
|
||||||
data-testid="reference-tile-link"
|
draggable={false}
|
||||||
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
|
onClick={(e) => {
|
||||||
|
if (dragDistanceRef.current > 15) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
dragDistanceRef.current = 0;
|
||||||
|
}}
|
||||||
|
className="block relative select-none aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
|
draggable={false}
|
||||||
src={imgSrc}
|
src={imgSrc}
|
||||||
alt={ref.title}
|
alt={ref.title}
|
||||||
fill
|
fill
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function ScaleOfImpact() {
|
|||||||
className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none select-none overflow-hidden mix-blend-overlay"
|
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">
|
<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>
|
<span className="font-mono tracking-tighter">1.435.223</span>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@@ -73,26 +73,30 @@ export function ScaleOfImpact() {
|
|||||||
<motion.path
|
<motion.path
|
||||||
d="M0,50 Q250,0 500,50 T1000,50"
|
d="M0,50 Q250,0 500,50 T1000,50"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#82ED20" // primary color
|
stroke="#ffc107" // warning/yellow color
|
||||||
strokeWidth="4"
|
strokeWidth="4"
|
||||||
style={{ pathLength }}
|
style={{ pathLength }}
|
||||||
className="drop-shadow-[0_0_15px_rgba(130,237,32,0.8)]"
|
className="drop-shadow-[0_0_15px_rgba(255,193,7,0.8)]"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Static Data Grid (No jumping text) */}
|
{/* 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="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12 w-full max-w-5xl mt-12">
|
||||||
<div className="flex flex-col items-center">
|
<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="font-mono text-4xl md:text-5xl font-black text-white tracking-tighter mb-2">200 <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 className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Projects' : 'Projekte'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<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="font-mono text-4xl md:text-5xl font-black text-white tracking-tighter mb-2">974 <span className="text-primary text-2xl">km</span></div>
|
||||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Cable Laying' : 'Kabelverlegung'}</div>
|
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Cable Laying' : 'Kabelverlegung'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<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="font-mono text-4xl md:text-5xl font-black text-white tracking-tighter mb-2">300 <span className="text-primary text-2xl">km</span></div>
|
||||||
|
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Open Trenching' : 'Offener Tiefbau'}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="font-mono text-4xl md:text-5xl font-black text-white tracking-tighter mb-2">160 <span className="text-primary text-2xl">km</span></div>
|
||||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'HDD Drilling' : 'Spülbohrung'}</div>
|
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'HDD Drilling' : 'Spülbohrung'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export function ServiceDetailGrid({
|
|||||||
|
|
||||||
<div className="relative z-10 flex flex-col md:flex-row justify-between items-start mb-12 gap-6">
|
<div className="relative z-10 flex flex-col md:flex-row justify-between items-start mb-12 gap-6">
|
||||||
<div className="pt-2 flex-1 min-w-0 pr-4">
|
<div className="pt-2 flex-1 min-w-0 pr-4">
|
||||||
<h4 className="text-2xl md:text-3xl font-extrabold text-neutral-dark uppercase tracking-wider break-words hyphens-auto" lang="de">{panel.title}</h4>
|
<h4 className="text-xl md:text-2xl font-extrabold text-neutral-dark uppercase tracking-wider text-balance break-words" lang="de">{panel.title}</h4>
|
||||||
<div className="h-1 w-12 bg-primary mt-6 transition-all duration-700 group-hover:w-24 rounded-full"></div>
|
<div className="h-1 w-12 bg-primary mt-6 transition-all duration-700 group-hover:w-24 rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { motion, Variants } from 'framer-motion';
|
import { motion, Variants } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||||
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"
|
className="flex overflow-x-auto md:overflow-visible snap-x snap-mandatory pb-6 md:pb-0 md:grid md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 hide-scrollbar -mx-4 px-4 md:mx-0 md:px-0"
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
whileInView="visible"
|
whileInView="visible"
|
||||||
@@ -87,8 +88,9 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
|
|||||||
{companiesData.map((company: any, index: number) => {
|
{companiesData.map((company: any, index: number) => {
|
||||||
const isCurrent = company.title.toUpperCase() === 'E-TIB GMBH';
|
const isCurrent = company.title.toUpperCase() === 'E-TIB GMBH';
|
||||||
const hasUrl = !!company.url;
|
const hasUrl = !!company.url;
|
||||||
const CardWrapper = hasUrl ? 'a' : 'div';
|
const isExternal = hasUrl && company.url.startsWith('http');
|
||||||
const wrapperProps = hasUrl ? { href: company.url, target: "_blank", rel: "noopener noreferrer" } : {};
|
const CardWrapper = hasUrl ? (isExternal ? 'a' : Link) : 'div';
|
||||||
|
const wrapperProps = hasUrl ? (isExternal ? { href: company.url, target: "_blank", rel: "noopener noreferrer" } : { href: company.url }) : {};
|
||||||
|
|
||||||
// Generate logo representation based on title
|
// Generate logo representation based on title
|
||||||
const isEtib = company.title.includes('E-TIB');
|
const isEtib = company.title.includes('E-TIB');
|
||||||
@@ -112,33 +114,21 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
|
|||||||
const positionClass = bgPositionClasses[index % bgPositionClasses.length];
|
const positionClass = bgPositionClasses[index % bgPositionClasses.length];
|
||||||
const animationClass = animationClasses[index % animationClasses.length];
|
const animationClass = animationClasses[index % animationClasses.length];
|
||||||
|
|
||||||
return (
|
const CardContent = (
|
||||||
<motion.div
|
<>
|
||||||
key={index}
|
<div className="absolute inset-0 bg-[#0a192f] z-0">
|
||||||
variants={itemVariants}
|
<Image
|
||||||
className="h-full"
|
src={company.backgroundImage}
|
||||||
>
|
alt="Background"
|
||||||
<CardWrapper
|
fill
|
||||||
{...wrapperProps}
|
className={`object-cover transition-all duration-1000 ease-out ${
|
||||||
className={`group relative overflow-hidden aspect-[4/3] flex flex-col justify-center items-center shadow-xl transition-all duration-500 bg-neutral-dark block w-full h-full sm:rounded-xl select-none ${
|
isCurrent
|
||||||
isCurrent
|
? 'opacity-30 grayscale-0 scale-105'
|
||||||
? 'border-2 border-primary shadow-[0_0_30px_rgba(17,124,97,0.15)] cursor-default'
|
: 'opacity-40 grayscale-[50%] group-hover:opacity-30 group-hover:grayscale-0 group-hover:scale-110'
|
||||||
: 'border border-white/5 hover:shadow-2xl hover:-translate-y-1 hover:border-white/10 cursor-pointer'
|
}`}
|
||||||
}`}
|
sizes="(max-width: 768px) 100vw, 33vw"
|
||||||
>
|
/>
|
||||||
<div className="absolute inset-0 bg-[#0a192f] z-0">
|
</div>
|
||||||
<Image
|
|
||||||
src={company.backgroundImage}
|
|
||||||
alt="Background"
|
|
||||||
fill
|
|
||||||
className={`object-cover transition-all duration-1000 ease-out ${
|
|
||||||
isCurrent
|
|
||||||
? 'opacity-30 grayscale-0 scale-105'
|
|
||||||
: 'opacity-40 grayscale-[50%] group-hover:opacity-30 group-hover:grayscale-0 group-hover:scale-110'
|
|
||||||
}`}
|
|
||||||
sizes="(max-width: 768px) 100vw, 33vw"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Heavy dark gradient overlay to make logos pop */}
|
{/* Heavy dark gradient overlay to make logos pop */}
|
||||||
<div className={`absolute inset-0 z-0 transition-colors duration-700 mix-blend-multiply ${
|
<div className={`absolute inset-0 z-0 transition-colors duration-700 mix-blend-multiply ${
|
||||||
@@ -227,13 +217,42 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
|
|||||||
{company.url && (
|
{company.url && (
|
||||||
<div className="absolute bottom-6 left-0 right-0 flex justify-center opacity-0 group-hover:opacity-100 transform translate-y-4 group-hover:translate-y-0 transition-all duration-500 z-20">
|
<div className="absolute bottom-6 left-0 right-0 flex justify-center opacity-0 group-hover:opacity-100 transform translate-y-4 group-hover:translate-y-0 transition-all duration-500 z-20">
|
||||||
<div className="flex items-center text-white font-bold uppercase text-xs tracking-widest bg-white/10 backdrop-blur-md px-4 py-2 rounded-full border border-white/20">
|
<div className="flex items-center text-white font-bold uppercase text-xs tracking-widest bg-white/10 backdrop-blur-md px-4 py-2 rounded-full border border-white/20">
|
||||||
Website Besuchen
|
{isExternal ? (isDe ? 'Website Besuchen' : 'Visit Website') : (isDe ? 'Zum Standort' : 'View Location')}
|
||||||
<svg className="w-4 h-4 ml-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
<svg className="w-4 h-4 ml-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</CardWrapper>
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const sharedClass = `group relative overflow-hidden aspect-[4/3] flex flex-col justify-center items-center shadow-xl transition-all duration-500 bg-neutral-dark block w-full h-full sm:rounded-xl select-none ${
|
||||||
|
isCurrent
|
||||||
|
? 'border border-white/5 hover:shadow-2xl hover:-translate-y-1 hover:border-white/10 cursor-pointer'
|
||||||
|
: 'border border-white/5 hover:shadow-2xl hover:-translate-y-1 hover:border-white/10 cursor-pointer'
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
key={index}
|
||||||
|
variants={itemVariants}
|
||||||
|
className="h-full flex-shrink-0 snap-center w-[85%] sm:w-[70%] md:w-full"
|
||||||
|
>
|
||||||
|
{hasUrl ? (
|
||||||
|
isExternal ? (
|
||||||
|
<a href={company.url} target="_blank" rel="noopener noreferrer" className={sharedClass}>
|
||||||
|
{CardContent}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<Link href={company.url as string} className={sharedClass}>
|
||||||
|
{CardContent}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div className={sharedClass}>
|
||||||
|
{CardContent}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export interface TeamMember {
|
export interface TeamMember {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -22,90 +23,23 @@ interface TeamGridProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TeamGrid({ members }: TeamGridProps) {
|
export function TeamGrid({ members }: TeamGridProps) {
|
||||||
|
const t = useTranslations('TeamGrid');
|
||||||
|
|
||||||
if (!members || members.length === 0) return null;
|
if (!members || members.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-24 bg-white relative overflow-hidden">
|
<section className="py-24 bg-white relative overflow-hidden">
|
||||||
<div className="container relative z-10">
|
<div className="container relative z-10">
|
||||||
<div className="mb-12">
|
<div className="mb-12">
|
||||||
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">Persönliche Beratung</h2>
|
<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">Ihre Ansprechpartner</h3>
|
<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">
|
<p className="text-text-secondary max-w-2xl text-lg">
|
||||||
Sprechen Sie direkt mit unseren Experten für Ihr regionales Projekt.
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{members.length > 0 && (
|
|
||||||
<div className="mb-20">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 30 }}
|
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="relative rounded-[2.5rem] bg-neutral-dark overflow-hidden shadow-2xl border border-white/10"
|
|
||||||
>
|
|
||||||
{/* Premium Background Effects */}
|
|
||||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(238,114,3,0.15),transparent_50%)] pointer-events-none" />
|
|
||||||
<div className="absolute top-0 right-0 w-[500px] h-[500px] bg-primary/20 blur-[120px] mix-blend-screen rounded-full pointer-events-none" />
|
|
||||||
<div className="absolute inset-0 opacity-20 pointer-events-none" style={{ backgroundImage: "url('data:image/svg+xml,%3Csvg width=\\'40\\' height=\\'40\\' viewBox=\\'0 0 40 40\\' xmlns=\\'http://www.w3.org/2000/svg\\'%3E%3Cpath d=\\'M0 0h40v40H0V0zm20 20h20v20H20V20zM0 20h20v20H0V20z\\' fill=\\'%23ffffff\\' fill-opacity=\\'0.02\\' fill-rule=\\'evenodd\\'/%3E%3C/svg%3E')" }} />
|
|
||||||
|
|
||||||
<div className="relative z-10 flex flex-col lg:flex-row items-center justify-between p-8 md:p-12 lg:p-20 gap-12 lg:gap-16">
|
|
||||||
{/* Text Content */}
|
|
||||||
<div className="w-full lg:w-[55%] flex flex-col justify-center text-white">
|
|
||||||
<div className="inline-flex items-center space-x-2 px-4 py-1.5 mb-8 rounded-full border border-primary/30 bg-primary/10 text-primary text-xs font-bold uppercase tracking-widest self-start backdrop-blur-md shadow-[0_0_20px_rgba(238,114,3,0.2)]">
|
|
||||||
<span className="relative flex h-2 w-2 mr-1">
|
|
||||||
<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
|
|
||||||
</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}
|
|
||||||
</h3>
|
|
||||||
<p className="text-xl md:text-2xl text-white/70 font-medium mb-10 leading-relaxed max-w-xl">
|
|
||||||
{members[0].position}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
|
||||||
{members[0].email && (
|
|
||||||
<a href={`mailto:${members[0].email}`} className="group flex items-center justify-center sm:justify-start gap-4 bg-white/5 hover:bg-primary border border-white/10 hover:border-primary px-6 py-4 rounded-2xl transition-all duration-300 shadow-lg">
|
|
||||||
<svg className="w-5 h-5 text-white/70 group-hover:text-white transition-colors" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
|
||||||
<span className="font-semibold text-white/90 group-hover:text-white transition-colors">{members[0].email}</span>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
{members[0].phone && (
|
|
||||||
<a href={`tel:${members[0].phone}`} className="group flex items-center justify-center sm:justify-start gap-4 bg-white/5 hover:bg-white/10 border border-white/10 px-6 py-4 rounded-2xl transition-all duration-300 shadow-lg">
|
|
||||||
<svg className="w-5 h-5 text-white/70 group-hover:text-white transition-colors" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
|
|
||||||
<span className="font-semibold text-white/90 group-hover:text-white transition-colors">{members[0].phone}</span>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Premium Image Block */}
|
|
||||||
<div className="w-full lg:w-[45%] flex justify-center lg:justify-end perspective-1000">
|
|
||||||
<div className="relative w-64 h-64 md:w-80 md:h-80 lg:w-[400px] lg:h-[400px] rounded-[3rem] overflow-hidden border-4 border-white/10 shadow-[0_30px_60px_rgba(0,0,0,0.6)] transform md:rotate-3 hover:rotate-0 transition-all duration-700 bg-neutral-800 group">
|
|
||||||
{members[0].image && (typeof members[0].image === 'string' ? members[0].image : members[0].image.url) ? (
|
|
||||||
<Image
|
|
||||||
src={typeof members[0].image === 'string' ? members[0].image : members[0].image.url}
|
|
||||||
alt={members[0].name}
|
|
||||||
fill
|
|
||||||
sizes="400px"
|
|
||||||
className="object-cover scale-110 group-hover:scale-100 transition-transform duration-1000 ease-out"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center text-white/20">
|
|
||||||
<svg className="w-20 h-20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 xl:gap-10">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 xl:gap-10">
|
||||||
{members.slice(1).map((member, i) => (
|
{members.map((member, i) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={member.id}
|
key={member.id}
|
||||||
initial={{ opacity: 0, y: 30 }}
|
initial={{ opacity: 0, y: 30 }}
|
||||||
@@ -145,7 +79,7 @@ export function TeamGrid({ members }: TeamGridProps) {
|
|||||||
{member.branch && (
|
{member.branch && (
|
||||||
<div className="mb-6">
|
<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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export function CorporateBackground() {
|
|||||||
const y5 = useTransform(scrollYProgress, [0, 1], [0, -500]);
|
const y5 = useTransform(scrollYProgress, [0, 1], [0, -500]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 z-[50] overflow-hidden pointer-events-none" aria-hidden="true">
|
<div className="absolute inset-0 z-[50] overflow-hidden pointer-events-none hidden md:block" aria-hidden="true">
|
||||||
{/* 1. Top Right - Medium, rotating moderately */}
|
{/* 1. Top Right - Medium, rotating moderately */}
|
||||||
<motion.div
|
<motion.div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
41
components/decorations/EUFundingBadge.tsx
Normal file
41
components/decorations/EUFundingBadge.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
export function EUFundingBadge() {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 1.5, ease: [0.16, 1, 0.3, 1] }}
|
||||||
|
className="absolute top-0 right-0 w-[260px] md:w-[380px] h-[260px] md:h-[380px] z-0 pointer-events-none select-none overflow-hidden"
|
||||||
|
style={{
|
||||||
|
// Perfekter weicher Fade, der sicherstellt, dass harte Kanten komplett unsichtbar werden
|
||||||
|
maskImage: 'radial-gradient(100% 100% at top right, black 10%, rgba(0,0,0,0.8) 30%, transparent 70%)',
|
||||||
|
WebkitMaskImage: 'radial-gradient(100% 100% at top right, black 10%, rgba(0,0,0,0.8) 30%, transparent 70%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Background Flag */}
|
||||||
|
<div className="absolute inset-0 w-full h-full opacity-95">
|
||||||
|
<Image
|
||||||
|
src="/assets/eu-flag.webp"
|
||||||
|
alt="European Union Background"
|
||||||
|
fill
|
||||||
|
className="object-cover object-top"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Text overlaid on the flag */}
|
||||||
|
<div className="absolute top-0 right-0 p-5 md:p-8 flex flex-col items-end text-right z-10 drop-shadow-[0_2px_8px_rgba(0,0,0,0.9)]">
|
||||||
|
<span className="block text-[9px] md:text-[11px] font-bold text-white/90 tracking-wide leading-snug drop-shadow-md">
|
||||||
|
Kofinanziert von der
|
||||||
|
</span>
|
||||||
|
<span className="block text-[11px] md:text-[13px] font-extrabold text-white tracking-wider leading-snug uppercase drop-shadow-md">
|
||||||
|
Europäischen Union
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
|
import { sendContactFormAction } from '@/app/actions/contact';
|
||||||
|
|
||||||
export function ContactForm() {
|
export function ContactForm() {
|
||||||
const [status, setStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
const [status, setStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||||
@@ -11,10 +12,18 @@ export function ContactForm() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setStatus('loading');
|
setStatus('loading');
|
||||||
|
|
||||||
// Simulate server action using @mintel/mail or API route
|
const formData = new FormData(e.currentTarget);
|
||||||
setTimeout(() => {
|
try {
|
||||||
setStatus('success');
|
const result = await sendContactFormAction(formData);
|
||||||
}, 1500);
|
if (result.success) {
|
||||||
|
setStatus('success');
|
||||||
|
} else {
|
||||||
|
setStatus('error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
setStatus('error');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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>
|
<p>Vielen Dank für Ihr Interesse an der E-TIB Gruppe. Ein Ansprechpartner wird sich zeitnah mit Ihnen in Verbindung setzen.</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.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">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<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
|
<input
|
||||||
id="firstName"
|
id="name"
|
||||||
|
name="name"
|
||||||
required
|
required
|
||||||
disabled={status === 'loading'}
|
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"
|
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">
|
<div className="space-y-2">
|
||||||
<label htmlFor="lastName" className="block text-sm font-semibold text-neutral-dark">Nachname</label>
|
<label htmlFor="lastName" className="block text-sm font-semibold text-neutral-dark">Nachname</label>
|
||||||
<input
|
<input
|
||||||
id="lastName"
|
id="lastName"
|
||||||
|
name="lastName"
|
||||||
required
|
required
|
||||||
disabled={status === 'loading'}
|
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"
|
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>
|
<label htmlFor="email" className="block text-sm font-semibold text-neutral-dark">E-Mail Adresse</label>
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
required
|
required
|
||||||
disabled={status === 'loading'}
|
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>
|
<label htmlFor="company" className="block text-sm font-semibold text-neutral-dark">Unternehmen (Optional)</label>
|
||||||
<input
|
<input
|
||||||
id="company"
|
id="company"
|
||||||
|
name="company"
|
||||||
disabled={status === 'loading'}
|
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"
|
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"
|
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>
|
<label htmlFor="message" className="block text-sm font-semibold text-neutral-dark">Ihre Nachricht</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="message"
|
id="message"
|
||||||
|
name="message"
|
||||||
required
|
required
|
||||||
disabled={status === 'loading'}
|
disabled={status === 'loading'}
|
||||||
rows={5}
|
rows={5}
|
||||||
@@ -100,6 +131,8 @@ export function ContactForm() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input type="text" name="company_website" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={status === 'loading'}
|
disabled={status === 'loading'}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useAnalytics } from '../analytics/useAnalytics';
|
|||||||
import { AnalyticsEvents } from '../analytics/analytics-events';
|
import { AnalyticsEvents } from '../analytics/analytics-events';
|
||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { ChevronRight } from 'lucide-react';
|
import { ChevronRight } from 'lucide-react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import { AISearchResults } from '../search/AISearchResults';
|
import { AISearchResults } from '../search/AISearchResults';
|
||||||
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
|
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
|
||||||
const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
|
const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
|
||||||
@@ -15,28 +16,29 @@ const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
|
|||||||
export default function Hero({ data }: { data?: any }) {
|
export default function Hero({ data }: { data?: any }) {
|
||||||
const t = useTranslations('Home.hero');
|
const t = useTranslations('Home.hero');
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
|
const pathname = usePathname();
|
||||||
const { trackEvent } = useAnalytics();
|
const { trackEvent } = useAnalytics();
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||||
const [heroPlaceholder, setHeroPlaceholder] = useState(
|
const [heroPlaceholder, setHeroPlaceholder] = useState(
|
||||||
'Projekt beschreiben oder Kabel suchen...',
|
t('searchPlaceholder')
|
||||||
);
|
);
|
||||||
const typingRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const typingRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
const HERO_PLACEHOLDERS = [
|
const HERO_PLACEHOLDERS = [
|
||||||
'Querschnittsberechnung für 110kV Trasse', // Hochspannung
|
t('placeholder1'),
|
||||||
'Wie schwer ist NAYY 4x150?',
|
t('placeholder2'),
|
||||||
'Ich plane einen Solarpark, was brauche ich?', // Projekt Solar
|
t('placeholder3'),
|
||||||
'Unterschied zwischen N2XSY und NAY2XSY?', // Fach
|
t('placeholder4'),
|
||||||
'Mittelspannungskabel für Windkraftanlage', // Windpark
|
t('placeholder5'),
|
||||||
'Welches Aluminiumkabel für 20kV?', // Mittelspannung
|
t('placeholder6'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Typing animation for the hero search placeholder
|
// Typing animation for the hero search placeholder
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
setHeroPlaceholder('Projekt beschreiben oder Kabel suchen...');
|
setHeroPlaceholder(t('searchPlaceholder'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ export default function Hero({ data }: { data?: any }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Section className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
<Section key={`home-hero-${pathname}`} className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
||||||
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
|
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
|
||||||
<div className="max-w-5xl mx-auto md:mx-0">
|
<div className="max-w-5xl mx-auto md:mx-0">
|
||||||
<div>
|
<div>
|
||||||
@@ -152,7 +154,7 @@ export default function Hero({ data }: { data?: any }) {
|
|||||||
size="lg"
|
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"
|
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" />
|
<ChevronRight className="w-5 h-5 ml-2 -mr-1" />
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
@@ -160,7 +162,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 className="flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
href="/contact"
|
href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}
|
||||||
variant="white"
|
variant="white"
|
||||||
size="lg"
|
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"
|
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"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useLocale } from 'next-intl';
|
|||||||
import { ShieldCheck, Leaf, Lock, Accessibility, Zap, Mail, Phone } from 'lucide-react';
|
import { ShieldCheck, Leaf, Lock, Accessibility, Zap, Mail, Phone } from 'lucide-react';
|
||||||
import { LanguageSwitcher } from './LanguageSwitcher';
|
import { LanguageSwitcher } from './LanguageSwitcher';
|
||||||
import { Tooltip } from '@/components/ui/Tooltip';
|
import { Tooltip } from '@/components/ui/Tooltip';
|
||||||
|
import { EUFundingBadge } from '@/components/decorations/EUFundingBadge';
|
||||||
interface CompanyInfo {
|
interface CompanyInfo {
|
||||||
contactEmail: string;
|
contactEmail: string;
|
||||||
contactPhone: string;
|
contactPhone: string;
|
||||||
@@ -18,7 +19,7 @@ export function Footer({ companyInfo }: FooterProps) {
|
|||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="bg-[#050B14] text-neutral-light py-10 md:py-16 relative overflow-hidden border-t border-white/5">
|
<footer className="bg-[#050B14] text-neutral-light pt-10 pb-28 md:py-16 relative overflow-hidden border-t border-white/5">
|
||||||
{/* Subtle background tech grid */}
|
{/* Subtle background tech grid */}
|
||||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:32px_32px] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_100%)] pointer-events-none" />
|
<div className="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:32px_32px] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_100%)] pointer-events-none" />
|
||||||
|
|
||||||
@@ -26,9 +27,11 @@ export function Footer({ companyInfo }: FooterProps) {
|
|||||||
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-primary/40 to-transparent" />
|
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-primary/40 to-transparent" />
|
||||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1/3 h-[200px] bg-primary/20 blur-[100px] rounded-full pointer-events-none opacity-50" />
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1/3 h-[200px] bg-primary/20 blur-[100px] rounded-full pointer-events-none opacity-50" />
|
||||||
|
|
||||||
|
<EUFundingBadge />
|
||||||
|
|
||||||
<div className="container relative z-10 grid grid-cols-1 md:grid-cols-4 lg:grid-cols-12 gap-4 md:gap-6 lg:gap-8 mb-8 md:mb-12">
|
<div className="container relative z-10 grid grid-cols-1 md:grid-cols-4 lg:grid-cols-12 gap-4 md:gap-6 lg:gap-8 mb-8 md:mb-12">
|
||||||
{/* Brand Column Bento */}
|
{/* Brand Column Bento */}
|
||||||
<div className="col-span-1 md:col-span-4 lg:col-span-5 flex flex-col justify-between bg-white/[0.02] border border-white/5 rounded-3xl p-6 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden">
|
<div className="col-span-1 md:col-span-4 lg:col-span-5 flex flex-col justify-between bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
|
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
|
||||||
|
|
||||||
<div className="space-y-4 md:space-y-6 relative z-10">
|
<div className="space-y-4 md:space-y-6 relative z-10">
|
||||||
@@ -96,7 +99,7 @@ export function Footer({ companyInfo }: FooterProps) {
|
|||||||
<div className="col-span-1 md:col-span-4 lg:col-span-7 grid grid-cols-1 sm:grid-cols-2 gap-4 md:gap-6 lg:gap-8">
|
<div className="col-span-1 md:col-span-4 lg:col-span-7 grid grid-cols-1 sm:grid-cols-2 gap-4 md:gap-6 lg:gap-8">
|
||||||
|
|
||||||
{/* Company Column */}
|
{/* Company Column */}
|
||||||
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-6 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
|
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
|
||||||
<div className="absolute inset-0 bg-gradient-to-bl from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-bl from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" />
|
||||||
<h4 className="font-heading font-bold uppercase tracking-widest text-sm mb-6 md:mb-8 text-white">
|
<h4 className="font-heading font-bold uppercase tracking-widest text-sm mb-6 md:mb-8 text-white">
|
||||||
{locale === 'de' ? 'Unternehmen' : 'Company'}
|
{locale === 'de' ? 'Unternehmen' : 'Company'}
|
||||||
@@ -104,12 +107,13 @@ export function Footer({ companyInfo }: FooterProps) {
|
|||||||
</h4>
|
</h4>
|
||||||
<ul className="space-y-2 md:space-y-4 relative z-10 flex-grow">
|
<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' ? 'Kompetenzen' : 'Competencies', href: `/${locale}/${locale === 'de' ? 'kompetenzen' : 'competencies'}` },
|
||||||
{ label: locale === 'de' ? 'Über uns' : 'About us', href: `/${locale}/ueber-uns` },
|
{ 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' ? 'Unser Team' : 'Our Team', href: `/${locale}/team` },
|
||||||
{ label: locale === 'de' ? 'Zertifikate' : 'Certificates', href: `/${locale}/zertifikate` },
|
{ label: locale === 'de' ? 'Zertifikate' : 'Certificates', href: `/${locale}/zertifikate` },
|
||||||
{ label: locale === 'de' ? 'Karriere' : 'Career', href: `/${locale}/karriere` },
|
{ label: locale === 'de' ? 'Referenzen' : 'References', href: `/${locale}/referenzen` },
|
||||||
{ 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'}` },
|
{ label: locale === 'de' ? 'Kontakt' : 'Contact', href: `/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}` },
|
||||||
].map((link) => (
|
].map((link) => (
|
||||||
<li key={link.href}>
|
<li key={link.href}>
|
||||||
@@ -123,7 +127,7 @@ export function Footer({ companyInfo }: FooterProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Legal Column */}
|
{/* Legal Column */}
|
||||||
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-6 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
|
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
|
||||||
<div className="absolute inset-0 bg-gradient-to-tr from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-tr from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" />
|
||||||
<h4 className="font-heading font-bold uppercase tracking-widest text-sm mb-6 md:mb-8 text-white">
|
<h4 className="font-heading font-bold uppercase tracking-widest text-sm mb-6 md:mb-8 text-white">
|
||||||
{locale === 'de' ? 'Rechtliches' : 'Legal'}
|
{locale === 'de' ? 'Rechtliches' : 'Legal'}
|
||||||
@@ -131,8 +135,8 @@ export function Footer({ companyInfo }: FooterProps) {
|
|||||||
</h4>
|
</h4>
|
||||||
<ul className="space-y-2 md:space-y-4 relative z-10 flex-grow">
|
<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' ? 'Impressum' : 'Imprint', href: `/${locale}/${locale === 'de' ? 'impressum' : 'imprint'}` },
|
||||||
{ label: locale === 'de' ? 'Datenschutz' : 'Privacy Policy', href: `/${locale}/datenschutz` },
|
{ label: locale === 'de' ? 'Datenschutz' : 'Privacy Policy', href: `/${locale}/${locale === 'de' ? 'datenschutz' : 'privacy-policy'}` },
|
||||||
{ label: locale === 'de' ? 'AGB' : 'Terms', href: `/${locale}/${locale === 'de' ? 'agb' : 'terms'}` },
|
{ label: locale === 'de' ? 'AGB' : 'Terms', href: `/${locale}/${locale === 'de' ? 'agb' : 'terms'}` },
|
||||||
].map((link) => (
|
].map((link) => (
|
||||||
<li key={link.href}>
|
<li key={link.href}>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface NavLink {
|
|||||||
label: string;
|
label: string;
|
||||||
url: string;
|
url: string;
|
||||||
children?: NavLink[];
|
children?: NavLink[];
|
||||||
|
isGroupLabel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
@@ -71,7 +72,7 @@ export function Header({ navLinks }: HeaderProps) {
|
|||||||
className={`fixed top-0 left-0 right-0 z-50 flex justify-center transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] ${
|
className={`fixed top-0 left-0 right-0 z-50 flex justify-center transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] ${
|
||||||
isStandard
|
isStandard
|
||||||
? 'bg-[#050B14]/95 backdrop-blur-xl border-b border-white/10'
|
? 'bg-[#050B14]/95 backdrop-blur-xl border-b border-white/10'
|
||||||
: (isSolidMode ? 'pt-4 px-4' : 'pt-0 px-0')
|
: (isSolidMode ? 'pt-4 px-4 sm:px-6' : 'pt-0 px-0')
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -79,7 +80,7 @@ export function Header({ navLinks }: HeaderProps) {
|
|||||||
isStandard
|
isStandard
|
||||||
? 'py-4 px-6 md:px-8'
|
? 'py-4 px-6 md:px-8'
|
||||||
: isSolidMode
|
: isSolidMode
|
||||||
? 'rounded-full bg-white/50 backdrop-blur-xl shadow-[0_8px_32px_rgba(0,0,0,0.08)] border border-white/40 py-2.5 px-6 md:px-8'
|
? 'rounded-full bg-white/50 backdrop-blur-xl shadow-[0_8px_32px_rgba(0,0,0,0.08)] border border-white/40 py-2.5 px-8 md:px-10'
|
||||||
: 'rounded-none bg-transparent py-6 px-4 md:px-8 border border-transparent'
|
: 'rounded-none bg-transparent py-6 px-4 md:px-8 border border-transparent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -164,6 +165,20 @@ export function Header({ navLinks }: HeaderProps) {
|
|||||||
function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink, currentLocale: string, pathname: string, isSolidMode: boolean }) {
|
function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink, currentLocale: string, pathname: string, isSolidMode: boolean }) {
|
||||||
const [isHovered, setIsHovered] = React.useState(false);
|
const [isHovered, setIsHovered] = React.useState(false);
|
||||||
|
|
||||||
|
// Close dropdown on navigation
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsHovered(false);
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
// Close dropdown on scroll
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (isHovered) setIsHovered(false);
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, [isHovered]);
|
||||||
|
|
||||||
const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/)
|
const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/)
|
||||||
? `/${currentLocale}${link.url}`
|
? `/${currentLocale}${link.url}`
|
||||||
: link.url;
|
: link.url;
|
||||||
@@ -218,15 +233,25 @@ function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink
|
|||||||
{/* Top glowing accent line */}
|
{/* Top glowing accent line */}
|
||||||
<div className="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70" />
|
<div className="absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70" />
|
||||||
|
|
||||||
{link.children.map((child) => {
|
{link.children.map((child, idx) => {
|
||||||
const childUrl = child.url.startsWith('/') && !child.url.match(/^\/(en|de)/)
|
const childUrl = child.url.startsWith('/') && !child.url.match(/^\/(en|de)/)
|
||||||
? `/${currentLocale}${child.url}`
|
? `/${currentLocale}${child.url}`
|
||||||
: child.url;
|
: child.url;
|
||||||
const isChildActive = pathname === childUrl;
|
const isChildActive = pathname === childUrl;
|
||||||
|
|
||||||
|
if (child.isGroupLabel) {
|
||||||
|
return (
|
||||||
|
<div key={`group-${idx}`} className={`mt-2 mb-1 px-4 text-[11px] font-bold uppercase tracking-wider ${isSolidMode ? 'text-neutral-400' : 'text-neutral-500'}`}>
|
||||||
|
{child.label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TransitionLink
|
<TransitionLink
|
||||||
key={child.url}
|
key={child.url}
|
||||||
href={childUrl}
|
href={childUrl}
|
||||||
|
onClick={() => setIsHovered(false)}
|
||||||
className={`group/dropitem relative block px-4 py-3.5 rounded-2xl text-sm font-medium transition-all duration-300 overflow-hidden ${
|
className={`group/dropitem relative block px-4 py-3.5 rounded-2xl text-sm font-medium transition-all duration-300 overflow-hidden ${
|
||||||
isSolidMode
|
isSolidMode
|
||||||
? isChildActive ? 'bg-primary/10 text-primary' : 'text-neutral-600 hover:text-primary hover:bg-neutral-50/80'
|
? isChildActive ? 'bg-primary/10 text-primary' : 'text-neutral-600 hover:text-primary hover:bg-neutral-50/80'
|
||||||
|
|||||||
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 ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
const AIOrb = dynamic(() => import('./AIOrb'), { ssr: false });
|
const AIOrb = dynamic(() => import('./AIOrb'), { ssr: false });
|
||||||
|
|
||||||
const LOADING_TEXTS = [
|
|
||||||
'Durchsuche das Kabelhandbuch... 📖',
|
|
||||||
'Frage den Senior-Ingenieur... 👴🔧',
|
|
||||||
'Frage ChatGPTs Cousin 2. Grades... 🤖',
|
|
||||||
];
|
|
||||||
|
|
||||||
interface ProductMatch {
|
interface ProductMatch {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -51,7 +46,13 @@ export function AISearchResults({
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
|
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
|
||||||
const [copiedAll, setCopiedAll] = useState(false);
|
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 inputRef = useRef<HTMLInputElement>(null);
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -141,11 +142,11 @@ export function AISearchResults({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
// Start rotating loading texts
|
// Start rotating loading texts
|
||||||
let textIdx = Math.floor(Math.random() * LOADING_TEXTS.length);
|
let textIdx = Math.floor(Math.random() * loadingTexts.length);
|
||||||
setLoadingText(LOADING_TEXTS[textIdx]);
|
setLoadingText(loadingTexts[textIdx]);
|
||||||
loadingIntervalRef.current = setInterval(() => {
|
loadingIntervalRef.current = setInterval(() => {
|
||||||
textIdx = (textIdx + 1) % LOADING_TEXTS.length;
|
textIdx = (textIdx + 1) % loadingTexts.length;
|
||||||
setLoadingText(LOADING_TEXTS[textIdx]);
|
setLoadingText(loadingTexts[textIdx]);
|
||||||
}, 2500);
|
}, 2500);
|
||||||
}, 400);
|
}, 400);
|
||||||
|
|
||||||
@@ -191,8 +192,8 @@ export function AISearchResults({
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
const msg =
|
const msg =
|
||||||
err.name === 'AbortError'
|
err.name === 'AbortError'
|
||||||
? 'Anfrage hat zu lange gedauert. Bitte versuche es erneut.'
|
? t('timeoutError')
|
||||||
: err.message || 'Ein Fehler ist aufgetreten.';
|
: err.message || t('genericError');
|
||||||
|
|
||||||
// Show error as a system message in the chat instead of a separate error banner
|
// Show error as a system message in the chat instead of a separate error banner
|
||||||
setMessages((prev) => [
|
setMessages((prev) => [
|
||||||
@@ -248,7 +249,7 @@ export function AISearchResults({
|
|||||||
|
|
||||||
const handleCopyChat = () => {
|
const handleCopyChat = () => {
|
||||||
const fullChat = messages
|
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');
|
.join('\n\n');
|
||||||
handleCopy(fullChat);
|
handleCopy(fullChat);
|
||||||
};
|
};
|
||||||
@@ -294,7 +295,7 @@ export function AISearchResults({
|
|||||||
<div>
|
<div>
|
||||||
<h2 className="text-white font-bold text-sm tracking-wide">Ohm</h2>
|
<h2 className="text-white font-bold text-sm tracking-wide">Ohm</h2>
|
||||||
<p className="text-[10px] text-white/30 font-medium tracking-wider uppercase">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -303,14 +304,14 @@ export function AISearchResults({
|
|||||||
<button
|
<button
|
||||||
onClick={handleCopyChat}
|
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"
|
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 ? (
|
{copiedAll ? (
|
||||||
<Check className="w-3.5 h-3.5 text-accent" />
|
<Check className="w-3.5 h-3.5 text-accent" />
|
||||||
) : (
|
) : (
|
||||||
<Copy className="w-3.5 h-3.5" />
|
<Copy className="w-3.5 h-3.5" />
|
||||||
)}
|
)}
|
||||||
<span>{copiedAll ? 'Kopiert' : 'Chat kopieren'}</span>
|
<span>{copiedAll ? t('copied') : t('copyChat')}</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
@@ -336,16 +337,15 @@ export function AISearchResults({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xl md:text-2xl font-bold text-white/80">
|
<p className="text-xl md:text-2xl font-bold text-white/80">
|
||||||
Wie kann ich helfen?
|
{t('howCanIHelp')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-white/30 mt-2 max-w-md">
|
<p className="text-sm text-white/30 mt-2 max-w-md">
|
||||||
Beschreibe dein Projekt, frag nach bestimmten Kabeln, oder nenne mir deine
|
{t('helpDescription')}
|
||||||
Anforderungen.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Quick prompts */}
|
{/* Quick prompts */}
|
||||||
<div className="flex flex-wrap justify-center gap-2 mt-4">
|
<div className="flex flex-wrap justify-center gap-2 mt-4">
|
||||||
{['Windpark 33kV Verkabelung', 'NYCWY 4x185', 'Erdkabel für Solarpark'].map(
|
{prompts.map(
|
||||||
(prompt) => (
|
(prompt) => (
|
||||||
<button
|
<button
|
||||||
key={prompt}
|
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-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'
|
: '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 ? (
|
{copiedIndex === index ? (
|
||||||
<Check className="w-3.5 h-3.5" />
|
<Check className="w-3.5 h-3.5" />
|
||||||
@@ -431,7 +431,7 @@ export function AISearchResults({
|
|||||||
{msg.role === 'assistant' && msg.products && msg.products.length > 0 && (
|
{msg.role === 'assistant' && msg.products && msg.products.length > 0 && (
|
||||||
<div className="mt-4 space-y-2 border-t border-white/[0.06] pt-4">
|
<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">
|
<h4 className="text-[10px] font-bold tracking-widest uppercase text-white/30 mb-2">
|
||||||
Empfohlene Produkte
|
{t('recommendedProducts')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
{msg.products.map((product, idx) => (
|
{msg.products.map((product, idx) => (
|
||||||
@@ -509,7 +509,7 @@ export function AISearchResults({
|
|||||||
<AIOrb isThinking={false} hasError={true} />
|
<AIOrb isThinking={false} hasError={true} />
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<p className="text-xs text-red-300/60 mt-1">{error}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
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"
|
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" />
|
<RotateCcw className="w-3 h-3" />
|
||||||
Nochmal versuchen
|
{t('tryAgain')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -544,7 +544,7 @@ export function AISearchResults({
|
|||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
onKeyDown={onKeyDown}
|
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"
|
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}
|
disabled={isLoading}
|
||||||
tabIndex={1}
|
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-accent text-primary shadow-lg shadow-accent/20 hover:shadow-accent/40 hover:scale-105 active:scale-95'
|
||||||
: 'bg-white/5 text-white/20'
|
: 'bg-white/5 text-white/20'
|
||||||
}`}
|
}`}
|
||||||
aria-label="Nachricht senden"
|
aria-label={t('send')}
|
||||||
>
|
>
|
||||||
<ArrowUp className="w-4 h-4" strokeWidth={2.5} />
|
<ArrowUp className="w-4 h-4" strokeWidth={2.5} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-3 mt-2.5">
|
<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">
|
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15">
|
||||||
Enter zum Senden · Esc zum Schließen
|
{t('footerShortcuts')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15">
|
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-white/15">
|
||||||
·
|
·
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[9px] uppercase tracking-[0.15em] font-medium text-accent/40 flex items-center gap-1">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
1
contact-page-auth-debug.html
Normal file
1
contact-page-auth-debug.html
Normal file
File diff suppressed because one or more lines are too long
1
contact-page-debug.html
Normal file
1
contact-page-debug.html
Normal file
File diff suppressed because one or more lines are too long
BIN
contact.png
Normal file
BIN
contact.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 288 KiB |
@@ -1,22 +1,59 @@
|
|||||||
---
|
---
|
||||||
title: "Allgemeine Geschäftsbedingungen"
|
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."
|
excerpt: "Die Allgemeinen Geschäftsbedingungen der E-TIB GmbH für unsere Leistungen und Lieferungen."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Allgemeine Geschäftsbedingungen (AGB)
|
# Allgemeine Geschäftsbedingungen (AGB)
|
||||||
|
|
||||||
## 1. Geltungsbereich
|
## 1. Geltungsbereich und Vertragsabschluss
|
||||||
Diese Allgemeinen Geschäftsbedingungen gelten für alle Verträge, Lieferungen und sonstigen Leistungen der E-TIB GmbH gegenüber ihren Kunden.
|
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
|
## 2. Leistungsumfang und Mitwirkungspflichten des Kunden
|
||||||
Ein Vertrag kommt erst durch unsere schriftliche Auftragsbestätigung oder durch die Ausführung des Auftrags zustande.
|
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
|
## 3. Ausführungsfristen, Behinderung und Bauverzögerungen
|
||||||
Alle Preise verstehen sich in Euro zzgl. der gesetzlichen Mehrwertsteuer. Zahlungen sind innerhalb von 14 Tagen nach Rechnungsstellung ohne Abzug zu leisten.
|
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
|
## 4. Abnahme, Gefahrübergang und Gewährleistung
|
||||||
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.
|
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
|
## 5. Preise, Abschlagszahlungen und Zahlungsbedingungen
|
||||||
Gerichtsstand für alle Streitigkeiten aus dem Vertragsverhältnis ist Cottbus, sofern der Kunde Kaufmann ist.
|
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.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "Grabenlose Leitungsverlegung"
|
title: "Bohrtechnik"
|
||||||
date: "2024-03-20"
|
date: "2024-03-20"
|
||||||
excerpt: "Präzise Horizontalspülbohrungen und Erdraketen in allen Bodenklassen für eine absolut oberflächenschonende und effiziente Leitungsverlegung."
|
excerpt: "Präzise Horizontalspülbohrungen und Erdraketen in allen Bodenklassen für eine absolut oberflächenschonende und effiziente Leitungsverlegung."
|
||||||
layout: "fullBleed"
|
layout: "fullBleed"
|
||||||
@@ -7,9 +7,9 @@ layout: "fullBleed"
|
|||||||
|
|
||||||
<HeroSection
|
<HeroSection
|
||||||
badge="Kernkompetenz"
|
badge="Kernkompetenz"
|
||||||
title="Grabenlose Verlegung"
|
title="Bohrtechnik"
|
||||||
subtitle="Oberflächenschonende Horizontalspülbohrungen und Erdraketen"
|
subtitle="Grabenlose Leitungsverlegung, Horizontalspülbohrungen und Erdraketen"
|
||||||
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
|
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-47.jpg' }}
|
||||||
alignment="center"
|
alignment="center"
|
||||||
ctaLabel="Projekt anfragen"
|
ctaLabel="Projekt anfragen"
|
||||||
ctaHref="/de/kontakt"
|
ctaHref="/de/kontakt"
|
||||||
@@ -26,7 +26,7 @@ layout: "fullBleed"
|
|||||||
|
|
||||||
## HDD-Spülbohrungen und Erdraketen
|
## HDD-Spülbohrungen und Erdraketen
|
||||||
|
|
||||||
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.
|
Mit unserem spezialisierten Gerätepark beherrschen wir die unterschiedlichsten Verfahren. Beim **Horizontalspülbohrverfahren (HDD)** steuern wir den Bohrkopf punktgenau unter der Erde hindurch. Wir können bis zu 400 Meter am Stück bohren und, je nach Bohrlänge, Schutzrohre mit einem Durchmesser von bis zu 600 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.
|
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.
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ layout: "fullBleed"
|
|||||||
fullWidth: false,
|
fullWidth: false,
|
||||||
lists: [
|
lists: [
|
||||||
[
|
[
|
||||||
"Max. Bohrlänge: 250 m",
|
"Max. Bohrlänge: 400 m",
|
||||||
"Max. Durchmesser: 400er Rohr",
|
"Max. Durchmesser: 600er Rohr",
|
||||||
"Zentimetergenaue Steuerbarkeit",
|
"Zentimetergenaue Steuerbarkeit",
|
||||||
"Querung von Bahn, Autobahn & Gewässern"
|
"Querung von Bahn, Autobahn & Gewässern"
|
||||||
]
|
]
|
||||||
@@ -71,7 +71,7 @@ layout: "fullBleed"
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HomeReferencesSlider badge="Praxisbeispiele" title="Abgeschlossene Bohr-Projekte" description="Erfolgreiche HDD-Bohrungen und grabenlose Leitungsverlegungen unter komplexesten Bedingungen." />
|
<HomeReferencesSlider badge="Praxisbeispiele" title="Referenzen" description="Erfolgreiche HDD-Bohrungen und grabenlose Leitungsverlegungen unter komplexesten Bedingungen." />
|
||||||
|
|
||||||
<CallToAction
|
<CallToAction
|
||||||
title="Suchen Sie nach einer grabenlosen Lösung?"
|
title="Suchen Sie nach einer grabenlosen Lösung?"
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ layout: "fullBleed"
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HomeReferencesSlider badge="Praxisbeispiele" title="Abgeschlossene Breitband-Projekte" description="Wir haben bereits hunderte Kilometer FTTX-Netze erfolgreich realisiert. Entdecken Sie unsere Referenzen." />
|
<HomeReferencesSlider badge="Praxisbeispiele" title="Referenzen" description="Wir haben bereits hunderte Kilometer FTTX-Netze erfolgreich realisiert. Entdecken Sie unsere Referenzen." />
|
||||||
|
|
||||||
<CallToAction
|
<CallToAction
|
||||||
title="Sie planen einen FTTX-Ausbau?"
|
title="Sie planen einen FTTX-Ausbau?"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
|||||||
|
|
||||||
<HomeHero
|
<HomeHero
|
||||||
|
|
||||||
title={"Die Experten für\nKabeltiefbau"}
|
title={"Die Experten für\nKabelnetzbau"}
|
||||||
description="Wir realisieren komplexe Versorgungsleitungen für eine moderne Gesellschaft. Verlässlich, innovativ und mit höchster Präzision im Kabeltiefbau und der Bohrtechnik."
|
description="Wir realisieren komplexe Versorgungsleitungen für eine moderne Gesellschaft. Verlässlich, innovativ und mit höchster Präzision im Kabeltiefbau und der Bohrtechnik."
|
||||||
videoUrl="/assets/videos/web/hero-kabelpflug.mp4"
|
videoUrl="/assets/videos/web/hero-kabelpflug.mp4"
|
||||||
linkText="Unsere Leistungen"
|
linkText="Unsere Leistungen"
|
||||||
@@ -18,27 +18,30 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
|||||||
companies={[
|
companies={[
|
||||||
{
|
{
|
||||||
title: "E-TIB GMBH",
|
title: "E-TIB GMBH",
|
||||||
description: "Das Unternehmen hat sich voll und ganz auf den Kabelleitungstiefbau spezialisiert. Das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand.",
|
description: "Das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand.",
|
||||||
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
|
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
|
||||||
backgroundImage: "/assets/photos/DJI_0243.JPG"
|
url: "/de/standorte/guben",
|
||||||
|
backgroundImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-54.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Ingenieurgesellschaft mbH",
|
title: "E-TIB Ingenieurgesellschaft mbH",
|
||||||
description: "Unsere Ingenieure planen für Sie die Energie- und Telekommunikationsnetze der Zukunft - Vermessung und Dokumentation inklusive.",
|
description: "Unsere Ingenieure planen für Sie die Energie- und Telekommunikationsnetze der Zukunft - Vermessung und Dokumentation inklusive.",
|
||||||
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
||||||
url: "https://www.etib-ing.com/",
|
url: "https://www.etib-ing.com/",
|
||||||
backgroundImage: "/assets/photos/DSC01137.JPG"
|
backgroundImage: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Verwaltung GmbH",
|
title: "E-TIB Verwaltung GmbH",
|
||||||
description: "Zentrale Dienste, Einkauf und Finanzen sowie Verwaltung von Immobilien, Grundstücken, Maschinen und Geräten.",
|
description: "Zentrale Dienste, Einkauf und Finanzen sowie Verwaltung von Immobilien, Grundstücken, Maschinen und Geräten.",
|
||||||
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
||||||
|
url: "/de/standorte/buelstedt",
|
||||||
backgroundImage: "/assets/photos/DJI_0048.JPG"
|
backgroundImage: "/assets/photos/DJI_0048.JPG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Bohrtechnik GmbH",
|
title: "E-TIB Bohrtechnik GmbH",
|
||||||
description: "Präzise Horizontalspülbohrungen in allen Bodenklassen für eine oberflächenschonende und grabenlose Leitungsverlegung.",
|
description: "Präzise Horizontalspülbohrungen in allen Bodenklassen für eine oberflächenschonende und grabenlose Leitungsverlegung.",
|
||||||
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
||||||
|
url: "/de/bohrtechnik",
|
||||||
backgroundImage: "/assets/photos/DSC08653.JPG"
|
backgroundImage: "/assets/photos/DSC08653.JPG"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
@@ -59,21 +62,25 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
|||||||
subtitle="Über E-TIB"
|
subtitle="Über E-TIB"
|
||||||
badgeStyle="industrial"
|
badgeStyle="industrial"
|
||||||
className="mb-6 md:mb-8"
|
className="mb-6 md:mb-8"
|
||||||
>Ihr <span className="text-primary relative whitespace-nowrap">starker<svg className="absolute -bottom-2 left-0 w-full h-3 text-primary/20" viewBox="0 0 100 10" preserveAspectRatio="none"><path d="M0 5 Q 50 10 100 5" stroke="currentColor" strokeWidth="4" fill="none"/></svg></span> Partner <br/>für Infrastruktur</Heading>
|
>Ihr <span className="text-primary relative whitespace-nowrap">starker<svg className="absolute -bottom-2 left-0 w-full h-3 text-warning/60" viewBox="0 0 100 10" preserveAspectRatio="none"><path d="M0 5 Q 50 10 100 5" stroke="currentColor" strokeWidth="4" fill="none"/></svg></span> Partner <br/>für Infrastruktur</Heading>
|
||||||
|
|
||||||
<div className="text-xl text-text-secondary leading-relaxed mb-8">
|
<div className="text-xl text-text-secondary leading-relaxed mb-8">
|
||||||
Die E-TIB GmbH ist ein leistungsfähiger und moderner Anbieter von technischen Dienstleistungen. Wir haben uns auf zukunftsweisende Kernbereiche der Infrastruktur spezialisiert.
|
Die E-TIB GmbH ist ein leistungsfähiger und moderner Anbieter von technischen Dienstleistungen. Wir haben uns auf zukunftsweisende Kernbereiche der Infrastruktur spezialisiert.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="grid grid-cols-2 gap-6 pt-8 border-t border-neutral-200">
|
<div className="grid grid-cols-3 gap-6 pt-8 border-t border-neutral-200">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">2015</div>
|
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">2015</div>
|
||||||
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Gründung</div>
|
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Gründung</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">50+</div>
|
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">100+</div>
|
||||||
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Mitarbeiter</div>
|
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Mitarbeitende</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">100+</div>
|
||||||
|
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Kunden</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,12 +117,12 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
|||||||
ctaHref="/de/kompetenzen"
|
ctaHref="/de/kompetenzen"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: "Kabeltiefbau",
|
title: "Kabelleitungstiefbau",
|
||||||
description: "Verlegung von Mittel- und Niederspannungskabeln",
|
description: "Kabelleitungstiefbau (Hoch-/Mittel- und Niederspannung) sowie Kabelmontagen bis 110 kV",
|
||||||
tag: "Energie",
|
tag: "Energie",
|
||||||
size: "large",
|
size: "large",
|
||||||
href: "/de/kabeltiefbau",
|
href: "/de/kabeltiefbau",
|
||||||
image: { url: "/assets/photos/DSC01123.JPG", alt: "Kabeltiefbau" }
|
image: { url: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-54.jpg", alt: "Kabelleitungstiefbau" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Bohrtechnik",
|
title: "Bohrtechnik",
|
||||||
@@ -123,7 +130,7 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
|||||||
tag: "Innovation",
|
tag: "Innovation",
|
||||||
size: "medium",
|
size: "medium",
|
||||||
href: "/de/bohrtechnik",
|
href: "/de/bohrtechnik",
|
||||||
image: { url: "/assets/photos/DSC08653.JPG", alt: "Bohrtechnik" }
|
image: { url: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-47.jpg", alt: "Bohrtechnik" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Planung",
|
title: "Planung",
|
||||||
@@ -131,14 +138,15 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
|||||||
tag: "Präzision",
|
tag: "Präzision",
|
||||||
size: "small",
|
size: "small",
|
||||||
href: "/de/planung",
|
href: "/de/planung",
|
||||||
image: { url: "/assets/photos/DJI_0048.JPG", alt: "Planung" }
|
image: { url: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg", alt: "Planung" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Vermessung",
|
title: "Vermessung",
|
||||||
description: "Digitale Bestandsdokumentation",
|
description: "Digitale Bestandsdokumentation",
|
||||||
tag: "Digital",
|
tag: "Digital",
|
||||||
size: "accent",
|
size: "accent",
|
||||||
href: "/de/vermessung"
|
href: "/de/vermessung",
|
||||||
|
image: { url: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(26_von_99).jpg", alt: "Vermessung" }
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: "Kabelleitungstiefbau"
|
title: "Kabelleitungstiefbau"
|
||||||
date: "2024-03-20"
|
date: "2024-03-20"
|
||||||
excerpt: "Professioneller Kabeltiefbau für Mittel- und Niederspannungsnetze. Wir garantieren höchste Sicherheitsstandards und lückenlose Dokumentation für Ihr Projekt."
|
excerpt: "Professioneller Kabelleitungstiefbau (Hoch-, Mittel- und Niederspannung) und Kabelmontagen bis 110 kV. Wir garantieren höchste Sicherheitsstandards und lückenlose Dokumentation für Ihr Projekt."
|
||||||
layout: "fullBleed"
|
layout: "fullBleed"
|
||||||
---
|
---
|
||||||
|
|
||||||
<HeroSection
|
<HeroSection
|
||||||
badge="Kernkompetenz"
|
badge="Kernkompetenz"
|
||||||
title="Kabelleitungstiefbau"
|
title="Kabelleitungstiefbau"
|
||||||
subtitle="Komplette Infrastruktur-Lösungen für Mittel- und Niederspannungsnetze"
|
subtitle="Komplette Infrastruktur-Lösungen für Hoch-, Mittel- und Niederspannungsnetze sowie Kabelmontagen bis 110 kV"
|
||||||
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
|
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
|
||||||
alignment="center"
|
alignment="center"
|
||||||
ctaLabel="Projekt anfragen"
|
ctaLabel="Projekt anfragen"
|
||||||
@@ -24,13 +24,11 @@ layout: "fullBleed"
|
|||||||
|
|
||||||
Auf vielen Baustellen werden zunehmend Billiganbieter aus dem Ausland eingesetzt, was häufig dazu führt, dass **Sicherheitsstandards und Dokumentationsanforderungen missachtet** werden. Die Folgen sind gravierend: Unfälle, defekte Fremdleitungen (wie Strom, Wasser und Gas) sowie massive Bauverzögerungen und teure Nachträge. Ein vermeintliches Schnäppchen entpuppt sich so schnell als finanzielle Belastung.
|
Auf vielen Baustellen werden zunehmend Billiganbieter aus dem Ausland eingesetzt, was häufig dazu führt, dass **Sicherheitsstandards und Dokumentationsanforderungen missachtet** werden. Die Folgen sind gravierend: Unfälle, defekte Fremdleitungen (wie Strom, Wasser und Gas) sowie massive Bauverzögerungen und teure Nachträge. Ein vermeintliches Schnäppchen entpuppt sich so schnell als finanzielle Belastung.
|
||||||
|
|
||||||
Besonders bei geförderten Infrastruktur-Projekten gestaltet sich der Abruf der Fördergelder ohne eine absolut lückenlose, normgerechte Dokumentation äußerst problematisch.
|
|
||||||
|
|
||||||
## Alles aus einer Hand
|
## Alles aus einer Hand
|
||||||
|
|
||||||
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.
|
Die E-TIB GmbH hat sich voll und ganz auf den Kabelnetzbau spezialisiert. Durch unsere langjährige Erfahrung erkennen wir Problemstellungen frühzeitig und erarbeiten für unsere Auftraggeber im Vorfeld sichere und zufriedenstellende Lösungen.
|
||||||
|
|
||||||
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.
|
Wir bieten Ihnen das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand – mit eigenem, hochmodernem Maschinenpark und festangestelltem Fachpersonal. 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 />
|
<DataGridPulse />
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +37,7 @@ layout: "fullBleed"
|
|||||||
badge="Leistungsspektrum"
|
badge="Leistungsspektrum"
|
||||||
title="Alles für Ihr Netz"
|
title="Alles für Ihr Netz"
|
||||||
descriptionParagraphs={[
|
descriptionParagraphs={[
|
||||||
"Wir bieten höchste Präzision, modernste Maschinen und zertifizierte Experten für jedes Tiefbau-Projekt – von der Kabelverlegung bis zur Transformatorstation."
|
"Wir bieten höchste Präzision, modernste Maschinen und zertifizierte Experten für Ihr Projekt."
|
||||||
]}
|
]}
|
||||||
panels={[
|
panels={[
|
||||||
{
|
{
|
||||||
@@ -51,7 +49,7 @@ layout: "fullBleed"
|
|||||||
[
|
[
|
||||||
"Herstellung von Kabel- und Rohrgräben",
|
"Herstellung von Kabel- und Rohrgräben",
|
||||||
"Kabelverlegung von 0,4 kV bis 110 kV",
|
"Kabelverlegung von 0,4 kV bis 110 kV",
|
||||||
"Kabelmontagen von 0,4 kV bis 30 kV",
|
"Kabelmontagen bis 110 kV",
|
||||||
"Kabelpflugarbeiten für schnelle Verlegung"
|
"Kabelpflugarbeiten für schnelle Verlegung"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -73,7 +71,7 @@ layout: "fullBleed"
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HomeReferencesSlider badge="Praxisbeispiele" title="Abgeschlossene Tiefbau-Projekte" description="Wir haben über hunderte Kilometer Kabel verlegt. Sehen Sie sich unsere Referenz-Projekte an." />
|
<HomeReferencesSlider badge="Praxisbeispiele" title="Referenzen" description="Wir haben über hunderte Kilometer Kabel verlegt. Sehen Sie sich unsere Referenz-Projekte an." />
|
||||||
|
|
||||||
<CallToAction
|
<CallToAction
|
||||||
title="Sie haben ein konkretes Projekt?"
|
title="Sie haben ein konkretes Projekt?"
|
||||||
|
|||||||
@@ -5,21 +5,22 @@ excerpt: "Werden Sie Teil eines dynamischen Teams und bauen Sie mit uns die Infr
|
|||||||
layout: "fullBleed"
|
layout: "fullBleed"
|
||||||
---
|
---
|
||||||
|
|
||||||
<HomeHero
|
<HeroSection
|
||||||
badge="Karriere"
|
badge="Karriere"
|
||||||
title="Zukunft bauen"
|
title="Zukunft bauen"
|
||||||
description="Suchen Sie eine neue Herausforderung in einem zukunftssicheren Markt? Bei E-TIB arbeiten wir täglich daran, Deutschland zu vernetzen und die Energiewende voranzutreiben."
|
subtitle="Suchen Sie eine neue Herausforderung in einem zukunftssicheren Markt? Bei E-TIB arbeiten wir täglich daran, Deutschland zu vernetzen und die Energiewende voranzutreiben."
|
||||||
videoUrl="/assets/videos/web/hero-kabelpflug.mp4"
|
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-28.jpg' }}
|
||||||
linkText="Offene Stellen"
|
alignment="center"
|
||||||
linkHref="#stellen"
|
ctaLabel="Offene Stellen"
|
||||||
|
ctaHref="#stellen"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<JobListingBlock showFairs={false} showJobs={true} />
|
<JobListingBlock showFairs={false} showJobs={true} />
|
||||||
|
|
||||||
<BenefitGrid
|
<BenefitGrid
|
||||||
badge="E-TIB Standard"
|
badge="E-TIB Standard"
|
||||||
title="Wir bieten unseren Mitarbeitern"
|
title="Wir bieten unseren Mitarbeitenden"
|
||||||
description="Die E-TIB GmbH wurde aus der Motivation gegründet, hochwertige Leistungen sowie faire Entlohnung zu ermöglichen. Neben der Entlohnung steht die Wertschätzung jedes einzelnen Mitarbeiters bei uns an oberster Stelle."
|
description="Die E-TIB GmbH wurde aus der Motivation gegründet, hochwertige Leistungen sowie faire Entlohnung zu ermöglichen. Neben der Entlohnung steht die Wertschätzung jedes einzelnen Mitarbeitenden bei uns an oberster Stelle."
|
||||||
benefits={[
|
benefits={[
|
||||||
{
|
{
|
||||||
id: "region",
|
id: "region",
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ layout: "fullBleed"
|
|||||||
ctaHref="/de/kontakt"
|
ctaHref="/de/kontakt"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: "Kabeltiefbau",
|
title: "Kabelleitungstiefbau",
|
||||||
description: "Klassischer Grabenbau und professionelle Verlegung von Mittel- und Niederspannungskabeln.",
|
description: "Klassischer Grabenbau, professionelle Verlegung und Kabelmontage (Hoch-, Mittel- und Niederspannung) bis 110 kV.",
|
||||||
tag: "Energie",
|
tag: "Energie",
|
||||||
size: "large",
|
size: "large",
|
||||||
href: "/de/kabeltiefbau",
|
href: "/de/kabeltiefbau",
|
||||||
image: { url: "/assets/photos/DSC01123.JPG", alt: "Kabeltiefbau" }
|
image: { url: "/assets/photos/DSC01123.JPG", alt: "Kabelleitungstiefbau" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Bohrtechnik",
|
title: "Bohrtechnik",
|
||||||
@@ -70,13 +70,13 @@ layout: "fullBleed"
|
|||||||
panels={[
|
panels={[
|
||||||
{
|
{
|
||||||
id: "energy",
|
id: "energy",
|
||||||
title: "Kabelleitungstiefbau (Mittel-/Niederspannung)",
|
title: "Kabelleitungstiefbau (Hoch-/Mittel- und Niederspannung)",
|
||||||
icon: "energy",
|
icon: "energy",
|
||||||
lists: [
|
lists: [
|
||||||
[
|
[
|
||||||
"Herstellung von Kabel- und Rohrgräben",
|
"Herstellung von Kabel- und Rohrgräben",
|
||||||
"Kabelverlegung (0,4kV - 110kV)",
|
"Kabelverlegung (0,4kV - 110kV)",
|
||||||
"Kabelmontagen 0,4 kV - 30 kV",
|
"Kabelmontagen bis 110 kV",
|
||||||
"Errichtung von Transformatorstationen",
|
"Errichtung von Transformatorstationen",
|
||||||
"Erstellung von Erdungsanlagen",
|
"Erstellung von Erdungsanlagen",
|
||||||
"Errichtung von Straßenbeleuchtung",
|
"Errichtung von Straßenbeleuchtung",
|
||||||
@@ -143,8 +143,8 @@ layout: "fullBleed"
|
|||||||
<JsonLd data={[
|
<JsonLd data={[
|
||||||
{
|
{
|
||||||
"@type": "Service",
|
"@type": "Service",
|
||||||
"name": "Kabeltiefbau",
|
"name": "Kabelleitungstiefbau",
|
||||||
"description": "Klassischer Grabenbau und professionelle Verlegung von Mittel- und Niederspannungskabeln.",
|
"description": "Klassischer Grabenbau, professionelle Verlegung und Kabelmontage (Hoch-, Mittel- und Niederspannung) bis 110 kV.",
|
||||||
"provider": { "@type": "Organization", "name": "E-TIB GmbH" }
|
"provider": { "@type": "Organization", "name": "E-TIB GmbH" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ excerpt: "Treffen Sie das Team der E-TIB Gruppe persönlich auf Fachmessen und B
|
|||||||
layout: "fullBleed"
|
layout: "fullBleed"
|
||||||
---
|
---
|
||||||
|
|
||||||
<HomeHero
|
<HeroSection
|
||||||
badge="Messen & Events"
|
badge="Messen & Events"
|
||||||
title="Treffen Sie uns persönlich"
|
title="Treffen Sie uns persönlich"
|
||||||
description="Lernen Sie die E-TIB Gruppe auf den führenden Branchenmessen kennen."
|
subtitle="Lernen Sie die E-TIB Gruppe auf den führenden Branchenmessen kennen."
|
||||||
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
|
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-100.jpg' }}
|
||||||
linkText="Kontakt aufnehmen"
|
alignment="center"
|
||||||
linkHref="/de/kontakt"
|
ctaLabel="Kontakt aufnehmen"
|
||||||
|
ctaHref="/de/kontakt"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div id="termine">
|
<div id="termine">
|
||||||
@@ -19,9 +20,9 @@ layout: "fullBleed"
|
|||||||
showFairs={true}
|
showFairs={true}
|
||||||
showJobs={false}
|
showJobs={false}
|
||||||
fairs={[
|
fairs={[
|
||||||
{ name: 'Intersolar München', date: '2024 / 2025', type: 'Messe', location: 'München' },
|
{ name: 'Intersolar München', date: '23. - 25. Juni 2026', type: 'Messe', location: 'München', url: 'https://www.intersolar.de/' },
|
||||||
{ name: 'Windenergietage Linstow', date: '2024 / 2025', type: 'Fachkongress', location: 'Linstow' },
|
{ name: 'Windenergietage Linstow', date: '10. - 12. November 2026', type: 'Fachkongress', location: 'Linstow', url: 'https://www.windenergietage.de/' },
|
||||||
{ name: 'Kabel-Workshop Wiesbaden', date: '2024 / 2025', type: 'Fachmesse', location: 'Wiesbaden' }
|
{ name: 'Werkstatt Kabel 2026', date: '24. - 25. November 2026', type: 'Fachtagung', location: 'Wiesbaden', url: 'https://werkstatt-kabel.essociation.de/' }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ layout: "fullBleed"
|
|||||||
badge="Kernkompetenz"
|
badge="Kernkompetenz"
|
||||||
title="Planung & Beratung"
|
title="Planung & Beratung"
|
||||||
subtitle="Kompetente Begleitung für Energie- und Telekommunikationsnetze"
|
subtitle="Kompetente Begleitung für Energie- und Telekommunikationsnetze"
|
||||||
backgroundImage={{ url: '/assets/photos/DJI_0048.JPG' }}
|
backgroundImage={{ url: '/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg' }}
|
||||||
alignment="center"
|
alignment="center"
|
||||||
ctaLabel="Projekt anfragen"
|
ctaLabel="Projekt anfragen"
|
||||||
ctaHref="/de/kontakt"
|
ctaHref="/de/kontakt"
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Breitbandausbau FTTH, Landkreis Börde"
|
|
||||||
client: "EKN Breitband GmbH & Co. KG"
|
|
||||||
date: "2019-05-01"
|
|
||||||
dateString: "Seit 05/2019"
|
|
||||||
location: "Landkreis Börde"
|
|
||||||
featuredImage: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-294.jpg"
|
|
||||||
---
|
|
||||||
|
|
||||||
- 109.000 m Leitungstrasse herstellen (Tiefbau/HDD)
|
|
||||||
- 63 Stück Aufstellung Netzverteiler
|
|
||||||
- 747 Stück Herstellung Hausanschlüsse
|
|
||||||
- Koordination und Realisierung der gesamten Trassenführung
|
|
||||||
- Dokumentation und Abnahme der Hausanschlüsse
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Breitbandausbau FTTH, Bomlitz / Schwarmstedt"
|
|
||||||
client: "inexio Informationstechnologie und Telekommunikation GmbH"
|
|
||||||
date: "2018-08-01"
|
|
||||||
dateString: "08/2018 bis 03/2021"
|
|
||||||
location: "29699 Bomlitz / 29690 Schwarmstedt"
|
|
||||||
featuredImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-60.jpg"
|
|
||||||
---
|
|
||||||
|
|
||||||
- 88.890 m Leitungstrasse herstellen (Tiefbau/HDD)
|
|
||||||
- 23 Stück Aufstellung Multifunktionsgehäuse
|
|
||||||
- 52 Stück Aufstellung Netzverteiler
|
|
||||||
- 571 Stück Herstellung Hausanschlüsse
|
|
||||||
17
content/de/referenzen/dettmannsdorf.mdx
Normal file
17
content/de/referenzen/dettmannsdorf.mdx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "PV-Anlage Dettmannsdorf"
|
||||||
|
client: "Energiekontor AG"
|
||||||
|
date: "2026-06-01"
|
||||||
|
dateString: "01/2025 bis 06/2026"
|
||||||
|
location: "18334 Dettmannsdorf"
|
||||||
|
featuredImage: "/assets/photos/DSC08653.JPG"
|
||||||
|
---
|
||||||
|
|
||||||
|
- 12.300 m Kabelgraben ausheben und verfüllen
|
||||||
|
- 14.200 m Horizontalspülbohrung d 225 / d 200 / d 180
|
||||||
|
- 26.500 m Verlegung MS-Kabel 630 mm², 18/30 kV
|
||||||
|
- 7.100 m Verlegung MS-Kabel 1000 mm², 18/30 kV
|
||||||
|
- 26.500 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
||||||
|
- 26.500 m Einblasen LWL-Kabel, 12 Fasern
|
||||||
|
- sämtliche Mittelspannungsmontagen
|
||||||
|
- VLF/Mantelprüfungen/OTDR
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Kabelverbindung U30 - T10, Eisenhüttenstadt"
|
|
||||||
client: "ArcelorMittal Eisenhüttenstadt GmbH"
|
|
||||||
date: "2019-07-01"
|
|
||||||
dateString: "07/2019 bis 12/2019"
|
|
||||||
location: "15890 Eisenhüttenstadt"
|
|
||||||
featuredImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(24_von_99)_groß2.jpg"
|
|
||||||
---
|
|
||||||
|
|
||||||
- 2.095 m Kabelgraben ausheben und verfüllen
|
|
||||||
- 180 m Horizontalspülbohrung d 160 + d 50
|
|
||||||
- 5.700 m Lieferung und Verlegung MS-Kabel 240 mm², 18/30 kV
|
|
||||||
- 3.000 m Lieferung und Verlegung PE-Schutzrohr 50 x 4,6 mm
|
|
||||||
- 3.000 m Lieferung und Einblasen LWL-Kabel, 12 Fasern
|
|
||||||
- 3.000 m Lieferung und Verlegung Steuerkabel, 0,6/1 kV
|
|
||||||
- 16 Satz Lieferung und Montage MS-Verbindungsmuffe 240 mm², 18/30 kV
|
|
||||||
- 1 Stück Lieferung und Montage LWL-Verbindungsmuffe, 12 Fasern
|
|
||||||
- 4 Satz Lieferung und Montage MS-T-Stecker 240 mm², 18/30 kV
|
|
||||||
- Hauptprüfung / Mantelprüfung / OTDR-Messung
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Neubau Kabeltrasse, Forst"
|
|
||||||
client: "Mitteldeutsche Netzgesellschaft Strom mbH"
|
|
||||||
date: "2020-03-01"
|
|
||||||
dateString: "03/2020 bis 09/2020"
|
|
||||||
location: "03149 Forst"
|
|
||||||
featuredImage: "/assets/photos/DSC02156.jpg"
|
|
||||||
---
|
|
||||||
|
|
||||||
- 2984 m Kabelgraben ausheben und verfüllen
|
|
||||||
- 945 m Horizontalspülbohrung d 160 + d 50
|
|
||||||
- 3.929 m Verlegung MS-Kabel 240 mm², 12/20 kV
|
|
||||||
- 3.929 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
|
||||||
- 11 Satz Montage MS-Verbindungsmuffe 240 mm², 12/20 kV
|
|
||||||
- 1 Satz Montage MS-Endverschluss 240 mm², 12/20 kV
|
|
||||||
- 2.000 m Rückbau Freileitung (inkl. 16 Stück Maste)
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Einspeisung PV-Anlage, Görne"
|
|
||||||
client: "EVH GmbH"
|
|
||||||
date: "2020-05-01"
|
|
||||||
dateString: "05/2020 bis 07/2020"
|
|
||||||
location: "14728 Görne"
|
|
||||||
featuredImage: "/assets/photos/DSC08744.JPG"
|
|
||||||
---
|
|
||||||
|
|
||||||
- 4.215 m Kabelgraben ausheben und verfüllen
|
|
||||||
- 1.285 m Horizontalspülbohrung d 140 + d 50
|
|
||||||
- 5.500 m Verlegung MS-Kabel 300 mm², 12/20 kV
|
|
||||||
- 4.500 m Verlegung MS-Kabel 300 mm², 12/20 kV mittels Kabelpflug
|
|
||||||
- 10.000 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
|
||||||
- 10.000 m Einblasen LWL-Kabel, 12 Fasern
|
|
||||||
- 3 Stück Lieferung und Montage LWL-Verbindungsmuffe, 12 Fasern
|
|
||||||
- 16 Satz Montage MS-Verbindungsmuffe 300 mm², 12/20 kV
|
|
||||||
- 4 Satz Montage MS-T-Stecker 300 mm², 12/20 kV
|
|
||||||
- Mantelprüfung, VLF-Prüfung, OTDR-Messung
|
|
||||||
17
content/de/referenzen/jevenstedt.mdx
Normal file
17
content/de/referenzen/jevenstedt.mdx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "110 kV Kabeltrasse Jevenstedt OT Altenkattbek"
|
||||||
|
client: "wpd GmbH"
|
||||||
|
date: "2026-03-01"
|
||||||
|
dateString: "seit 03/2026"
|
||||||
|
location: "24808 Jevenstedt"
|
||||||
|
featuredImage: "/assets/photos/DSC01137.JPG"
|
||||||
|
---
|
||||||
|
|
||||||
|
- 500 m Kabelgraben ausheben und verfüllen
|
||||||
|
- 4.500 m Einpflügen von 3 x PE-Schutzrohr d 160
|
||||||
|
- 3.200 m Horizontalspülbohrung d 160
|
||||||
|
- 8.500 m Verlegung HS-Kabel 1.000 mm² 64/110 kV
|
||||||
|
- 11 Satz Verbindungsmuffen 1.000 mm² 64/110 kV
|
||||||
|
- 2 Satz Freiluftendverschlüsse 1.000 mm² 64/110 kV
|
||||||
|
- 8.500 m Einblasen LWL-Kabel, 12 Fasern
|
||||||
|
- Hochspannungsprüfung mit TE-Messung/OTDR
|
||||||
15
content/de/referenzen/ketzin.mdx
Normal file
15
content/de/referenzen/ketzin.mdx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
title: "Netzausbau Gewerbegebiet Ketzin"
|
||||||
|
client: "E.DIS Netz GmbH"
|
||||||
|
date: "2025-06-01"
|
||||||
|
dateString: "01/2025 bis 06/2025"
|
||||||
|
location: "14669 Ketzin"
|
||||||
|
featuredImage: "/assets/photos/DJI_0048.JPG"
|
||||||
|
---
|
||||||
|
|
||||||
|
- 1.600 m Kabelgraben ausheben und verfüllen
|
||||||
|
- 7.500 m Horizontalspülbohrung d 180
|
||||||
|
- 11.200 m Verlegung MS-Kabel 240 mm², 12/20 kV
|
||||||
|
- 11.200 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
||||||
|
- sämtliche Mittelspannungsmontagen
|
||||||
|
- VLF/Mantelprüfungen
|
||||||
17
content/de/referenzen/ostbevern.mdx
Normal file
17
content/de/referenzen/ostbevern.mdx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "Windpark Ostbevern"
|
||||||
|
client: "GP Joule Anlagenbau GmbH & Co. KG"
|
||||||
|
date: "2026-03-01"
|
||||||
|
dateString: "02/2025 bis 03/2026"
|
||||||
|
location: "48346 Ostbevern"
|
||||||
|
featuredImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-100.jpg"
|
||||||
|
---
|
||||||
|
|
||||||
|
- 4.100 m Kabelgraben ausheben und verfüllen
|
||||||
|
- 3.200 m Horizontalspülbohrung d 180 + d 50
|
||||||
|
- 3.200 m Verlegung MS-Kabel 150 mm², 18/30 kV
|
||||||
|
- 4.000 m Verlegung MS-Kabel 300 mm², 18/30 kV
|
||||||
|
- 7.500 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
||||||
|
- 7.500 m Einblasen LWL-Kabel, 12 Fasern
|
||||||
|
- sämtliche Mittelspannungsmontagen
|
||||||
|
- VLF/Mantelprüfungen/OTDR
|
||||||
17
content/de/referenzen/petershagen.mdx
Normal file
17
content/de/referenzen/petershagen.mdx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "PV-Anlage Petershagen"
|
||||||
|
client: "NaturStromAnlagen GmbH"
|
||||||
|
date: "2025-02-01"
|
||||||
|
dateString: "04/2024 bis 02/2025"
|
||||||
|
location: "15326 Petershagen"
|
||||||
|
featuredImage: "/assets/photos/DSC01123.JPG"
|
||||||
|
---
|
||||||
|
|
||||||
|
- 12.000 m Kabelgraben ausheben und verfüllen
|
||||||
|
- 3.000 m Horizontalspülbohrung d 225 / d 200 / d 160
|
||||||
|
- 10.000 m Verlegung MS-Kabel 630 mm², 18/30 kV
|
||||||
|
- 17.000 m Verlegung MS-Kabel 800 mm², 18/30 kV
|
||||||
|
- 10.000 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
||||||
|
- 10.000 m Einblasen LWL-Kabel, 12 Fasern
|
||||||
|
- sämtliche Mittelspannungsmontagen
|
||||||
|
- VLF/Mantelprüfungen/OTDR
|
||||||
17
content/de/referenzen/proetzel.mdx
Normal file
17
content/de/referenzen/proetzel.mdx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "PV-Anlage Prötzel OT Sternebeck"
|
||||||
|
client: "Trianel GmbH"
|
||||||
|
date: "2025-08-01"
|
||||||
|
dateString: "02/2025 bis 08/2025"
|
||||||
|
location: "15345 Prötzel"
|
||||||
|
featuredImage: "/assets/photos/DJI_0243.JPG"
|
||||||
|
---
|
||||||
|
|
||||||
|
- 2.600 m Kabelgraben ausheben und verfüllen
|
||||||
|
- 3.800 m Horizontalspülbohrung d 225 / d 200 / d 160
|
||||||
|
- 7.500 m Verlegung MS-Kabel 240 mm², 18/30 kV
|
||||||
|
- 8.800 m Verlegung MS-Kabel 800 mm², 18/30 kV
|
||||||
|
- 15.800 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
||||||
|
- 15.800 m Einblasen LWL-Kabel, 12 Fasern
|
||||||
|
- sämtliche Mittelspannungsmontagen
|
||||||
|
- VLF/Mantelprüfungen/OTDR
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Breitbandausbau FTTC, Planungsregion Spreewald"
|
|
||||||
client: "Telekom Deutschland GmbH"
|
|
||||||
date: "2017-09-01"
|
|
||||||
dateString: "09/2017 bis 01/2018"
|
|
||||||
location: "Planungsregion Spreewald"
|
|
||||||
featuredImage: "/assets/photos/DSC01129.JPG"
|
|
||||||
---
|
|
||||||
|
|
||||||
- 4.006 m Kabelgraben ausheben und verfüllen
|
|
||||||
- 4.006 m Verlegung PE-Schutzrohr 50 x 4,6 mm
|
|
||||||
- 13.008 m Horizontalspülbohrung D 50
|
|
||||||
- 94.951 m Einblasen von Kabel/Rohren unbelegt
|
|
||||||
- 42 Stück Stellen von Multifunktionsgehäusen
|
|
||||||
- Aufbau von Energieanschlusssäulen und elektrischen Anlagen 230V
|
|
||||||
- Koordination mit Energieversorgungsunternehmen und Inbetriebnahme der elektrischen Anlagen
|
|
||||||
- Oberflächen- und Tiefbauarbeiten für Multifunktionsgehäuse
|
|
||||||
@@ -42,7 +42,7 @@ layout: "fullBleed"
|
|||||||
{
|
{
|
||||||
id: "martin-polan",
|
id: "martin-polan",
|
||||||
name: "Martin Polan",
|
name: "Martin Polan",
|
||||||
position: "Polier",
|
position: "Bauleiter",
|
||||||
email: "m.polan@e-tib.com",
|
email: "m.polan@e-tib.com",
|
||||||
phone: "+49 171 3868167",
|
phone: "+49 171 3868167",
|
||||||
image: "/assets/photos/team/martin.jpg"
|
image: "/assets/photos/team/martin.jpg"
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ layout: "fullBleed"
|
|||||||
Seit unserer Gründung im Jahr 2015 in Guben haben wir es uns zur Aufgabe gemacht, den Ausbau der kritischen Infrastruktur in Deutschland aktiv mitzugestalten.
|
Seit unserer Gründung im Jahr 2015 in Guben haben wir es uns zur Aufgabe gemacht, den Ausbau der kritischen Infrastruktur in Deutschland aktiv mitzugestalten.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Mit über 50 engagierten Fachkräften und einem hochmodernen Maschinenpark meistern wir selbst anspruchsvollste Projekte im Kabelleitungstiefbau, der Spülbohrtechnik und der Netzmontage. Verlässlichkeit, Termintreue und höchste Qualitätsstandards sind dabei unser Fundament.
|
Mit über 100 Experten für Ihr Projekt (darunter Spezialisten der E-TIB GmbH, E-TIB Bohrtechnik und E-TIB Ingenieurgesellschaft) und einem hochmodernen Maschinenpark meistern wir selbst anspruchsvollste Projekte im Kabelnetzbau, der Spülbohrtechnik und der Netzmontage. Verlässlichkeit, Termintreue und höchste Qualitätsstandards sind dabei unser Fundament.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-6">
|
<div className="grid grid-cols-2 gap-6">
|
||||||
<div className="bg-white p-8 rounded-2xl shadow-sm border border-neutral-100">
|
<div className="bg-white p-8 rounded-2xl shadow-sm border border-neutral-100">
|
||||||
<div className="text-4xl font-extrabold text-primary mb-2">50+</div>
|
<div className="text-4xl font-extrabold text-primary mb-2">100+</div>
|
||||||
<div className="text-neutral-dark font-semibold">Mitarbeiter</div>
|
<div className="text-neutral-dark font-semibold">Mitarbeitende</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-8 rounded-2xl shadow-sm border border-neutral-100">
|
<div className="bg-white p-8 rounded-2xl shadow-sm border border-neutral-100">
|
||||||
<div className="text-4xl font-extrabold text-primary mb-2">2015</div>
|
<div className="text-4xl font-extrabold text-primary mb-2">2015</div>
|
||||||
@@ -52,7 +52,20 @@ layout: "fullBleed"
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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." />
|
<div className="bg-[#050B14] py-24 md:py-32 text-white relative overflow-hidden group my-16 md:my-24 rounded-[2.5rem] md:rounded-[3.5rem] mx-4 md:mx-auto max-w-7xl">
|
||||||
|
<div className="absolute inset-0 bg-[url('/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-54.jpg')] opacity-20 bg-cover bg-center mix-blend-overlay group-hover:scale-105 transition-transform duration-1000"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-[#050B14] via-[#0A1322]/90 to-[#050B14]"></div>
|
||||||
|
<div className="container max-w-4xl mx-auto px-4 relative z-10 text-center">
|
||||||
|
<div className="inline-block px-4 py-1.5 rounded-full bg-primary/10 text-primary border border-primary/20 text-xs font-bold uppercase tracking-wider mb-8">
|
||||||
|
Bundesweite Präsenz
|
||||||
|
</div>
|
||||||
|
<h2 className="font-heading text-4xl md:text-5xl lg:text-6xl font-extrabold mb-6 leading-tight text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70">Infrastruktur für<br />ein ganzes Land</h2>
|
||||||
|
<p className="text-lg md:text-xl text-white/60 mb-12 max-w-2xl mx-auto leading-relaxed">Seit 2016 haben wir in über 200 Projekten quer durch Deutschland bewiesen, dass wir Infrastrukturprojekte jeder Größenordnung verlässlich umsetzen.</p>
|
||||||
|
<a href="/de/referenzen" className="inline-flex items-center gap-3 bg-primary text-[#050B14] px-8 py-4 rounded-xl font-bold uppercase tracking-wider hover:bg-white hover:-translate-y-1 transition-all duration-300 shadow-[0_0_20px_rgba(130,237,32,0.3)]">
|
||||||
|
Zu unseren Referenzen <span className="text-xl leading-none">→</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<CompanyTimeline />
|
<CompanyTimeline />
|
||||||
|
|
||||||
@@ -63,32 +76,36 @@ layout: "fullBleed"
|
|||||||
companies={[
|
companies={[
|
||||||
{
|
{
|
||||||
title: "E-TIB GMBH",
|
title: "E-TIB GMBH",
|
||||||
description: "Das Unternehmen hat sich voll und ganz auf den Kabelleitungstiefbau spezialisiert. Das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand.",
|
description: "Das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand.",
|
||||||
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
|
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
|
||||||
url: "https://www.e-tib.com/",
|
url: "/de/standorte/guben",
|
||||||
backgroundImage: "/assets/photos/DJI_0243.JPG"
|
backgroundImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-54.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Ingenieurgesellschaft mbH",
|
title: "E-TIB Ingenieurgesellschaft mbH",
|
||||||
description: "Unsere Ingenieure planen für Sie die Energie- und Telekommunikationsnetze der Zukunft - Vermessung und Dokumentation inklusive.",
|
description: "Unsere Ingenieure planen für Sie die Energie- und Telekommunikationsnetze der Zukunft - Vermessung und Dokumentation inklusive.",
|
||||||
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
||||||
url: "https://www.etib-ing.com/",
|
url: "https://www.etib-ing.com/",
|
||||||
backgroundImage: "/assets/photos/DSC01137.JPG"
|
backgroundImage: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Verwaltung GmbH",
|
title: "E-TIB Verwaltung GmbH",
|
||||||
description: "Zentrale Dienste, Einkauf und Finanzen sowie Verwaltung von Immobilien, Grundstücken, Maschinen und Geräten.",
|
description: "Zentrale Dienste, Einkauf und Finanzen sowie Verwaltung von Immobilien, Grundstücken, Maschinen und Geräten.",
|
||||||
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
||||||
|
url: "/de/standorte/buelstedt",
|
||||||
backgroundImage: "/assets/photos/DJI_0048.JPG"
|
backgroundImage: "/assets/photos/DJI_0048.JPG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Bohrtechnik GmbH",
|
title: "E-TIB Bohrtechnik GmbH",
|
||||||
description: "Präzise Horizontalspülbohrungen in allen Bodenklassen für eine oberflächenschonende und grabenlose Leitungsverlegung.",
|
description: "Präzise Horizontalspülbohrungen in allen Bodenklassen für eine oberflächenschonende und grabenlose Leitungsverlegung.",
|
||||||
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
||||||
backgroundImage: "/assets/photos/DSC08653.JPG"
|
url: "/de/bohrtechnik",
|
||||||
}
|
backgroundImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-47.jpg"
|
||||||
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<CallToAction />
|
<CallToAction />
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ layout: "fullBleed"
|
|||||||
<HeroSection
|
<HeroSection
|
||||||
badge="Kernkompetenz"
|
badge="Kernkompetenz"
|
||||||
title="Vermessung & Dokumentation"
|
title="Vermessung & Dokumentation"
|
||||||
subtitle="Lückenlose und rechtssichere Erfassung aller Bauphasen"
|
subtitle="Hochpräzise digitale Bestandsdokumentation, 3D-Scans und Drohnenvermessung für Ihre Leitungstrassen und Anlagen."
|
||||||
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
|
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(24_von_99)_groß2.jpg' }}
|
||||||
alignment="center"
|
alignment="center"
|
||||||
ctaLabel="Projekt anfragen"
|
ctaLabel="Projekt anfragen"
|
||||||
ctaHref="/de/kontakt"
|
ctaHref="/de/kontakt"
|
||||||
|
|||||||
@@ -1,22 +1,59 @@
|
|||||||
---
|
---
|
||||||
title: "Terms and Conditions"
|
title: "Terms and Conditions"
|
||||||
date: "2024-03-20"
|
date: "2026-05-28"
|
||||||
excerpt: "The General Terms and Conditions of E-TIB GmbH for our services and deliveries."
|
excerpt: "The General Terms and Conditions of E-TIB GmbH for our services and deliveries."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Terms and Conditions (T&C)
|
# Terms and Conditions (T&C)
|
||||||
|
|
||||||
## 1. Scope
|
## 1. Scope and Conclusion of Contract
|
||||||
These General Terms and Conditions apply to all contracts, deliveries, and other services provided by E-TIB GmbH to its customers.
|
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. Conclusion of Contract
|
## 2. Scope of Services and Customer Cooperation
|
||||||
A contract is only formed upon our written order confirmation or by the execution of the order.
|
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. Prices and Payment Terms
|
## 3. Execution Deadlines, Obstruction, and Construction Delays
|
||||||
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.
|
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. Liability
|
## 4. Acceptance, Transfer of Risk, and Warranty
|
||||||
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.
|
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. Jurisdiction
|
## 5. Prices, Progress Payments, and Payment Terms
|
||||||
The place of jurisdiction for all disputes arising from the contractual relationship is Cottbus, provided the customer is a merchant.
|
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.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "Trenchless Line Laying"
|
title: "Drilling Technology"
|
||||||
date: "2024-03-20"
|
date: "2024-03-20"
|
||||||
excerpt: "Precise horizontal directional drilling and earth rockets in all soil classes for surface-friendly and efficient line laying."
|
excerpt: "Precise horizontal directional drilling and earth rockets in all soil classes for surface-friendly and efficient line laying."
|
||||||
layout: "fullBleed"
|
layout: "fullBleed"
|
||||||
@@ -7,9 +7,9 @@ layout: "fullBleed"
|
|||||||
|
|
||||||
<HeroSection
|
<HeroSection
|
||||||
badge="Core Competency"
|
badge="Core Competency"
|
||||||
title="Trenchless Laying"
|
title="Drilling Technology"
|
||||||
subtitle="Surface-friendly horizontal directional drilling and earth rockets"
|
subtitle="Trenchless Laying, horizontal directional drilling and earth rockets"
|
||||||
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
|
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-47.jpg' }}
|
||||||
alignment="center"
|
alignment="center"
|
||||||
ctaLabel="Request a project"
|
ctaLabel="Request a project"
|
||||||
ctaHref="/en/contact"
|
ctaHref="/en/contact"
|
||||||
@@ -17,18 +17,18 @@ layout: "fullBleed"
|
|||||||
|
|
||||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||||
<blockquote>
|
<blockquote>
|
||||||
If classic trench construction is out of the question for space, environmental or cost reasons, we rely on the most modern trenchless techniques. We protect nature and minimize restoration costs.
|
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>
|
</blockquote>
|
||||||
|
|
||||||
## Maximum Efficiency, Minimal Intervention
|
## Maximum Efficiency, Minimal Impact
|
||||||
|
|
||||||
Trenchless line laying is the smartest solution for complex construction projects. This process shows its absolute strengths particularly when crossing highly frequented roads, railway lines, highways or sensitive bodies of water. Traffic continues to flow, nature remains untouched and complex surfaces such as expensive paving or asphalt do not have to be destroyed.
|
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 Boreholes and Earth Rockets
|
## HDD Directional Drilling & Pneumatic Earth Rockets
|
||||||
|
|
||||||
With our specialized machinery, we master a wide variety of processes. With the **Horizontal Directional Drilling (HDD) process**, we steer the drill head precisely underground. We achieve drilling lengths of up to 250 meters in one piece and can pull in protective pipes with a diameter of up to 400 mm.
|
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 400 meters in a single run and, depending on the drilling length, pull in high-durability protective conduits with diameters up to 600 mm.
|
||||||
|
|
||||||
For shorter distances, such as fast and gentle house connections, we use our **earth rockets** (soil displacement method). Here we achieve drilling lengths of up to 15 meters for pipes up to 160 mm in diameter - ideal for leaving front gardens and driveways completely intact.
|
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 />
|
<DeepDrillAnimation />
|
||||||
</div>
|
</div>
|
||||||
@@ -47,8 +47,8 @@ layout: "fullBleed"
|
|||||||
fullWidth: false,
|
fullWidth: false,
|
||||||
lists: [
|
lists: [
|
||||||
[
|
[
|
||||||
"Max. drilling length: 250 m",
|
"Max. drilling length: 400 m",
|
||||||
"Max. diameter: 400mm pipe",
|
"Max. diameter: 600mm pipe",
|
||||||
"Precision control and steerability",
|
"Precision control and steerability",
|
||||||
"Crossing of railways, highways & water bodies"
|
"Crossing of railways, highways & water bodies"
|
||||||
]
|
]
|
||||||
@@ -71,7 +71,7 @@ layout: "fullBleed"
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HomeReferencesSlider badge="Case Studies" title="Completed Drilling Projects" description="Successful HDD drilling and trenchless cable laying under the most complex conditions." />
|
<HomeReferencesSlider badge="Case Studies" title="References" description="Successful HDD drilling and trenchless cable laying under the most complex conditions." />
|
||||||
|
|
||||||
<CallToAction
|
<CallToAction
|
||||||
title="Looking for a trenchless solution?"
|
title="Looking for a trenchless solution?"
|
||||||
|
|||||||
@@ -17,18 +17,18 @@ layout: "fullBleed"
|
|||||||
|
|
||||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||||
<blockquote>
|
<blockquote>
|
||||||
A life without electricity or high-speed internet is unimaginable today - that is why we lay cables. We build the backbone of the digital society.
|
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>
|
</blockquote>
|
||||||
|
|
||||||
## The Backbone of Digitalization
|
## The Digital Backbone: Precision-Driven Fiber Expansion
|
||||||
|
|
||||||
Fast internet and state-of-the-art telecommunications networks require an absolutely smooth and highly professional network expansion. With highly specialized assembly teams and our own modern fleet of machinery, E-TIB ensures the highest execution quality in nationwide fiber optic 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 Expansion from a Single Source
|
## FTTX Deployment: A Seamless, End-to-End Solution
|
||||||
|
|
||||||
As a powerful partner for telecommunications providers, we cover every step of physical network creation. We take over the construction of complete empty conduit routes as well as cable and pipe trenches. In addition, we position and build multi-function enclosures at strategic network nodes.
|
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 service does not end with pure civil engineering: We take care of the professional pulling and blowing in of highly sensitive fiber optic and telecommunication cables over long distances and carry out all required cable assemblies. In this way, we guarantee that the digital infrastructure can go into operation safely and performantly.
|
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>
|
</div>
|
||||||
|
|
||||||
<ServiceDetailGrid
|
<ServiceDetailGrid
|
||||||
@@ -69,7 +69,7 @@ layout: "fullBleed"
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HomeReferencesSlider badge="Case Studies" title="Completed Broadband Projects" description="We have already successfully realized hundreds of kilometers of FTTX networks. Discover our references." />
|
<HomeReferencesSlider badge="Case Studies" title="References" description="We have already successfully realized hundreds of kilometers of FTTX networks. Discover our references." />
|
||||||
|
|
||||||
<CallToAction
|
<CallToAction
|
||||||
title="Are you planning an FTTX expansion?"
|
title="Are you planning an FTTX expansion?"
|
||||||
|
|||||||
@@ -18,27 +18,30 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
|||||||
companies={[
|
companies={[
|
||||||
{
|
{
|
||||||
title: "E-TIB GMBH",
|
title: "E-TIB GMBH",
|
||||||
description: "The company has fully specialized in cable civil engineering. The complete range of services for the construction of cable routes from a single source.",
|
description: "The complete range of services for the construction of cable routes from a single source.",
|
||||||
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
|
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",
|
||||||
backgroundImage: "/assets/photos/DJI_0243.JPG"
|
url: "/en/standorte/guben",
|
||||||
|
backgroundImage: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-54.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Ingenieurgesellschaft mbH",
|
title: "E-TIB Ingenieurgesellschaft mbH",
|
||||||
description: "Our engineers plan the energy and telecommunications networks of the future for you - surveying and documentation included.",
|
description: "Our engineers plan the energy and telecommunications networks of the future for you - surveying and documentation included.",
|
||||||
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
||||||
url: "https://www.etib-ing.com/",
|
url: "https://www.etib-ing.com/",
|
||||||
backgroundImage: "/assets/photos/DSC01137.JPG"
|
backgroundImage: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Verwaltung GmbH",
|
title: "E-TIB Verwaltung GmbH",
|
||||||
description: "Central services, purchasing, finance, and management of real estate, properties, machinery, and equipment.",
|
description: "Central services, purchasing, finance, and management of real estate, properties, machinery, and equipment.",
|
||||||
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4",
|
||||||
|
url: "/en/standorte/buelstedt",
|
||||||
backgroundImage: "/assets/photos/DJI_0048.JPG"
|
backgroundImage: "/assets/photos/DJI_0048.JPG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "E-TIB Bohrtechnik GmbH",
|
title: "E-TIB Bohrtechnik GmbH",
|
||||||
description: "Precise horizontal directional drilling in all soil classes for surface-friendly and trenchless pipe laying.",
|
description: "Precise horizontal directional drilling in all soil classes for surface-friendly and trenchless pipe laying.",
|
||||||
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
icon: "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
|
||||||
|
url: "/en/drilling-technology",
|
||||||
backgroundImage: "/assets/photos/DSC08653.JPG"
|
backgroundImage: "/assets/photos/DSC08653.JPG"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
@@ -59,22 +62,26 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
|||||||
subtitle="About E-TIB"
|
subtitle="About E-TIB"
|
||||||
badgeStyle="industrial"
|
badgeStyle="industrial"
|
||||||
className="mb-6 md:mb-8"
|
className="mb-6 md:mb-8"
|
||||||
>Your <span className="text-primary relative whitespace-nowrap">strong<svg className="absolute -bottom-2 left-0 w-full h-3 text-primary/20" viewBox="0 0 100 10" preserveAspectRatio="none"><path d="M0 5 Q 50 10 100 5" stroke="currentColor" strokeWidth="4" fill="none"/></svg></span> Partner <br/>for Infrastructure</Heading>
|
>Your <span className="text-primary relative whitespace-nowrap">strong<svg className="absolute -bottom-2 left-0 w-full h-3 text-warning/60" viewBox="0 0 100 10" preserveAspectRatio="none"><path d="M0 5 Q 50 10 100 5" stroke="currentColor" strokeWidth="4" fill="none"/></svg></span> Partner <br/>for Infrastructure</Heading>
|
||||||
|
|
||||||
<div className="text-xl text-text-secondary leading-relaxed mb-8">
|
<div className="text-xl text-text-secondary leading-relaxed mb-8">
|
||||||
E-TIB GmbH is an efficient and modern provider of technical services. We specialize in future-oriented core areas of infrastructure.
|
E-TIB GmbH is an efficient and modern provider of technical services. We specialize in future-oriented core areas of infrastructure.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="grid grid-cols-2 gap-6 pt-8 border-t border-neutral-200">
|
<div className="grid grid-cols-3 gap-6 pt-8 border-t border-neutral-200">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">2015</div>
|
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">2015</div>
|
||||||
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Founded</div>
|
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Founded</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">50+</div>
|
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">100+</div>
|
||||||
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Employees</div>
|
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Employees</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl font-black text-primary mb-1 tracking-tighter">100+</div>
|
||||||
|
<div className="text-sm font-bold text-text-secondary uppercase tracking-wider">Clients</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -110,31 +117,36 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
|||||||
ctaHref="/en/competencies"
|
ctaHref="/en/competencies"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: "Cable Construction",
|
title: "Cable Civil Engineering",
|
||||||
description: "Installation of medium and low voltage cables",
|
description: "Cable civil engineering (high, medium, low voltage) and cable assembly up to 110 kV",
|
||||||
tag: "Energy",
|
tag: "Energy",
|
||||||
size: "large",
|
size: "large",
|
||||||
image: { url: "/assets/photos/DSC01123.JPG", alt: "Cable Construction" }
|
href: "/en/cable-civil-engineering",
|
||||||
|
image: { url: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-54.jpg", alt: "Cable Civil Engineering" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Drilling Technology",
|
title: "Drilling Technology",
|
||||||
description: "Horizontal directional drilling for trenchless installation",
|
description: "Horizontal directional drilling for trenchless installation",
|
||||||
tag: "Innovation",
|
tag: "Innovation",
|
||||||
size: "medium",
|
size: "medium",
|
||||||
image: { url: "/assets/photos/DSC08653.JPG", alt: "Drilling Technology" }
|
href: "/en/drilling-technology",
|
||||||
|
image: { url: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-47.jpg", alt: "Drilling Technology" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Planning",
|
title: "Planning",
|
||||||
description: "Route planning and approval procedures",
|
description: "Route planning and approval procedures",
|
||||||
tag: "Precision",
|
tag: "Precision",
|
||||||
size: "small",
|
size: "small",
|
||||||
image: { url: "/assets/photos/DJI_0048.JPG", alt: "Planning" }
|
href: "/en/planning",
|
||||||
|
image: { url: "/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg", alt: "Planning" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Surveying",
|
title: "Surveying",
|
||||||
description: "Digital as-built documentation",
|
description: "Digital as-built documentation",
|
||||||
tag: "Digital",
|
tag: "Digital",
|
||||||
size: "accent"
|
size: "accent",
|
||||||
|
href: "/en/surveying",
|
||||||
|
image: { url: "/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(26_von_99).jpg", alt: "Surveying" }
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: "Cable Civil Engineering"
|
title: "Cable Civil Engineering"
|
||||||
date: "2024-03-20"
|
date: "2024-03-20"
|
||||||
excerpt: "Professional cable civil engineering for medium and low voltage networks. We guarantee maximum safety standards and seamless documentation for your project."
|
excerpt: "Professional cable civil engineering (high, medium, and low voltage) and cable assembly up to 110 kV. We guarantee maximum safety standards and seamless documentation for your project."
|
||||||
layout: "fullBleed"
|
layout: "fullBleed"
|
||||||
---
|
---
|
||||||
|
|
||||||
<HeroSection
|
<HeroSection
|
||||||
badge="Core Competency"
|
badge="Core Competency"
|
||||||
title="Cable Civil Engineering"
|
title="Cable Civil Engineering"
|
||||||
subtitle="Complete infrastructure solutions for medium and low voltage networks"
|
subtitle="Complete infrastructure solutions for high, medium, and low voltage networks and cable assembly up to 110 kV"
|
||||||
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
|
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
|
||||||
alignment="center"
|
alignment="center"
|
||||||
ctaLabel="Request a project"
|
ctaLabel="Request a project"
|
||||||
@@ -17,29 +17,27 @@ layout: "fullBleed"
|
|||||||
|
|
||||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||||
<blockquote>
|
<blockquote>
|
||||||
Everywhere digging and drilling is happening, but unfortunately a lot also goes wrong. We focus on quality, the highest safety standards and legally compliant documentation - so that your construction project does not turn into a cost trap.
|
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>
|
</blockquote>
|
||||||
|
|
||||||
## Quality instead of a Cost Trap
|
## Precision over Compromise: Preventing Infrastructure Failures
|
||||||
|
|
||||||
On many construction sites, cheap providers from abroad are increasingly being used, which often leads to **safety standards and documentation requirements being ignored**. The consequences are severe: accidents, damaged external lines (such as electricity, water and gas) as well as massive construction delays and expensive supplements. A supposed bargain quickly turns out to be a financial burden.
|
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.
|
||||||
|
|
||||||
Particularly for funded infrastructure projects, retrieving the funding is extremely problematic without absolutely seamless, standard-compliant documentation.
|
## Everything from a single source
|
||||||
|
|
||||||
## Everything from a Single Source
|
E-TIB GmbH is fully specialized in cable network construction. Thanks to our many years of experience, we identify problems early on and develop safe and satisfactory solutions for our clients in advance.
|
||||||
|
|
||||||
Thanks to our many years of experience, we identify problems early on and work out safe and satisfactory solutions for our clients in advance. E-TIB GmbH has fully specialized in cable civil engineering.
|
We offer you the complete range of services for the construction of cable routes from a single source - with our own ultra-modern machine park and permanently employed specialist staff. In direct cooperation with our partner office, E-TIB Ingenieurgesellschaft mbH, we can also rule out planning errors early on and master even the most complex documentation requirements confidently.
|
||||||
|
|
||||||
We offer you the complete range of services for the construction of cable routes from a single source - with our own ultra-modern machine fleet and permanent specialist staff from the region. In direct cooperation with our partner office, E-TIB Ingenieurgesellschaft mbH, we can also rule out planning errors at an early stage and sovereignly meet even the most complex documentation requirements.
|
|
||||||
|
|
||||||
<DataGridPulse />
|
<DataGridPulse />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ServiceDetailGrid
|
<ServiceDetailGrid
|
||||||
badge="Service Portfolio"
|
badge="Service Spectrum"
|
||||||
title="Everything for Your Network"
|
title="Everything for your network"
|
||||||
descriptionParagraphs={[
|
descriptionParagraphs={[
|
||||||
"We offer the highest precision, modern machines and certified experts for every civil engineering project - from cable laying to transformer stations."
|
"We offer the highest precision, state-of-the-art machinery, and certified experts for your project."
|
||||||
]}
|
]}
|
||||||
panels={[
|
panels={[
|
||||||
{
|
{
|
||||||
@@ -51,7 +49,7 @@ layout: "fullBleed"
|
|||||||
[
|
[
|
||||||
"Production of cable and pipe trenches",
|
"Production of cable and pipe trenches",
|
||||||
"Cable laying from 0.4 kV to 110 kV",
|
"Cable laying from 0.4 kV to 110 kV",
|
||||||
"Cable assembly from 0.4 kV to 30 kV",
|
"Cable assembly up to 110 kV",
|
||||||
"Cable plowing works for fast laying"
|
"Cable plowing works for fast laying"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -73,7 +71,7 @@ layout: "fullBleed"
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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." />
|
<HomeReferencesSlider badge="Case Studies" title="References" description="We have laid hundreds of kilometers of cables. Take a look at our reference projects." />
|
||||||
|
|
||||||
<CallToAction
|
<CallToAction
|
||||||
title="Do you have a specific project?"
|
title="Do you have a specific project?"
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ excerpt: "Become part of a dynamic team and help us build the infrastructure of
|
|||||||
layout: "fullBleed"
|
layout: "fullBleed"
|
||||||
---
|
---
|
||||||
|
|
||||||
<HomeHero
|
<HeroSection
|
||||||
badge="Careers"
|
badge="Careers"
|
||||||
title="Build the Future"
|
title="Build the Future"
|
||||||
description="Are you looking for a new challenge in a future-proof market? At E-TIB, we work daily to network Germany and drive the energy transition forward."
|
subtitle="Are you looking for a new challenge in a future-proof market? At E-TIB, we work daily to network Germany and drive the energy transition forward."
|
||||||
videoUrl="/assets/videos/web/hero-kabelpflug.mp4"
|
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-28.jpg' }}
|
||||||
linkText="Open Positions"
|
alignment="center"
|
||||||
linkHref="#stellen"
|
ctaLabel="Open Positions"
|
||||||
|
ctaHref="#stellen"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<JobListingBlock showFairs={false} showJobs={true} />
|
<JobListingBlock showFairs={false} showJobs={true} />
|
||||||
|
|||||||
@@ -23,19 +23,19 @@ layout: "fullBleed"
|
|||||||
ctaHref="/en/contact"
|
ctaHref="/en/contact"
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
title: "Cable Construction",
|
title: "Cable Civil Engineering",
|
||||||
description: "Classic trench construction and professional laying of medium and low voltage cables.",
|
description: "Classic trench construction, professional laying and cable assembly (high, medium, and low voltage) up to 110 kV.",
|
||||||
tag: "Energy",
|
tag: "Energy",
|
||||||
size: "large",
|
size: "large",
|
||||||
href: "/en/kabeltiefbau",
|
href: "/en/cable-civil-engineering",
|
||||||
image: { url: "/assets/photos/DSC01123.JPG", alt: "Cable Construction" }
|
image: { url: "/assets/photos/DSC01123.JPG", alt: "Cable Civil Engineering" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Drilling Technology",
|
title: "Drilling Technology",
|
||||||
description: "Horizontal directional drilling (HDD) for trenchless, surface-friendly installation.",
|
description: "Horizontal directional drilling (HDD) for trenchless, surface-friendly installation.",
|
||||||
tag: "Innovation",
|
tag: "Innovation",
|
||||||
size: "medium",
|
size: "medium",
|
||||||
href: "/en/bohrtechnik",
|
href: "/en/drilling-technology",
|
||||||
image: { url: "/assets/photos/DSC08653.JPG", alt: "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).",
|
description: "Expansion of future-proof broadband networks (FTTX).",
|
||||||
tag: "Communication",
|
tag: "Communication",
|
||||||
size: "small",
|
size: "small",
|
||||||
href: "/en/glasfaser",
|
href: "/en/fiber-optics",
|
||||||
image: { url: "/assets/photos/DSC01129.JPG", alt: "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.",
|
description: "Route planning, approval procedures, and precise GIS documentation.",
|
||||||
tag: "Digital",
|
tag: "Digital",
|
||||||
size: "accent",
|
size: "accent",
|
||||||
href: "/en/planung"
|
href: "/en/planning"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -70,13 +70,13 @@ layout: "fullBleed"
|
|||||||
panels={[
|
panels={[
|
||||||
{
|
{
|
||||||
id: "energy",
|
id: "energy",
|
||||||
title: "Cable Civil Engineering (Medium/Low Voltage)",
|
title: "Cable Civil Engineering (High/Medium/Low Voltage)",
|
||||||
icon: "energy",
|
icon: "energy",
|
||||||
lists: [
|
lists: [
|
||||||
[
|
[
|
||||||
"Production of cable and pipe trenches",
|
"Production of cable and pipe trenches",
|
||||||
"Cable laying (0.4kV - 110kV)",
|
"Cable laying (0.4kV - 110kV)",
|
||||||
"Cable assembly 0.4 kV - 30 kV",
|
"Cable assembly up to 110 kV",
|
||||||
"Construction of transformer stations",
|
"Construction of transformer stations",
|
||||||
"Establishment of grounding systems",
|
"Establishment of grounding systems",
|
||||||
"Erection of street lighting",
|
"Erection of street lighting",
|
||||||
@@ -143,8 +143,8 @@ layout: "fullBleed"
|
|||||||
<JsonLd data={[
|
<JsonLd data={[
|
||||||
{
|
{
|
||||||
"@type": "Service",
|
"@type": "Service",
|
||||||
"name": "Cable Construction",
|
"name": "Cable Civil Engineering",
|
||||||
"description": "Classic trench construction and professional laying of medium and low voltage cables.",
|
"description": "Classic trench construction, professional laying and cable assembly (high, medium, and low voltage) up to 110 kV.",
|
||||||
"provider": { "@type": "Organization", "name": "E-TIB GmbH" }
|
"provider": { "@type": "Organization", "name": "E-TIB GmbH" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user