Compare commits
25 Commits
fix/refere
...
fix/transl
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b79d18a93 | |||
| fa747763b8 | |||
| 0d71e2ad24 | |||
| 64d4edc175 | |||
| 222c894266 | |||
| 4a3aee7691 | |||
| fd8dac97d0 | |||
| bc5a78d73e | |||
| f43b945756 | |||
| 183342cb8c | |||
| 3728c37dce | |||
| f61f1411cb | |||
| d2668e36eb | |||
| 5678ddcfd9 | |||
| 06d5178614 | |||
| a983696153 | |||
| 6908964df5 | |||
| 159587f545 | |||
| c02b49644e | |||
| 313fa14c56 | |||
| 052be7d1c9 | |||
| aef10f706c | |||
| 35d9650626 | |||
| 140cb012df | |||
| e1c9720eeb |
@@ -43,9 +43,33 @@ jobs:
|
||||
- name: 🧹 Maintenance (High Density Cleanup)
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Purging old build layers and dangling images..."
|
||||
docker image prune -f
|
||||
docker builder prune -f --filter "until=24h"
|
||||
echo "=== System Disk Usage ==="
|
||||
df -h || true
|
||||
echo "=== Tool Cache Usage ==="
|
||||
du -sh /opt/hostedtoolcache/* || true
|
||||
du -sh /opt/hostedtoolcache/.[!.]* || true
|
||||
echo "Purging old tool caches..."
|
||||
rm -rf /opt/hostedtoolcache/* || true
|
||||
rm -rf /opt/hostedtoolcache/.[!.]* || true
|
||||
echo "=== Host Disk Usage ==="
|
||||
docker run --rm -v /:/host-root alpine df -h || true
|
||||
echo "=== Host Inode Usage ==="
|
||||
docker run --rm -v /:/host-root alpine df -i || true
|
||||
echo "=== Running Host Containers ==="
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || true
|
||||
echo "=== Host Docker Info ==="
|
||||
docker info || true
|
||||
echo "Pruning host Buildkit builder caches aggressively..."
|
||||
for container in $(docker ps --filter "name=buildx_buildkit_builder" --format "{{.Names}}"); do
|
||||
echo "Pruning Buildkit inside container: $container"
|
||||
docker exec -i "$container" buildctl prune --all || true
|
||||
done
|
||||
echo "Purging old build layers, unused volumes and images..."
|
||||
docker builder prune -a -f || true
|
||||
docker volume prune -f || true
|
||||
docker system prune -a -f || true
|
||||
echo "=== System Disk Usage After Prune ==="
|
||||
df -h || true
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -207,6 +231,7 @@ jobs:
|
||||
UMAMI_WEBSITE_ID=${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
|
||||
UMAMI_API_ENDPOINT=${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
|
||||
NPM_TOKEN=${{ steps.auth.outputs.working_token }}
|
||||
NPM_DOMAIN=${{ steps.auth.outputs.working_domain }}
|
||||
tags: registry.infra.mintel.me/mintel/e-tib.com:${{ needs.prepare.outputs.image_tag }}
|
||||
secrets: |
|
||||
"NPM_TOKEN=${{ steps.auth.outputs.working_token }}"
|
||||
|
||||
1
.turbo-test/85340b254e0f10f1-manifest.json
Normal file
1
.turbo-test/85340b254e0f10f1-manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-lint.log":{"size":10746,"mtime_nanos":1779098006288513580,"mode":420,"is_dir":false}},"order":[".turbo/turbo-lint.log"]}
|
||||
1
.turbo-test/85340b254e0f10f1-meta.json
Normal file
1
.turbo-test/85340b254e0f10f1-meta.json
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"85340b254e0f10f1","duration":6595,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"9cc3e16c40a10f42f7a963333e800e1384f047b1f387b18b5f37769c34f36609"}
|
||||
BIN
.turbo-test/85340b254e0f10f1.tar.zst
Normal file
BIN
.turbo-test/85340b254e0f10f1.tar.zst
Normal file
Binary file not shown.
1
.turbo/cache/32905076da5f4894-manifest.json
vendored
Normal file
1
.turbo/cache/32905076da5f4894-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-typecheck.log":{"size":92,"mtime_nanos":1779097674383861303,"mode":420,"is_dir":false}},"order":[".turbo/turbo-typecheck.log"]}
|
||||
1
.turbo/cache/32905076da5f4894-meta.json
vendored
Normal file
1
.turbo/cache/32905076da5f4894-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"32905076da5f4894","duration":4492,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"58f8ebc92a0452cbb0a09c5ebe800385fcc42ff82fee098bb7d6c8828bc526f0"}
|
||||
BIN
.turbo/cache/32905076da5f4894.tar.zst
vendored
Normal file
BIN
.turbo/cache/32905076da5f4894.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/7e2629942c881f80-manifest.json
vendored
Normal file
1
.turbo/cache/7e2629942c881f80-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-test.log":{"size":818,"mtime_nanos":1779097671747078359,"mode":420,"is_dir":false}},"order":[".turbo/turbo-test.log"]}
|
||||
1
.turbo/cache/7e2629942c881f80-meta.json
vendored
Normal file
1
.turbo/cache/7e2629942c881f80-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"7e2629942c881f80","duration":1855,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"58f8ebc92a0452cbb0a09c5ebe800385fcc42ff82fee098bb7d6c8828bc526f0"}
|
||||
BIN
.turbo/cache/7e2629942c881f80.tar.zst
vendored
Normal file
BIN
.turbo/cache/7e2629942c881f80.tar.zst
vendored
Normal file
Binary file not shown.
1
.turbo/cache/85340b254e0f10f1-manifest.json
vendored
Normal file
1
.turbo/cache/85340b254e0f10f1-manifest.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{".turbo/turbo-lint.log":{"size":10746,"mtime_nanos":1779097675143794164,"mode":420,"is_dir":false}},"order":[".turbo/turbo-lint.log"]}
|
||||
1
.turbo/cache/85340b254e0f10f1-meta.json
vendored
Normal file
1
.turbo/cache/85340b254e0f10f1-meta.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"hash":"85340b254e0f10f1","duration":5252,"sha":"35d9650626b56a727e9c1981308145cb7b2f28aa","dirty_hash":"58f8ebc92a0452cbb0a09c5ebe800385fcc42ff82fee098bb7d6c8828bc526f0"}
|
||||
BIN
.turbo/cache/85340b254e0f10f1.tar.zst
vendored
Normal file
BIN
.turbo/cache/85340b254e0f10f1.tar.zst
vendored
Normal file
Binary file not shown.
119
.turbo/turbo-lint.log
Normal file
119
.turbo/turbo-lint.log
Normal file
@@ -0,0 +1,119 @@
|
||||
|
||||
|
||||
> e-tib-nextjs@2.2.12 lint /Volumes/Alpha SSD/Coding/e-tib.com
|
||||
> eslint .
|
||||
|
||||
|
||||
/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/[slug]/page.tsx
|
||||
1:20 warning 'redirect' is defined but never used @typescript-eslint/no-unused-vars
|
||||
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.
|
||||
|
||||
31
.turbo/turbo-test.log
Normal file
31
.turbo/turbo-test.log
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
> e-tib-nextjs@2.2.12 test /Volumes/Alpha SSD/Coding/e-tib.com
|
||||
> vitest run --passWithNoTests
|
||||
|
||||
[?25l
|
||||
RUN v4.1.2 /Volumes/Alpha SSD/Coding/e-tib.com
|
||||
|
||||
[?2026h
|
||||
❯ lib/mail/mailer.test.ts [queued]
|
||||
|
||||
Test Files 0 passed (2)
|
||||
Tests 0 passed (0)
|
||||
Start at 11:47:51
|
||||
Duration 504ms
|
||||
[?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
|
||||
|
||||
❯ lib/env.test.ts 0/6
|
||||
|
||||
Test Files 1 passed (2)
|
||||
Tests 1 passed (7)
|
||||
Start at 11:47:51
|
||||
Duration 605ms
|
||||
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K ✓ lib/env.test.ts (6 tests) 3ms
|
||||
|
||||
Test Files 2 passed (2)
|
||||
Tests 7 passed (7)
|
||||
Start at 11:47:51
|
||||
Duration 672ms (transform 113ms, setup 0ms, import 222ms, tests 5ms, environment 813ms)
|
||||
|
||||
[?25h
|
||||
5
.turbo/turbo-typecheck.log
Normal file
5
.turbo/turbo-typecheck.log
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
> e-tib-nextjs@2.2.12 typecheck /Volumes/Alpha SSD/Coding/e-tib.com
|
||||
> tsc --noEmit
|
||||
|
||||
@@ -20,13 +20,15 @@ ENV CI=true
|
||||
COPY pnpm-lock.yaml package.json .npmrc* ./
|
||||
COPY patches* ./patches/
|
||||
|
||||
ARG NPM_DOMAIN=git.infra.mintel.me
|
||||
|
||||
# Configure private registry and install dependencies
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
--mount=type=secret,id=NPM_TOKEN \
|
||||
if [ -f /run/secrets/NPM_TOKEN ]; then \
|
||||
export NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN); \
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc; \
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${NPM_TOKEN}" >> .npmrc; \
|
||||
echo "@mintel:registry=https://${NPM_DOMAIN}/api/packages/mmintel/npm" > .npmrc; \
|
||||
echo "//${NPM_DOMAIN}/api/packages/mmintel/npm/:_authToken=${NPM_TOKEN}" >> .npmrc; \
|
||||
fi && \
|
||||
pnpm install --no-frozen-lockfile && \
|
||||
rm -f .npmrc
|
||||
|
||||
@@ -5,8 +5,8 @@ RUN apk add --no-cache libc6-compat python3 make g++ curl
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Enable corepack for pnpm
|
||||
RUN corepack enable
|
||||
# Install pnpm v10 explicitly (corepack defaults to v11 which requires Node 22)
|
||||
RUN npm install -g pnpm@10.18.3
|
||||
|
||||
# Pre-set the pnpm store directory
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SITE_URL } from '@/lib/schema';
|
||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { getAllReferences } from '@/lib/references';
|
||||
import { defaultLocations } from '@/lib/map-data';
|
||||
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
||||
|
||||
// Import components used in MDX
|
||||
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
|
||||
@@ -26,8 +26,12 @@ import { FaqBlock } from '@/components/blocks/FaqBlock';
|
||||
import { CertificatesBlock } from '@/components/blocks/CertificatesBlock';
|
||||
import { HeroSection } from '@/components/blocks/HeroSection';
|
||||
import { TeamGrid } from '@/components/blocks/TeamGrid';
|
||||
import { DeepDrillAnimation } from '@/components/blocks/DeepDrillAnimation';
|
||||
import { DataGridPulse } from '@/components/blocks/DataGridPulse';
|
||||
import { GrowthChart } from '@/components/blocks/GrowthChart';
|
||||
import JsonLd from '@/components/JsonLd';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { AnimatedCounter } from '@/components/ui';
|
||||
|
||||
|
||||
const mdxComponents = {
|
||||
@@ -47,6 +51,10 @@ const mdxComponents = {
|
||||
TeamGrid,
|
||||
JsonLd,
|
||||
Button,
|
||||
AnimatedCounter,
|
||||
GrowthChart,
|
||||
DeepDrillAnimation,
|
||||
DataGridPulse,
|
||||
// Standard HTML element mapping for consistent E-TIB typography
|
||||
h1: (props: any) => <Heading level={1} size="2" className="hidden" {...props} />, // Hidden because Hero handles H1
|
||||
h2: (props: any) => <Heading level={2} size="3" className="mt-16 mb-6 border-b border-neutral-100 pb-4" {...props} />,
|
||||
@@ -145,7 +153,8 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
||||
|
||||
// Fetch references to enrich the map if it's used
|
||||
const references = await getAllReferences(locale);
|
||||
const enrichedLocations = defaultLocations.map(loc => {
|
||||
const allLocations = [...defaultLocations, ...minorLocations];
|
||||
const enrichedLocations = allLocations.map(loc => {
|
||||
if (loc.type === 'project') {
|
||||
const ref = references.find(r => r.slug === loc.id);
|
||||
if (ref) {
|
||||
@@ -169,7 +178,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
||||
id: r.slug,
|
||||
slug: r.slug,
|
||||
title: r.frontmatter.title,
|
||||
category: r.frontmatter.category || 'Projekt',
|
||||
category: Array.isArray(r.frontmatter.category) ? r.frontmatter.category[0] : (r.frontmatter.category || 'Projekt'),
|
||||
image: r.frontmatter.featuredImage
|
||||
}))} />,
|
||||
};
|
||||
|
||||
@@ -100,10 +100,6 @@ export default async function Layout(props: {
|
||||
label: safeLocale === 'de' ? 'Referenzen' : 'References',
|
||||
url: `/${safeLocale}/referenzen`
|
||||
},
|
||||
{
|
||||
label: safeLocale === 'de' ? 'Karriere' : 'Careers',
|
||||
url: `/${safeLocale}/${await mapFileSlugToTranslated('karriere', safeLocale)}`
|
||||
},
|
||||
{
|
||||
label: safeLocale === 'de' ? 'Messen' : 'Events',
|
||||
url: `/${safeLocale}/${await mapFileSlugToTranslated('messen', safeLocale)}`
|
||||
|
||||
@@ -18,6 +18,9 @@ import JsonLd from '@/components/JsonLd';
|
||||
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Heading } from '@/components/ui/Heading';
|
||||
import { AnimatedCounter } from '@/components/ui';
|
||||
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
||||
import { ScaleOfImpact } from '@/components/blocks/ScaleOfImpact';
|
||||
import { getAllReferences } from '@/lib/references';
|
||||
|
||||
const mdxComponents = (references: any[]) => ({
|
||||
@@ -31,6 +34,9 @@ const mdxComponents = (references: any[]) => ({
|
||||
JsonLd,
|
||||
Button,
|
||||
Heading,
|
||||
AnimatedCounter,
|
||||
InteractiveGermanyMap,
|
||||
ScaleOfImpact,
|
||||
});
|
||||
|
||||
export async function generateMetadata(props: any): Promise<Metadata> {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import Image from 'next/image';
|
||||
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
||||
import { defaultLocations } from '@/lib/map-data';
|
||||
import { defaultLocations, minorLocations } from '@/lib/map-data';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
@@ -34,6 +34,17 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
};
|
||||
}
|
||||
|
||||
function cleanLocation(location?: string): string {
|
||||
if (!location) return '';
|
||||
// Remove 5 digit postal codes
|
||||
let cleaned = location.replace(/\b\d{5}\b/g, '');
|
||||
// Clean up multiple spaces, slashes and commas
|
||||
cleaned = cleaned.replace(/\s*,\s*/g, ', ').replace(/\s*\/\s*/g, ' / ').trim();
|
||||
// Remove leading/trailing slash/comma residues
|
||||
cleaned = cleaned.replace(/^[,/\s]+|[,/\s]+$/g, '').trim();
|
||||
return cleaned || location;
|
||||
}
|
||||
|
||||
export default async function ReferenzenOverview(props: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await props.params;
|
||||
setRequestLocale(locale);
|
||||
@@ -41,8 +52,8 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
|
||||
const references = await getAllReferences(locale);
|
||||
|
||||
// Enrich the static map locations with dynamic project data (images, details)
|
||||
const enrichedLocations = defaultLocations.map(loc => {
|
||||
const allLocations = [...defaultLocations, ...minorLocations];
|
||||
const enrichedLocations = allLocations.map(loc => {
|
||||
if (loc.type === 'project') {
|
||||
const ref = references.find(r => r.slug === loc.id);
|
||||
if (ref) {
|
||||
@@ -64,9 +75,9 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
{/* Map Hero Section */}
|
||||
<InteractiveGermanyMap
|
||||
isHero={true}
|
||||
badge="Unsere Referenzen"
|
||||
title={<>Erfolgreich umgesetzte<br/>Projekte.</>}
|
||||
description="Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben. Entdecken Sie unsere Standorte."
|
||||
badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'}
|
||||
title={locale === 'en' ? <>Successfully realized<br/>projects.</> : <>Erfolgreich umgesetzte<br/>Projekte.</>}
|
||||
description={locale === 'en' ? 'From broadband expansion to complex 110kV lines: A selection of our nationwide projects where we have created infrastructure for the future. Discover our locations.' : 'Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben. Entdecken Sie unsere Standorte.'}
|
||||
locations={enrichedLocations}
|
||||
/>
|
||||
|
||||
@@ -104,7 +115,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
<div className="absolute top-4 left-4 z-10">
|
||||
<span className="bg-white/95 backdrop-blur-md text-neutral-dark px-3 py-1.5 rounded-sm text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 shadow-sm border border-neutral-200">
|
||||
<MapPin className="w-3 h-3 text-primary" />
|
||||
{ref.frontmatter.location}
|
||||
{cleanLocation(ref.frontmatter.location)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ export function CertificatesBlock({ badge, title, description, certificates = de
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: 'easeOut' } },
|
||||
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: 'easeOut' as const } },
|
||||
};
|
||||
|
||||
if (!isMounted) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import * as React from 'react';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { Building2, Compass, Layers, ArrowDownToLine, Wrench, Factory, Zap, MapPin, CheckCircle2 } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
const iconMap: Record<string, React.ElementType> = {
|
||||
Building2,
|
||||
@@ -16,37 +17,6 @@ const iconMap: Record<string, React.ElementType> = {
|
||||
CheckCircle2
|
||||
};
|
||||
|
||||
const defaultMilestones = [
|
||||
{
|
||||
date: '16.12.2015',
|
||||
year: '2015',
|
||||
title: 'Gründung E-TIB GmbH',
|
||||
desc: 'Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen.',
|
||||
iconName: 'Building2',
|
||||
},
|
||||
{
|
||||
date: '04.02.2019',
|
||||
year: '2019',
|
||||
title: 'Gründung E-TIB Ingenieurgesellschaft',
|
||||
desc: 'Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung.',
|
||||
iconName: 'Compass',
|
||||
},
|
||||
{
|
||||
date: '14.11.2019',
|
||||
year: '2019',
|
||||
title: 'Gründung E-TIB Verwaltung GmbH',
|
||||
desc: 'Zentrale Dienste, Erwerb, Vermietung, Verpachtung und Verwaltung von Immobilien und Maschinen.',
|
||||
iconName: 'Layers',
|
||||
},
|
||||
{
|
||||
date: '21.10.2025',
|
||||
year: '2025',
|
||||
title: 'Gründung E-TIB Bohrtechnik GmbH',
|
||||
desc: 'Spezialisierung auf präzise Horizontalspülbohrungen in allen Bodenklassen.',
|
||||
iconName: 'ArrowDownToLine',
|
||||
},
|
||||
];
|
||||
|
||||
interface Milestone {
|
||||
date: string;
|
||||
year: string;
|
||||
@@ -62,10 +32,45 @@ interface CompanyTimelineProps {
|
||||
}
|
||||
|
||||
export function CompanyTimeline({
|
||||
badge = 'Unsere Geschichte',
|
||||
title = 'Meilensteine der Entwicklung',
|
||||
milestones = defaultMilestones
|
||||
badge,
|
||||
title,
|
||||
milestones
|
||||
}: CompanyTimelineProps) {
|
||||
const t = useTranslations('CompanyTimeline');
|
||||
|
||||
const finalBadge = badge || t('badge');
|
||||
const finalTitle = title || t('title');
|
||||
|
||||
const finalMilestones = milestones || [
|
||||
{
|
||||
date: '16.12.2015',
|
||||
year: '2015',
|
||||
title: t('milestones.2015.title'),
|
||||
desc: t('milestones.2015.desc'),
|
||||
iconName: 'Building2',
|
||||
},
|
||||
{
|
||||
date: '04.02.2019',
|
||||
year: '2019',
|
||||
title: t('milestones.2019_ing.title'),
|
||||
desc: t('milestones.2019_ing.desc'),
|
||||
iconName: 'Compass',
|
||||
},
|
||||
{
|
||||
date: '14.11.2019',
|
||||
year: '2019',
|
||||
title: t('milestones.2019_verw.title'),
|
||||
desc: t('milestones.2019_verw.desc'),
|
||||
iconName: 'Layers',
|
||||
},
|
||||
{
|
||||
date: '21.10.2025',
|
||||
year: '2025',
|
||||
title: t('milestones.2025.title'),
|
||||
desc: t('milestones.2025.desc'),
|
||||
iconName: 'ArrowDownToLine',
|
||||
},
|
||||
];
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
@@ -84,10 +89,10 @@ export function CompanyTimeline({
|
||||
<div className="text-center mb-24">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-white border border-neutral-200 text-neutral-dark text-xs font-bold uppercase tracking-wider mb-6 shadow-sm">
|
||||
<span className="w-2 h-2 rounded-full bg-primary animate-pulse" />
|
||||
{badge}
|
||||
{finalBadge}
|
||||
</div>
|
||||
<h3 className="font-heading text-4xl md:text-5xl lg:text-6xl font-extrabold text-neutral-dark tracking-tight">
|
||||
{title}
|
||||
{finalTitle}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -104,7 +109,7 @@ export function CompanyTimeline({
|
||||
</div>
|
||||
|
||||
<div className="space-y-16 md:space-y-24">
|
||||
{milestones.map((milestone, i) => {
|
||||
{finalMilestones.map((milestone, i) => {
|
||||
const isEven = i % 2 === 0;
|
||||
const Icon = iconMap[milestone.iconName] || Building2;
|
||||
|
||||
|
||||
52
components/blocks/DataGridPulse.tsx
Normal file
52
components/blocks/DataGridPulse.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
export function DataGridPulse() {
|
||||
const locale = useLocale();
|
||||
return (
|
||||
<div className="relative w-full py-32 bg-[#050B14] overflow-hidden flex flex-col items-center border-y border-primary/20 -mx-[50vw] px-[50vw] mb-16">
|
||||
|
||||
{/* Blueprint grid background */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-20 pointer-events-none"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(#82ED20 1px, transparent 1px), linear-gradient(90deg, #82ED20 1px, transparent 1px)',
|
||||
backgroundSize: '40px 40px'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Pulsing horizontal lines (Data Flow) */}
|
||||
<motion.div
|
||||
animate={{ x: ["-100%", "100%"] }}
|
||||
transition={{ duration: 3, 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]"
|
||||
/>
|
||||
<motion.div
|
||||
animate={{ x: ["100%", "-100%"] }}
|
||||
transition={{ duration: 4, 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]"
|
||||
/>
|
||||
|
||||
<div className="relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center text-center">
|
||||
<h3 className="text-sm font-bold uppercase tracking-widest text-primary mb-6">
|
||||
{locale === 'en' ? 'Network Expansion in Numbers' : 'Netzausbau in Zahlen'}
|
||||
</h3>
|
||||
|
||||
<div className="bg-black/40 backdrop-blur-md p-12 md:p-16 rounded-3xl border border-white/10 shadow-2xl">
|
||||
<div className="font-mono text-6xl md:text-8xl font-black text-white tracking-tighter mb-4 leading-none" style={{ fontVariantNumeric: 'tabular-nums' }}>
|
||||
372.161<span className="text-primary text-3xl md:text-5xl ml-2">m</span>
|
||||
</div>
|
||||
<p className="text-white/70 text-lg md:text-xl font-medium max-w-lg mx-auto">
|
||||
{locale === 'en'
|
||||
? 'Cable routes laid since 2023. A massive infrastructure achievement, driven by our own machinery.'
|
||||
: 'Verlegte Kabeltrassen seit 2023. Eine massive Infrastrukturleistung, getragen von unserem eigenen Maschinenpark.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
components/blocks/DeepDrillAnimation.tsx
Normal file
64
components/blocks/DeepDrillAnimation.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
export function DeepDrillAnimation() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const locale = useLocale();
|
||||
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start center", "end center"]
|
||||
});
|
||||
|
||||
const drillDepth = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative w-full py-32 flex flex-col items-center">
|
||||
|
||||
<div className="absolute inset-0 bg-[#050B14] -mx-[50vw] px-[50vw]" />
|
||||
|
||||
<div className="relative z-10 w-full max-w-3xl mx-auto flex flex-col items-center text-center text-white">
|
||||
|
||||
<h3 className="text-3xl font-heading font-extrabold mb-8 tracking-tight">
|
||||
{locale === 'en' ? 'Precision in Depth' : 'Präzision in der Tiefe'}
|
||||
</h3>
|
||||
<p className="text-white/60 text-lg mb-16 max-w-xl">
|
||||
{locale === 'en'
|
||||
? 'Horizontal Directional Drilling (HDD) allows us to cross obstacles and rivers with extreme care.'
|
||||
: 'Horizontalspülbohrverfahren (HDD) erlaubt uns, Hindernisse und Flüsse extrem schonend zu unterqueren.'}
|
||||
</p>
|
||||
|
||||
{/* The Drill Path */}
|
||||
<div className="relative w-2 h-96 bg-white/10 rounded-full mb-16">
|
||||
<motion.div
|
||||
className="absolute top-0 left-0 w-full bg-primary rounded-full"
|
||||
style={{ height: drillDepth, boxShadow: '0 0 20px rgba(130,237,32,0.6)' }}
|
||||
>
|
||||
{/* The Drill Head */}
|
||||
<div className="absolute -bottom-2 -left-2 w-6 h-6 bg-white rounded-full border-4 border-primary shadow-[0_0_15px_rgba(255,255,255,1)]" />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* The Reveal Data */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div className="font-mono text-6xl md:text-8xl font-black text-white tracking-tighter mb-4" style={{ fontVariantNumeric: 'tabular-nums' }}>
|
||||
102.771<span className="text-primary text-3xl md:text-5xl ml-2">m</span>
|
||||
</div>
|
||||
<div className="text-sm font-bold uppercase tracking-widest text-primary">
|
||||
{locale === 'en' ? 'Drilled distance since 2023' : 'Gebohrte Strecke seit 2023'}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
80
components/blocks/GrowthChart.tsx
Normal file
80
components/blocks/GrowthChart.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export function GrowthChart() {
|
||||
const data = [
|
||||
{ year: '2023', hdd: 29436, kabel: 76727 },
|
||||
{ year: '2024', hdd: 28680, kabel: 131964 },
|
||||
{ year: '2025', hdd: 44655, kabel: 163470 },
|
||||
];
|
||||
|
||||
const maxVal = 163470; // Highest value for scaling
|
||||
|
||||
return (
|
||||
<div className="w-full bg-white rounded-3xl p-8 md:p-12 shadow-sm border border-neutral-100 my-16">
|
||||
<div className="mb-10">
|
||||
<h3 className="text-3xl font-heading font-extrabold text-neutral-dark mb-2">Bewiesene Leistungsfähigkeit</h3>
|
||||
<p className="text-neutral-500 font-medium">Unsere verlässliche Baukapazität der letzten drei Jahre (in Metern)</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{data.map((item, index) => {
|
||||
const hddWidth = (item.hdd / maxVal) * 100;
|
||||
const kabelWidth = (item.kabel / maxVal) * 100;
|
||||
|
||||
return (
|
||||
<div key={item.year} className="relative">
|
||||
<div className="text-sm font-extrabold text-neutral-dark mb-2 w-16">{item.year}</div>
|
||||
<div className="space-y-2">
|
||||
{/* Kabelverlegung Bar */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1 bg-neutral-100 rounded-full h-6 overflow-hidden relative">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${kabelWidth}%` }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
transition={{ duration: 1.5, delay: 0.1 * index, ease: "easeOut" }}
|
||||
className="absolute top-0 left-0 h-full bg-primary rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
||||
Kabel: {new Intl.NumberFormat('de-DE').format(item.kabel)}m
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HDD Bar */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex-1 bg-neutral-100 rounded-full h-4 overflow-hidden relative">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${hddWidth}%` }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
transition={{ duration: 1.5, delay: 0.2 + 0.1 * index, ease: "easeOut" }}
|
||||
className="absolute top-0 left-0 h-full bg-neutral-dark rounded-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-24 text-right text-xs font-bold text-neutral-500 uppercase">
|
||||
HDD: {new Intl.NumberFormat('de-DE').format(item.hdd)}m
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex gap-6 border-t border-neutral-100 pt-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-primary" />
|
||||
<span className="text-xs font-bold text-neutral-500 uppercase">Kabelverlegung</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-neutral-dark" />
|
||||
<span className="text-xs font-bold text-neutral-500 uppercase">HDD Spülbohrung</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,10 @@ import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
||||
import { Location, defaultLocations } from '@/lib/map-data';
|
||||
import { Location, defaultLocations, minorLocations } from '@/lib/map-data';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
|
||||
const allLocations = [...defaultLocations, ...minorLocations];
|
||||
|
||||
interface Stat {
|
||||
value: string;
|
||||
@@ -24,22 +27,31 @@ interface InteractiveGermanyMapProps {
|
||||
}
|
||||
|
||||
export function InteractiveGermanyMap({
|
||||
badge = 'Einsatzgebiete',
|
||||
title = <>Deutschlandweit<br/>für Sie im Einsatz.</>,
|
||||
description = 'Von unseren strategischen Standorten in Guben und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.',
|
||||
badge,
|
||||
title,
|
||||
description,
|
||||
stats = [
|
||||
{ value: '100', suffix: '%', label: 'Überregionale Reichweite' },
|
||||
{ value: '2', suffix: '+', label: 'Operative Standorte' },
|
||||
],
|
||||
locations = defaultLocations,
|
||||
locations = allLocations,
|
||||
isHero = false
|
||||
}: InteractiveGermanyMapProps) {
|
||||
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
|
||||
const router = useRouter();
|
||||
const locale = useLocale();
|
||||
const t = useTranslations('InteractiveGermanyMap');
|
||||
const tStandard = useTranslations('StandardPage');
|
||||
|
||||
const finalBadge = badge || tStandard('badge');
|
||||
// the map is mostly used with specific props, so we leave title & description to fallbacks if not provided
|
||||
const finalTitle = title || (locale === 'en' ? <>Nationwide<br/>in operation for you.</> : <>Deutschlandweit<br/>für Sie im Einsatz.</>);
|
||||
const finalDescription = description || (locale === 'en' ? 'From our strategic locations in Guben and Bülstedt, we control and implement complex infrastructure projects nationwide.' : 'Von unseren strategischen Standorten in Guben und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.');
|
||||
|
||||
const hq = locations.find((l) => l.type === 'hq');
|
||||
const branch = locations.find((l) => l.type === 'branch');
|
||||
const projects = locations.filter((l) => l.type === 'project');
|
||||
const minorNodes = locations.filter((l) => l.type === 'minor_node');
|
||||
|
||||
return (
|
||||
<div className={isHero ? "relative w-full" : "relative w-full max-w-7xl mx-auto py-16 md:py-24 px-4 sm:px-6 mt-16 md:mt-20"}>
|
||||
@@ -57,21 +69,21 @@ export function InteractiveGermanyMap({
|
||||
<div className="xl:col-span-5 text-white z-20">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-8">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>{badge}</span>
|
||||
<span>{finalBadge}</span>
|
||||
</div>
|
||||
|
||||
{isHero ? (
|
||||
<h1 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]">
|
||||
{title}
|
||||
{finalTitle}
|
||||
</h1>
|
||||
) : (
|
||||
<h3 className="font-heading text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 break-words [hyphens:auto]">
|
||||
{title}
|
||||
{finalTitle}
|
||||
</h3>
|
||||
)}
|
||||
|
||||
<p className="text-white/60 text-lg mb-12 leading-relaxed">
|
||||
{description}
|
||||
{finalDescription}
|
||||
</p>
|
||||
|
||||
{/* Industrial Stats Grid */}
|
||||
@@ -91,80 +103,49 @@ export function InteractiveGermanyMap({
|
||||
</div>
|
||||
|
||||
{/* Map Right */}
|
||||
<div className="xl:col-span-7 relative flex items-center justify-center lg:justify-end mt-8 lg:mt-0 z-10">
|
||||
<div className="xl:col-span-7 relative flex items-center justify-center min-h-[500px] lg:min-h-[700px] z-10 pointer-events-none">
|
||||
{/* Map Container - Enforce strict aspect ratio matching the SVG (1024x1024) */}
|
||||
<div className="relative w-full max-w-[600px] aspect-square z-10">
|
||||
<div className="relative w-full max-w-[600px] aspect-square group pointer-events-auto">
|
||||
|
||||
{/* Map SVG */}
|
||||
<div className="absolute inset-0 opacity-[0.25] mix-blend-screen drop-shadow-2xl brightness-200">
|
||||
<div className="absolute inset-0 opacity-[0.25] mix-blend-screen drop-shadow-2xl brightness-200 pointer-events-none">
|
||||
<Image
|
||||
src="/germany-map.svg"
|
||||
alt="Deutschlandkarte"
|
||||
alt={locale === 'en' ? "Map of Germany" : "Deutschlandkarte"}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Connections (Lines) */}
|
||||
<svg className="absolute inset-0 w-full h-full pointer-events-none z-10">
|
||||
{hq && branch && (
|
||||
<motion.line
|
||||
x1={`${hq.x}%`}
|
||||
y1={`${hq.y}%`}
|
||||
x2={`${branch.x}%`}
|
||||
y2={`${branch.y}%`}
|
||||
stroke="rgba(130,237,32,0.6)"
|
||||
strokeWidth="2"
|
||||
strokeDasharray="6 6"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 2, ease: "easeOut" }}
|
||||
/>
|
||||
)}
|
||||
{hq && projects.map((proj, idx) => (
|
||||
<motion.line
|
||||
key={proj.id}
|
||||
x1={`${hq.x}%`}
|
||||
y1={`${hq.y}%`}
|
||||
x2={`${proj.x}%`}
|
||||
y2={`${proj.y}%`}
|
||||
stroke="rgba(130,237,32,0.3)"
|
||||
strokeWidth="1.5"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 1.5, delay: 0.5 + idx * 0.2 }}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
{/* Minor Location Markers (The 100+ generic projects) */}
|
||||
{locations.filter(l => l.type === 'minor_node').map((loc, idx) => (
|
||||
<div
|
||||
key={`minor-${idx}`}
|
||||
className="absolute z-10 group/minor cursor-pointer w-10 h-10 flex items-center justify-center"
|
||||
style={{
|
||||
left: `${loc.x}%`,
|
||||
top: `${loc.y}%`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
onMouseEnter={() => setActiveLocation(loc)}
|
||||
onMouseLeave={() => setActiveLocation(null)}
|
||||
>
|
||||
<div className="w-1.5 h-1.5 bg-primary/80 rounded-full group-hover/minor:bg-white group-hover/minor:scale-150 transition-all duration-300" />
|
||||
<div className="absolute inset-0 m-auto w-2 h-2 bg-primary rounded-full opacity-0 group-hover/minor:animate-ping" />
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Location Pins */}
|
||||
{locations.map((loc, idx) => {
|
||||
{/* Major & Reference Location Markers (HQ, Branch, Project) */}
|
||||
{locations.filter(l => l.type !== 'minor_node').map((loc, idx) => {
|
||||
const isHQ = loc.type === 'hq';
|
||||
const isBranch = loc.type === 'branch';
|
||||
const isActive = activeLocation?.id === loc.id;
|
||||
|
||||
const isNearLeft = loc.x < 50;
|
||||
const isNearRight = loc.x >= 50;
|
||||
const isNearTop = loc.y < 55; // 55% threshold to strongly avoid the top header
|
||||
|
||||
const tooltipClasses = [
|
||||
'absolute w-[280px] sm:w-64 bg-white text-neutral-dark p-5 rounded-2xl shadow-2xl z-[9999] border border-neutral-100',
|
||||
isNearLeft ? 'left-1/2 sm:left-0 -translate-x-1/2 sm:translate-x-0' : 'left-1/2 sm:left-auto sm:right-0 -translate-x-1/2 sm:translate-x-0',
|
||||
isNearTop
|
||||
? 'top-full mt-4 before:absolute before:-top-6 before:left-0 before:w-full before:h-6'
|
||||
: 'bottom-full mb-4 before:absolute before:-bottom-6 before:left-0 before:w-full before:h-6'
|
||||
].join(' ');
|
||||
|
||||
const arrowClasses = [
|
||||
'absolute border-8 border-transparent drop-shadow-sm hidden sm:block',
|
||||
isNearLeft ? 'left-6' : 'right-6',
|
||||
isNearTop ? '-top-4 border-b-white border-t-0' : '-bottom-4 border-t-white border-b-0'
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<div
|
||||
key={loc.id}
|
||||
className={`absolute transform -translate-x-1/2 -translate-y-1/2 group cursor-pointer w-16 h-16 flex items-center justify-center ${isActive ? 'z-[60]' : 'z-20'}`}
|
||||
className={`absolute transform -translate-x-1/2 -translate-y-1/2 group/pin cursor-pointer w-16 h-16 flex items-center justify-center ${isActive ? 'z-[60]' : 'z-20'}`}
|
||||
style={{ left: `${loc.x}%`, top: `${loc.y}%` }}
|
||||
onMouseEnter={() => setActiveLocation(loc)}
|
||||
onMouseLeave={() => setActiveLocation(null)}
|
||||
@@ -177,121 +158,112 @@ export function InteractiveGermanyMap({
|
||||
<div className="absolute inset-0 m-auto w-6 h-6 rounded-full animate-ping bg-primary/50 scale-150" />
|
||||
)}
|
||||
|
||||
{/* Marker Dot */}
|
||||
{/* Elegant Marker Dot */}
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', delay: idx * 0.1 }}
|
||||
className={`relative flex items-center justify-center rounded-full shadow-[0_0_20px_rgba(130,237,32,0.4)] transition-transform duration-300 ${
|
||||
isActive ? 'scale-125 z-30 ring-4 ring-primary/30' : 'scale-100'
|
||||
className={`relative flex items-center justify-center rounded-full shadow-[0_0_20px_rgba(130,237,32,0.6)] transition-transform duration-300 ${
|
||||
isActive ? 'scale-125 z-30 ring-4 ring-primary/40' : 'scale-100 group-hover/pin:scale-110'
|
||||
} ${
|
||||
isHQ || isBranch
|
||||
? 'w-6 h-6 bg-primary text-[#050B14]'
|
||||
? 'w-7 h-7 bg-primary text-[#050B14]'
|
||||
: 'w-4 h-4 bg-white ring-[3px] ring-primary'
|
||||
}`}
|
||||
>
|
||||
{(isHQ || isBranch) && <MapPin className="w-3.5 h-3.5" />}
|
||||
{(isHQ || isBranch) && <MapPin className="w-4 h-4" />}
|
||||
</motion.div>
|
||||
|
||||
{/* Tooltip */}
|
||||
<AnimatePresence>
|
||||
{isActive && (
|
||||
<motion.div
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: isNearTop ? -15 : 15, scale: 0.9 },
|
||||
visible: {
|
||||
opacity: 1, y: 0, scale: 1,
|
||||
transition: {
|
||||
type: "spring", stiffness: 350, damping: 25,
|
||||
staggerChildren: 0.05, delayChildren: 0.05
|
||||
}
|
||||
},
|
||||
exit: {
|
||||
opacity: 0, y: isNearTop ? -10 : 10, scale: 0.95,
|
||||
transition: { duration: 0.15, ease: "easeIn" }
|
||||
}
|
||||
}}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
className={`${tooltipClasses} overflow-hidden p-0`}
|
||||
>
|
||||
{loc.featuredImage && (
|
||||
<motion.div
|
||||
variants={{ hidden: { opacity: 0 }, visible: { opacity: 1 } }}
|
||||
className="relative w-full h-32 bg-neutral-100 overflow-hidden"
|
||||
>
|
||||
<Image
|
||||
src={loc.featuredImage}
|
||||
alt={loc.name}
|
||||
fill
|
||||
className="object-cover transform transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-80" />
|
||||
<div className="absolute bottom-3 left-4 flex items-center gap-2">
|
||||
{isHQ ? <Factory className="w-4 h-4 text-primary drop-shadow" /> : <Zap className="w-4 h-4 text-primary drop-shadow" />}
|
||||
<span className="font-extrabold text-sm leading-tight text-white drop-shadow-md">{loc.name}</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div className={loc.featuredImage ? "p-5 pt-4" : "p-5"}>
|
||||
{!loc.featuredImage && (
|
||||
<motion.div
|
||||
variants={{ hidden: { opacity: 0, x: -5 }, visible: { opacity: 1, x: 0 } }}
|
||||
className="flex items-center gap-2 mb-2"
|
||||
>
|
||||
{isHQ ? <Factory className="w-4 h-4 text-primary" /> : <Zap className="w-4 h-4 text-primary" />}
|
||||
<span className="font-extrabold text-sm leading-tight text-neutral-dark">{loc.name}</span>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<motion.p
|
||||
variants={{ hidden: { opacity: 0, y: 5 }, visible: { opacity: 1, y: 0 } }}
|
||||
className="text-xs text-neutral-500 font-medium"
|
||||
>
|
||||
{loc.description}
|
||||
</motion.p>
|
||||
|
||||
{loc.details && (
|
||||
<ul className="mt-4 space-y-2">
|
||||
{loc.details.map((detail, i) => (
|
||||
<motion.li
|
||||
key={i}
|
||||
variants={{ hidden: { opacity: 0, x: -5 }, visible: { opacity: 1, x: 0 } }}
|
||||
className="text-[11px] text-neutral-dark flex items-center gap-2 font-semibold"
|
||||
>
|
||||
<div className="w-4 h-4 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary" />
|
||||
</div>
|
||||
{detail}
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{loc.href && (
|
||||
<motion.div
|
||||
variants={{ hidden: { opacity: 0, y: 5 }, visible: { opacity: 1, y: 0 } }}
|
||||
className="mt-4 pt-3 border-t border-neutral-100 text-xs font-bold text-primary flex items-center gap-1 group/link"
|
||||
>
|
||||
Zum Projekt
|
||||
<span className="text-base leading-none translate-y-[1px] transform transition-transform duration-300 group-hover:translate-x-1.5">→</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Arrow Down/Up */}
|
||||
<div className={arrowClasses} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Global Tooltip */}
|
||||
<AnimatePresence>
|
||||
{activeLocation && (
|
||||
<motion.div
|
||||
key="tooltip"
|
||||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="absolute z-[9999] pointer-events-none"
|
||||
style={{
|
||||
left: `${activeLocation.x}%`,
|
||||
top: `${activeLocation.y}%`,
|
||||
transform: activeLocation.x < 50 ? 'translate(20px, -50%)' : 'translate(calc(-100% - 20px), -50%)',
|
||||
}}
|
||||
>
|
||||
<div className="bg-[#050B14]/95 backdrop-blur-xl border border-white/10 rounded-2xl shadow-2xl min-w-[200px] w-max max-w-[300px] text-white overflow-hidden">
|
||||
|
||||
{activeLocation.featuredImage && (
|
||||
<div className="relative w-full h-32 bg-neutral-900">
|
||||
<Image
|
||||
src={activeLocation.featuredImage}
|
||||
alt={activeLocation.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#050B14] to-transparent opacity-90" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-4">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<MapPin className="w-3.5 h-3.5 text-primary" />
|
||||
<span className="text-xs font-bold text-primary uppercase tracking-wider">
|
||||
{t(`types.${activeLocation.type === 'minor_node' ? 'project' : activeLocation.type}`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-heading font-bold text-lg leading-tight mb-2">
|
||||
{activeLocation.name}
|
||||
</div>
|
||||
{activeLocation.type !== 'minor_node' ? (
|
||||
<div className="text-white/60 text-sm line-clamp-3 mb-3">
|
||||
{activeLocation.description}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-white/60 text-xs mb-3">
|
||||
{t(`descriptions.${['wind','pv','fiber','power','battery'].includes(activeLocation.description || '') ? activeLocation.description : 'default'}`)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(activeLocation.details || activeLocation.type === 'minor_node') && (
|
||||
<ul className="mb-3 space-y-1.5 border-t border-white/10 pt-3">
|
||||
{activeLocation.type === 'minor_node' ? (
|
||||
<>
|
||||
<li className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
|
||||
<span>{t(`features.${['wind','pv','fiber','power','battery'].includes(activeLocation.description || '') ? activeLocation.description : 'default'}`)}</span>
|
||||
</li>
|
||||
<li className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
|
||||
<span>{t('features.method')}</span>
|
||||
</li>
|
||||
</>
|
||||
) : (
|
||||
activeLocation.details?.map((detail, i) => (
|
||||
<li key={i} className="text-[11px] text-white/80 flex items-start gap-2 font-medium">
|
||||
<CheckCircle2 className="w-3 h-3 text-primary shrink-0 mt-0.5" />
|
||||
<span>{detail}</span>
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{activeLocation.href && (
|
||||
<div className="inline-flex items-center text-xs font-bold text-primary">
|
||||
{t('learnMore')}
|
||||
<ArrowUpRight className="w-3.5 h-3.5 ml-1" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
96
components/blocks/ReferencesSlider.test.tsx
Normal file
96
components/blocks/ReferencesSlider.test.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ReferencesSlider, Reference } from './ReferencesSlider';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
|
||||
// Mock next/link to render standard <a> tag
|
||||
vi.mock('next/link', () => ({
|
||||
default: vi.fn(({ children, href, onClick, ...props }: any) => {
|
||||
return (
|
||||
<a href={href} onClick={onClick} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}));
|
||||
|
||||
// Mock next/image to render standard img tag
|
||||
vi.mock('next/image', () => ({
|
||||
default: (props: any) => {
|
||||
return <img {...props} />;
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock next-intl
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => 'en',
|
||||
NextIntlClientProvider: ({ children }: any) => <>{children}</>
|
||||
}));
|
||||
|
||||
describe('ReferencesSlider TDD', () => {
|
||||
const mockReferences: Reference[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Referenz Projekt Eins',
|
||||
slug: 'referenz-projekt-eins',
|
||||
category: 'Kabeltiefbau',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Referenz Projekt Zwei',
|
||||
slug: 'referenz-projekt-zwei',
|
||||
category: 'Bohrtechnik',
|
||||
}
|
||||
];
|
||||
|
||||
it('renders reference tiles correctly', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
expect(screen.getByText('Referenz Projekt Eins')).toBeTruthy();
|
||||
expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('wraps each tile in a link to /referenzen/[slug]', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const links = screen.getAllByTestId('reference-tile-link');
|
||||
expect(links).toHaveLength(2);
|
||||
expect(links[0].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-eins');
|
||||
expect(links[1].getAttribute('href')).toBe('/en/referenzen/referenz-projekt-zwei');
|
||||
});
|
||||
|
||||
it('allows navigation on simple click (no drag)', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
|
||||
|
||||
// Simulate simple click sequence
|
||||
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
|
||||
fireEvent.mouseUp(firstLink);
|
||||
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
||||
|
||||
firstLink.dispatchEvent(clickEvent);
|
||||
|
||||
expect(clickEvent.defaultPrevented).toBe(false);
|
||||
});
|
||||
|
||||
it('prevents navigation if mouse was dragged', () => {
|
||||
render(<ReferencesSlider references={mockReferences} />);
|
||||
|
||||
const firstLink = screen.getAllByTestId('reference-tile-link')[0];
|
||||
|
||||
// Simulate drag sequence
|
||||
fireEvent.mouseDown(firstLink, { pageX: 100, clientX: 100 });
|
||||
// Simulate mouse move of 20px
|
||||
fireEvent.mouseMove(firstLink, { pageX: 120, clientX: 120 });
|
||||
fireEvent.mouseUp(firstLink);
|
||||
|
||||
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true });
|
||||
firstLink.dispatchEvent(clickEvent);
|
||||
|
||||
expect(clickEvent.defaultPrevented).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||
import { useTranslations, useLocale } from 'next-intl';
|
||||
|
||||
export interface Reference {
|
||||
id: string;
|
||||
@@ -34,18 +35,23 @@ interface ReferencesSliderProps {
|
||||
}
|
||||
|
||||
export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
const t = useTranslations('ReferencesSlider');
|
||||
const locale = useLocale();
|
||||
|
||||
const { data } = props;
|
||||
const references = props.references || [];
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const dragDistanceRef = React.useRef(0);
|
||||
const startXRef = React.useRef(0);
|
||||
const scrollLeftRef = React.useRef(0);
|
||||
const isDraggingRef = React.useRef(false);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
const [startX, setStartX] = React.useState(0);
|
||||
const [scrollLeft, setScrollLeft] = React.useState(0);
|
||||
|
||||
const badge = props.badge || data?.badge || 'Ausgewählte Projekte';
|
||||
const title = props.title || data?.title || 'Referenzen & Erfolge';
|
||||
const description = props.description || data?.description || 'Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabeltiefbau, Bohrtechnik und Netzinfrastruktur.';
|
||||
const ctaLabel = props.ctaLabel || data?.ctaLabel || 'Alle Referenzen ansehen';
|
||||
const ctaHref = props.ctaHref || data?.ctaHref || '/referenzen';
|
||||
const badge = props.badge || data?.badge || t('badge');
|
||||
const title = props.title || data?.title || t('title');
|
||||
const description = props.description || data?.description || t('description');
|
||||
const ctaLabel = props.ctaLabel || data?.ctaLabel || t('ctaLabel');
|
||||
const ctaHref = props.ctaHref || data?.ctaHref || `/${locale}/referenzen`;
|
||||
|
||||
if (!references || references.length === 0) return null;
|
||||
|
||||
@@ -60,24 +66,36 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
const onMouseDown = (e: React.MouseEvent) => {
|
||||
if (!containerRef.current) return;
|
||||
setIsDragging(true);
|
||||
setStartX(e.pageX - containerRef.current.offsetLeft);
|
||||
setScrollLeft(containerRef.current.scrollLeft);
|
||||
isDraggingRef.current = true;
|
||||
const startXVal = e.clientX - (containerRef.current.offsetLeft || 0);
|
||||
startXRef.current = startXVal;
|
||||
scrollLeftRef.current = containerRef.current.scrollLeft || 0;
|
||||
dragDistanceRef.current = 0;
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
setIsDragging(false);
|
||||
isDraggingRef.current = false;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
setIsDragging(false);
|
||||
isDraggingRef.current = false;
|
||||
};
|
||||
|
||||
const onMouseMove = (e: React.MouseEvent) => {
|
||||
if (!isDragging || !containerRef.current) return;
|
||||
if (!isDraggingRef.current || !containerRef.current) return;
|
||||
e.preventDefault();
|
||||
const x = e.pageX - containerRef.current.offsetLeft;
|
||||
const walk = (x - startX) * 2; // Scroll-fast
|
||||
containerRef.current.scrollLeft = scrollLeft - walk;
|
||||
const x = e.clientX - (containerRef.current.offsetLeft || 0);
|
||||
const walk = (x - startXRef.current) * 2; // Scroll-fast
|
||||
containerRef.current.scrollLeft = scrollLeftRef.current - walk;
|
||||
dragDistanceRef.current = Math.abs(x - startXRef.current);
|
||||
};
|
||||
|
||||
const handleTileClick = (e: React.MouseEvent) => {
|
||||
if (dragDistanceRef.current > 10) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -117,7 +135,12 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
transition={{ delay: i * 0.1, duration: 0.6, ease: "easeOut" }}
|
||||
className="flex-shrink-0 w-[320px] md:w-[480px] snap-center group pointer-events-auto"
|
||||
>
|
||||
<div className="relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl">
|
||||
<Link
|
||||
href={`/${locale}/referenzen/${ref.slug}`}
|
||||
onClick={handleTileClick}
|
||||
data-testid="reference-tile-link"
|
||||
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
src={imgSrc}
|
||||
alt={ref.title}
|
||||
@@ -135,7 +158,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
|
||||
</span>
|
||||
<h4 className="font-heading text-xl md:text-2xl font-bold leading-tight break-words">{ref.title}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
|
||||
102
components/blocks/ScaleOfImpact.tsx
Normal file
102
components/blocks/ScaleOfImpact.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { useLocale } from 'next-intl';
|
||||
|
||||
export function ScaleOfImpact() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const locale = useLocale();
|
||||
|
||||
// Track scroll progress through this container
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start end", "end start"]
|
||||
});
|
||||
|
||||
// Map scroll progress to SVG path length
|
||||
const pathLength = useTransform(scrollYProgress, [0.2, 0.8], [0, 1]);
|
||||
|
||||
// Opacity for the massive background numbers
|
||||
const bgOpacity = useTransform(scrollYProgress, [0.1, 0.4, 0.8, 1], [0, 0.1, 0.1, 0]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative w-full min-h-[70vh] bg-[#050B14] flex flex-col items-center justify-center overflow-hidden border-y border-neutral-800"
|
||||
>
|
||||
{/* Massive Watermark Numbers */}
|
||||
<motion.div
|
||||
style={{ opacity: bgOpacity }}
|
||||
className="absolute inset-0 flex flex-col items-center justify-center pointer-events-none select-none overflow-hidden mix-blend-overlay"
|
||||
>
|
||||
<div className="font-heading font-black text-[20vw] leading-none text-white opacity-20 whitespace-nowrap">
|
||||
<span className="font-mono tracking-tighter">474.932</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Foreground Content */}
|
||||
<div className="relative z-10 container max-w-7xl mx-auto px-4 flex flex-col items-center text-center">
|
||||
|
||||
<h2 className="text-3xl md:text-5xl font-heading font-extrabold text-white mb-6 tracking-tight">
|
||||
{locale === 'en' ? (
|
||||
<>An infrastructure <br className="md:hidden"/> that connects the country.</>
|
||||
) : (
|
||||
<>Eine Infrastruktur, <br className="md:hidden"/> die das Land verbindet.</>
|
||||
)}
|
||||
</h2>
|
||||
|
||||
<p className="text-white/60 text-lg md:text-xl max-w-2xl font-medium mb-16">
|
||||
{locale === 'en'
|
||||
? "We don't just talk about the energy transition. We build it. In the last 36 months alone, we have completed a route that stretches from our headquarters in Guben all the way to Munich."
|
||||
: "Wir sprechen nicht nur über die Energiewende. Wir bauen sie. Allein in den letzten 36 Monaten haben wir eine Strecke realisiert, die von unserem Hauptsitz in Guben bis nach München reicht."
|
||||
}
|
||||
</p>
|
||||
|
||||
{/* Abstract SVG Line Animation */}
|
||||
<div className="w-full max-w-4xl h-32 relative">
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 1000 100"
|
||||
preserveAspectRatio="none"
|
||||
className="absolute inset-0"
|
||||
>
|
||||
{/* Background dim line */}
|
||||
<path
|
||||
d="M0,50 Q250,0 500,50 T1000,50"
|
||||
fill="none"
|
||||
stroke="rgba(255,255,255,0.05)"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
{/* Glowing animated line */}
|
||||
<motion.path
|
||||
d="M0,50 Q250,0 500,50 T1000,50"
|
||||
fill="none"
|
||||
stroke="#82ED20" // primary color
|
||||
strokeWidth="4"
|
||||
style={{ pathLength }}
|
||||
className="drop-shadow-[0_0_15px_rgba(130,237,32,0.8)]"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Static Data Grid (No jumping text) */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-16 w-full max-w-4xl mt-12">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="font-mono text-5xl md:text-6xl font-black text-white tracking-tighter mb-2">100<span className="text-primary">+</span></div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Major Projects' : 'Großprojekte'}</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="font-mono text-5xl md:text-6xl font-black text-white tracking-tighter mb-2">372<span className="text-primary text-3xl">km</span></div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Cable Laying' : 'Kabelverlegung'}</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="font-mono text-5xl md:text-6xl font-black text-white tracking-tighter mb-2">102<span className="text-primary text-3xl">km</span></div>
|
||||
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'HDD Drilling' : 'Spülbohrung'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import Image from 'next/image';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { ShieldCheck, Leaf, Lock, Accessibility, Zap, Mail, Phone } from 'lucide-react';
|
||||
import { LanguageSwitcher } from './LanguageSwitcher';
|
||||
|
||||
import { Tooltip } from '@/components/ui/Tooltip';
|
||||
interface CompanyInfo {
|
||||
contactEmail: string;
|
||||
contactPhone: string;
|
||||
@@ -165,10 +165,18 @@ export function Footer({ companyInfo }: FooterProps) {
|
||||
|
||||
{/* Quality Badges */}
|
||||
<div className="mt-4 md:mt-6 flex flex-wrap justify-center gap-x-4 gap-y-3 md:gap-x-8 md:gap-y-4 text-neutral-400 text-[10px] md:text-xs font-medium uppercase tracking-wider px-2">
|
||||
<div className="flex items-center gap-2"><ShieldCheck className="w-4 h-4 text-neutral-400" /><span>SSL Secured</span></div>
|
||||
<div className="flex items-center gap-2"><Leaf className="w-4 h-4 text-primary" /><span>Green Hosting</span></div>
|
||||
<div className="flex items-center gap-2"><Lock className="w-4 h-4 text-neutral-400" /><span>DSGVO Compliant</span></div>
|
||||
<div className="flex items-center gap-2"><Zap className="w-4 h-4 text-neutral-400" /><span>High Performance</span></div>
|
||||
<Tooltip content={locale === 'de' ? 'Ihre Daten werden sicher und verschlüsselt übertragen.' : 'Your data is transmitted securely and encrypted.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><ShieldCheck className="w-4 h-4 text-neutral-400" /><span>SSL Secured</span></div>
|
||||
</Tooltip>
|
||||
<Tooltip content={locale === 'de' ? 'Wir nutzen 100% Ökostrom für den Betrieb unserer Website.' : 'We use 100% green energy to run our website.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><Leaf className="w-4 h-4 text-primary" /><span>Green Hosting</span></div>
|
||||
</Tooltip>
|
||||
<Tooltip content={locale === 'de' ? 'Ihre Privatsphäre ist uns wichtig. Wir halten uns streng an den Datenschutz.' : 'Your privacy is important to us. We strictly adhere to data protection laws.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><Lock className="w-4 h-4 text-neutral-400" /><span>DSGVO Compliant</span></div>
|
||||
</Tooltip>
|
||||
<Tooltip content={locale === 'de' ? 'Blitzschnelle Ladezeiten für ein optimales Nutzererlebnis.' : 'Lightning-fast loading times for an optimal user experience.'}>
|
||||
<div className="flex items-center gap-2 cursor-help"><Zap className="w-4 h-4 text-neutral-400" /><span>High Performance</span></div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* Mintel Design & Version */}
|
||||
|
||||
70
components/layout/LanguageSwitcher.test.tsx
Normal file
70
components/layout/LanguageSwitcher.test.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import * as React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { LanguageSwitcher } from './LanguageSwitcher';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
usePathname: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock TransitionLink to render standard anchor tag and forward all props
|
||||
vi.mock('@/components/ui/TransitionLink', () => ({
|
||||
TransitionLink: ({ children, href, onClick, ...props }: any) => (
|
||||
<a href={href} onClick={onClick} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock HoverShineOverlay
|
||||
vi.mock('@/components/ui/HoverShineOverlay', () => ({
|
||||
HoverShineOverlay: () => <div data-testid="hover-shine" />,
|
||||
}));
|
||||
|
||||
describe('LanguageSwitcher TDD', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('translates /en/trade-fairs to /de/messen when switching to German', () => {
|
||||
(usePathname as any).mockReturnValue('/en/trade-fairs');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const deLink = screen.getByLabelText('Switch to Deutsch');
|
||||
expect(deLink.getAttribute('href')).toBe('/de/messen');
|
||||
});
|
||||
|
||||
it('translates /de/messen to /en/trade-fairs when switching to English', () => {
|
||||
(usePathname as any).mockReturnValue('/de/messen');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const enLink = screen.getByLabelText('Switch to English');
|
||||
expect(enLink.getAttribute('href')).toBe('/en/trade-fairs');
|
||||
});
|
||||
|
||||
it('translates /en/about-us to /de/ueber-uns when switching to German', () => {
|
||||
vi.mocked(usePathname).mockReturnValue('/en/about-us');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const deLink = screen.getByLabelText('Switch to Deutsch');
|
||||
expect(deLink.getAttribute('href')).toBe('/de/ueber-uns');
|
||||
});
|
||||
|
||||
it('translates /de/ueber-uns to /en/about-us when switching to English', () => {
|
||||
vi.mocked(usePathname).mockReturnValue('/de/ueber-uns');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const enLink = screen.getByLabelText('Switch to English');
|
||||
expect(enLink.getAttribute('href')).toBe('/en/about-us');
|
||||
});
|
||||
|
||||
it('preserves nested paths like blog posts', () => {
|
||||
vi.mocked(usePathname).mockReturnValue('/de/blog/some-post');
|
||||
render(<LanguageSwitcher />);
|
||||
|
||||
const enLink = screen.getByLabelText('Switch to English');
|
||||
expect(enLink.getAttribute('href')).toBe('/en/blog/some-post');
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,41 @@
|
||||
import { TransitionLink } from '@/components/ui/TransitionLink';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||
import enMessages from '@/messages/en.json';
|
||||
import deMessages from '@/messages/de.json';
|
||||
|
||||
const getMessages = (locale: string) => {
|
||||
return locale === 'de' ? deMessages : enMessages;
|
||||
};
|
||||
|
||||
// Synchronous version of mapSlugToFileSlug
|
||||
function syncMapSlugToFileSlug(translatedSlug: string, locale: string): string {
|
||||
const messages = getMessages(locale);
|
||||
const slugs = messages.Slugs;
|
||||
|
||||
if (slugs.pages && translatedSlug in slugs.pages) {
|
||||
return slugs.pages[translatedSlug as keyof typeof slugs.pages];
|
||||
}
|
||||
return translatedSlug;
|
||||
}
|
||||
|
||||
// Synchronous version of mapFileSlugToTranslated
|
||||
function syncMapFileSlugToTranslated(fileSlug: string, locale: string): string {
|
||||
const messages = getMessages(locale);
|
||||
const slugs = messages.Slugs;
|
||||
|
||||
const sections = [slugs.pages];
|
||||
for (const sectionData of sections) {
|
||||
if (sectionData && typeof sectionData === 'object') {
|
||||
for (const [translatedSlug, mappedSlug] of Object.entries(sectionData)) {
|
||||
if (mappedSlug === fileSlug) {
|
||||
return translatedSlug;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fileSlug;
|
||||
}
|
||||
|
||||
interface LanguageSwitcherProps {
|
||||
mobile?: boolean;
|
||||
@@ -14,8 +49,23 @@ export function LanguageSwitcher({ mobile = false, isSolidMode = false }: Langua
|
||||
const currentLocale = pathname.startsWith('/en') ? 'en' : 'de';
|
||||
|
||||
const getSwitchedUrl = (newLocale: string) => {
|
||||
const pathWithoutLocale = pathname.replace(/^\/(en|de)/, '');
|
||||
return `/${newLocale}${pathWithoutLocale === '' ? '' : pathWithoutLocale}`;
|
||||
const segments = pathname.split('/').filter(Boolean);
|
||||
if (segments.length === 0) {
|
||||
return `/${newLocale}`;
|
||||
}
|
||||
|
||||
const currentLoc = segments[0] === 'en' || segments[0] === 'de' ? segments[0] : 'de';
|
||||
const hasLocale = segments[0] === 'en' || segments[0] === 'de';
|
||||
const pathSegments = hasLocale ? segments.slice(1) : segments;
|
||||
|
||||
if (pathSegments.length === 1) {
|
||||
const slug = pathSegments[0];
|
||||
const fileSlug = syncMapSlugToFileSlug(slug, currentLoc);
|
||||
const translatedSlug = syncMapFileSlugToTranslated(fileSlug, newLocale);
|
||||
return `/${newLocale}/${translatedSlug}`;
|
||||
}
|
||||
|
||||
return `/${newLocale}/${pathSegments.join('/')}`;
|
||||
};
|
||||
|
||||
const locales = [
|
||||
|
||||
48
components/ui/AnimatedCounter.tsx
Normal file
48
components/ui/AnimatedCounter.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { motion, useMotionValue, useTransform, animate, useInView } from 'framer-motion';
|
||||
|
||||
interface AnimatedCounterProps {
|
||||
value: number;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
suffix?: string;
|
||||
prefix?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AnimatedCounter({
|
||||
value,
|
||||
duration = 2,
|
||||
delay = 0,
|
||||
suffix = '',
|
||||
prefix = '',
|
||||
className = '',
|
||||
}: AnimatedCounterProps) {
|
||||
const count = useMotionValue(0);
|
||||
const rounded = useTransform(count, (latest) => {
|
||||
// Format with thousands separator
|
||||
return new Intl.NumberFormat('de-DE').format(Math.round(latest));
|
||||
});
|
||||
|
||||
const ref = React.useRef(null);
|
||||
const inView = useInView(ref, { once: true, margin: "-100px" });
|
||||
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
const controls = animate(count, value, {
|
||||
duration: duration,
|
||||
delay: delay,
|
||||
ease: 'easeOut',
|
||||
});
|
||||
return controls.stop;
|
||||
}
|
||||
}, [inView, value, duration, delay, count]);
|
||||
|
||||
return (
|
||||
<motion.span ref={ref} className={className}>
|
||||
{prefix}<motion.span>{rounded}</motion.span>{suffix}
|
||||
</motion.span>
|
||||
);
|
||||
}
|
||||
19
components/ui/Tooltip.tsx
Normal file
19
components/ui/Tooltip.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
interface TooltipProps {
|
||||
children: ReactNode;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function Tooltip({ children, content }: TooltipProps) {
|
||||
return (
|
||||
<div className="group relative flex items-center justify-center">
|
||||
{children}
|
||||
<div className="absolute bottom-full mb-3 invisible opacity-0 translate-y-2 group-hover:visible group-hover:opacity-100 group-hover:translate-y-0 w-max max-w-[250px] px-4 py-2.5 text-[11px] md:text-xs text-white bg-neutral-900/95 backdrop-blur-md rounded-xl shadow-2xl border border-white/10 transition-all duration-300 pointer-events-none z-50 text-center leading-relaxed font-normal normal-case tracking-normal">
|
||||
{content}
|
||||
{/* Triangle Arrow */}
|
||||
<div className="absolute left-1/2 top-full -translate-x-1/2 border-4 border-transparent border-t-neutral-900/95" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,5 +7,6 @@ export * from './Card';
|
||||
export * from './Badge';
|
||||
export * from './Input';
|
||||
export * from './Textarea';
|
||||
export * from './AnimatedCounter';
|
||||
export * from './Label';
|
||||
export * from './Callout';
|
||||
|
||||
@@ -29,6 +29,8 @@ layout: "fullBleed"
|
||||
Mit unserem spezialisierten Gerätepark beherrschen wir die unterschiedlichsten Verfahren. Beim **Horizontalspülbohrverfahren (HDD)** steuern wir den Bohrkopf punktgenau unter der Erde hindurch. Wir realisieren dabei Bohrlängen von bis zu 250 Metern am Stück und können Schutzrohre mit einem Durchmesser von bis zu 400 mm einziehen.
|
||||
|
||||
Für kürzere Distanzen, wie beispielsweise schnelle und schonende Hausanschlüsse, setzen wir unsere **Erdraketen** (Bodenverdrängungsverfahren) ein. Hierbei erreichen wir Durchörterungslängen von bis zu 15 Metern für Rohre bis 160 mm Durchmesser – ideal um Vorgärten und Einfahrten komplett unversehrt zu lassen.
|
||||
|
||||
<DeepDrillAnimation />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
|
||||
@@ -143,5 +143,7 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
|
||||
]}
|
||||
/>
|
||||
|
||||
<ScaleOfImpact />
|
||||
|
||||
<HomeReferencesSlider />
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ layout: "fullBleed"
|
||||
Durch unsere langjährige Erfahrung erkennen wir Problemstellungen frühzeitig und erarbeiten für unsere Auftraggeber im Vorfeld sichere und zufriedenstellende Lösungen. Die E-TIB GmbH hat sich voll und ganz auf den Kabelleitungstiefbau spezialisiert.
|
||||
|
||||
Wir bieten Ihnen das komplette Leistungsspektrum zur Errichtung von Kabeltrassen aus einer Hand – mit eigenem, hochmodernem Maschinenpark und festangestelltem Fachpersonal aus der Region. In direkter Zusammenarbeit mit unserem Partnerbüro, der E-TIB Ingenieurgesellschaft mbH, können wir zudem frühzeitig Planungsfehler ausschließen und selbst die komplexesten Dokumentationsanforderungen souverän erfüllen.
|
||||
|
||||
<DataGridPulse />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
|
||||
@@ -24,73 +24,73 @@ layout: "fullBleed"
|
||||
{
|
||||
id: "region",
|
||||
title: "Spannende Bauvorhaben",
|
||||
description: "Einsatz bei interessanten regionalen und überregionalen Projekten.",
|
||||
description: "Einsatz bei regionalen und überregionalen Bauvorhaben.",
|
||||
icon: "region"
|
||||
},
|
||||
{
|
||||
id: "contract",
|
||||
title: "Unbefristeter Vertrag",
|
||||
description: "Sicherer Arbeitsplatz durch einen unbefristeten Arbeitsvertrag in Vollzeit.",
|
||||
description: "Sicherer, unbefristeter Arbeitsvertrag in Vollzeit.",
|
||||
icon: "contract"
|
||||
},
|
||||
{
|
||||
id: "money",
|
||||
title: "Faire Vergütung",
|
||||
description: "Leistungsbezogene, überdurchschnittliche und faire Bezahlung.",
|
||||
description: "Leistungsbezogene und faire Vergütung für Ihre Arbeit.",
|
||||
icon: "money"
|
||||
},
|
||||
{
|
||||
id: "allowance",
|
||||
title: "Tägliche Auslöse",
|
||||
description: "Wir zahlen eine steuerfreie Auslöse von bis zu 30,00 EUR pro Arbeitstag.",
|
||||
description: "Auslöse von bis zu 30,00 EUR pro Arbeitstag.",
|
||||
icon: "allowance"
|
||||
},
|
||||
{
|
||||
id: "vacation",
|
||||
title: "31 Tage Urlaub",
|
||||
description: "31 Tage Jahresurlaub sowie zusätzliches Urlaubsgeld zur Erholung.",
|
||||
description: "31 Tage Jahresurlaub sowie zusätzliches Urlaubsgeld.",
|
||||
icon: "vacation"
|
||||
},
|
||||
{
|
||||
id: "clothes",
|
||||
title: "Premium Arbeitskleidung",
|
||||
description: "Hochwertige Engelbert-Strauss Arbeitskleidung wird komplett gestellt.",
|
||||
title: "Marken-Arbeitskleidung",
|
||||
description: "Bereitstellung von hochwertiger Engelbert-Strauss Arbeitskleidung.",
|
||||
icon: "clothes"
|
||||
},
|
||||
{
|
||||
id: "card",
|
||||
title: "Zusatzzahlungen",
|
||||
description: "Monatliche Boni über eine persönliche MasterCard.",
|
||||
description: "Monatliche Zusatzzahlungen über eine persönliche MasterCard.",
|
||||
icon: "card"
|
||||
},
|
||||
{
|
||||
id: "retirement",
|
||||
title: "Altersvorsorge",
|
||||
description: "Attraktive Zuzahlung im Rahmen der betrieblichen Altersvorsorge.",
|
||||
description: "Zuzahlung im Rahmen der betrieblichen Altersvorsorge.",
|
||||
icon: "retirement"
|
||||
},
|
||||
{
|
||||
id: "education",
|
||||
title: "Weiterbildung",
|
||||
description: "Individuelle Entwicklungs- und Aufstiegsmöglichkeiten im Unternehmen.",
|
||||
description: "Individuelle Weiterbildungs- und Entwicklungsmöglichkeiten.",
|
||||
icon: "education"
|
||||
},
|
||||
{
|
||||
id: "car",
|
||||
title: "Firmenfahrzeug",
|
||||
description: "Stellung eines Fahrzeugs zur betrieblichen Nutzung und für Baustellen.",
|
||||
description: "Stellung eines Fahrzeugs zur betrieblichen Nutzung und Fahrten zur Baustelle.",
|
||||
icon: "car"
|
||||
},
|
||||
{
|
||||
id: "team",
|
||||
title: "Intensive Einarbeitung",
|
||||
description: "Professionelles Onboarding in einem hilfsbereiten und engagierten Team.",
|
||||
description: "Entsprechende Einarbeitung in einem engagierten Team.",
|
||||
icon: "team"
|
||||
},
|
||||
{
|
||||
id: "family",
|
||||
title: "Familiäres Klima",
|
||||
description: "Starker Zusammenhalt durch regelmäßige betrieblich organisierte Events.",
|
||||
description: "Familiäres Betriebsklima mit betrieblich organisierten Veranstaltungen.",
|
||||
icon: "family"
|
||||
}
|
||||
]}
|
||||
|
||||
@@ -52,9 +52,9 @@ layout: "fullBleed"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CompanyTimeline />
|
||||
<InteractiveGermanyMap isHero={true} badge="Bundesweite Präsenz" title="Infrastruktur für ein ganzes Land" description="Seit 2023 haben wir in über 100 Projekten quer durch Deutschland bewiesen, dass wir Infrastrukturprojekte jeder Größenordnung verlässlich umsetzen." />
|
||||
|
||||
<InteractiveGermanyMap />
|
||||
<CompanyTimeline />
|
||||
|
||||
<div id="struktur">
|
||||
<HomeSubCompanyTiles
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Trenchless Line Laying"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Precise horizontal directional drilling and earth rockets in all soil classes for surface-friendly and trenchless line laying."
|
||||
excerpt: "Precise horizontal directional drilling and earth rockets in all soil classes for surface-friendly and efficient line laying."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,15 +12,32 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
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.
|
||||
</blockquote>
|
||||
|
||||
## Maximum Efficiency, Minimal Intervention
|
||||
|
||||
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.
|
||||
|
||||
## HDD Boreholes and 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.
|
||||
|
||||
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.
|
||||
|
||||
<DeepDrillAnimation />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Procedures"
|
||||
title="Efficient & Environmentally Friendly"
|
||||
title="Our Drilling Technology in Detail"
|
||||
descriptionParagraphs={[
|
||||
"If classic trench construction is out of the question for reasons of space, environment, or cost, we rely on **state-of-the-art trenchless techniques**.",
|
||||
"This not only protects nature but also significantly saves on restoration costs for complex surfaces."
|
||||
"We select the exactly matching drilling method for each soil class and local condition to guarantee maximum economic efficiency."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
@@ -32,29 +49,33 @@ layout: "fullBleed"
|
||||
[
|
||||
"Max. drilling length: 250 m",
|
||||
"Max. diameter: 400mm pipe",
|
||||
"Complex crossing of railways, highways & water bodies"
|
||||
"Precision control and steerability",
|
||||
"Crossing of railways, highways & water bodies"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "erdrakete",
|
||||
title: "Earth Rocket",
|
||||
title: "Soil Displacement (Earth Rocket)",
|
||||
icon: "hdd",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Max. crossing length: 15 m",
|
||||
"Max. diameter: 160mm pipe",
|
||||
"Fast house connections without trenches"
|
||||
"Fast house connections without trenches",
|
||||
"No surface opening necessary"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Completed Drilling Projects" description="Successful HDD drilling and trenchless cable laying under the most complex conditions." />
|
||||
|
||||
<CallToAction
|
||||
title="Looking for a trenchless solution?"
|
||||
text="We would be happy to advise you on the possibilities of horizontal directional drilling for your project."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Cable Civil Engineering in Telecommunications"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Future-proof infrastructure through competent cable civil engineering for fiber optic and telecommunications networks."
|
||||
excerpt: "Future-proof infrastructure through competent cable civil engineering for fiber optic and telecommunications networks (FTTX)."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,15 +12,30 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC01129.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
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.
|
||||
</blockquote>
|
||||
|
||||
## The Backbone of Digitalization
|
||||
|
||||
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.
|
||||
|
||||
## FTTX Expansion from a Single Source
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Service Portfolio"
|
||||
title="Backbone of Digitalization"
|
||||
title="Complete Fiber Optic Expansion"
|
||||
descriptionParagraphs={[
|
||||
"Fast internet and state-of-the-art telecommunications networks require a **smooth and professional network expansion**.",
|
||||
"With state-of-the-art machinery and specialized assembly teams, E-TIB ensures the highest quality in fiber optic expansion."
|
||||
"From the empty conduit route to the finished fiber optic assembly - we deliver turnkey telecommunication infrastructure at the highest level."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
@@ -31,8 +46,9 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Production of cable and pipe trenches",
|
||||
"Production of empty conduit routes",
|
||||
"Setting up multi-function enclosures"
|
||||
"Production of complete empty conduit routes",
|
||||
"Setting up multi-function enclosures (MFG)",
|
||||
"Network node connection"
|
||||
]
|
||||
]
|
||||
},
|
||||
@@ -44,17 +60,20 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Pulling of TK/FO cables",
|
||||
"Blowing in of fiber optic cables",
|
||||
"Cable assembly TK/FO"
|
||||
"Pneumatic blowing in of fiber optic cables",
|
||||
"Cable assembly TK/FO",
|
||||
"Splicing and measuring work"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Completed Broadband Projects" description="We have already successfully realized hundreds of kilometers of FTTX networks. Discover our references." />
|
||||
|
||||
<CallToAction
|
||||
title="Are you planning an FTTX expansion?"
|
||||
text="We are happy to advise you on all aspects of FTTX and fiber optic expansion."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -139,4 +139,6 @@ description: "E-TIB GmbH is your reliable partner for complex cable routes, hori
|
||||
]}
|
||||
/>
|
||||
|
||||
<ScaleOfImpact />
|
||||
|
||||
<HomeReferencesSlider />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Cable Civil Engineering in Medium and Low Voltage"
|
||||
title: "Cable Civil Engineering"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Professional cable civil engineering for medium and low voltage networks: From the construction of cable routes to cable assemblies and street lighting."
|
||||
excerpt: "Professional cable civil engineering for medium and low voltage networks. We guarantee maximum safety standards and seamless documentation for your project."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,15 +12,34 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
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.
|
||||
</blockquote>
|
||||
|
||||
## Quality instead of a Cost Trap
|
||||
|
||||
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.
|
||||
|
||||
Particularly for funded infrastructure projects, retrieving the funding is extremely problematic without absolutely seamless, standard-compliant documentation.
|
||||
|
||||
## Everything from a Single Source
|
||||
|
||||
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 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 />
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Service Portfolio"
|
||||
title="Everything for your network"
|
||||
title="Everything for Your Network"
|
||||
descriptionParagraphs={[
|
||||
"Our services cover the **complete spectrum** for the construction of modern cable routes and energy infrastructure.",
|
||||
"We offer the highest precision, modern machines, and certified experts for every project."
|
||||
"We offer the highest precision, modern machines and certified experts for every civil engineering project - from cable laying to transformer stations."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
@@ -31,31 +50,34 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Production of cable and pipe trenches",
|
||||
"Cable laying from 0.4 kV - 110 kV",
|
||||
"Cable assembly 0.4 kV - 30 kV"
|
||||
"Cable laying from 0.4 kV to 110 kV",
|
||||
"Cable assembly from 0.4 kV to 30 kV",
|
||||
"Cable plowing works for fast laying"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "anlagen",
|
||||
title: "Systems & Lighting",
|
||||
title: "Systems & Infrastructure",
|
||||
icon: "energy",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Construction of transformer stations",
|
||||
"Establishment of grounding systems",
|
||||
"Establishment of complex grounding systems",
|
||||
"Erection of street lighting",
|
||||
"Switching operations"
|
||||
"Certified switching operations"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Completed Civil Engineering Projects" description="We have laid hundreds of kilometers of cables. Take a look at our reference projects." />
|
||||
|
||||
<CallToAction
|
||||
title="Do you have a specific project?"
|
||||
text="We would be happy to advise you on all questions regarding cable civil engineering and line laying."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -100,6 +100,6 @@ layout: "fullBleed"
|
||||
title="Speculative Application (m/f/d)"
|
||||
description="Can you imagine taking on a long-term challenge in our company but can't find a suitable job offer? Apply speculatively."
|
||||
ctaLabel="To the contact form"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
theme="dark"
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@ layout: "fullBleed"
|
||||
description="As a full-service partner in the field of cable civil engineering, we cover the entire spectrum of modern infrastructure projects."
|
||||
videoUrl="/assets/videos/web/hero-bohrung.mp4"
|
||||
linkText="Get in Touch"
|
||||
linkHref="/en/kontakt"
|
||||
linkHref="/en/contact"
|
||||
secondaryCtaLabel="View References"
|
||||
secondaryCtaHref="/en/#referenzen"
|
||||
/>
|
||||
@@ -20,7 +20,7 @@ layout: "fullBleed"
|
||||
badge="Core Competencies"
|
||||
title="Precision & Performance"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
items={[
|
||||
{
|
||||
title: "Cable Construction",
|
||||
|
||||
@@ -11,7 +11,7 @@ layout: "fullBleed"
|
||||
description="Get to know the E-TIB Group at the leading industry trade fairs."
|
||||
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
|
||||
linkText="Get in Touch"
|
||||
linkHref="/en/kontakt"
|
||||
linkHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div id="termine">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Planning and Consulting"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Professional planning and consulting in the field of energy and telecommunications networks: route planning, approval procedures, and project review."
|
||||
excerpt: "Professional planning and consulting in the field of energy and telecommunications networks: route planning, approval procedures and project control from a single source."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,51 +12,68 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DJI_0048.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
We offer our clients everything from a single source - from the rough planning of your project idea and cable civil engineering to the construction of your renewable energy project.
|
||||
</blockquote>
|
||||
|
||||
## Foundation for Project Success
|
||||
|
||||
Solid planning is the crucial foundation for every successful infrastructure project. Projects often fail or are massively delayed because the complexity of civil engineering planning and approval procedures was underestimated. Through our combined competence, you avoid expensive planning errors and frictional losses at the interfaces.
|
||||
|
||||
Both for the highly topical broadband expansion and for the demanding grid connection of solar or wind projects, absolutely precise and high-quality cable route planning is required. To do this, we inspect the route on site, analyze all potential construction conflicts at an early stage and ensure that all necessary applications for the laying are submitted completely and on time to the authorities.
|
||||
|
||||
## Strong Engineering Competence
|
||||
|
||||
In close cooperation with our specialized partner engineering firm, **E-TIB Ingenieurgesellschaft mbH**, we accompany you through all service phases. Whether structural planning for route optimization, complex approval planning or detailed execution planning - we steer your project from the first feasibility study to successful construction supervision and acceptance.
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Engineering Services"
|
||||
title="Foundation for Success"
|
||||
title="Our Planning Services"
|
||||
descriptionParagraphs={[
|
||||
"Solid planning is the foundation of every successful construction project. Avoid expensive planning errors and benefit from our comprehensive expertise.",
|
||||
"Our partner engineering firm **E-TIB Ingenieurgesellschaft mbH** supports us in the smooth implementation of complex network expansion projects."
|
||||
"Minimize risks and interfaces. We combine planning know-how with practical civil engineering expertise."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
id: "planung",
|
||||
title: "Planning",
|
||||
title: "Route & Approval Planning",
|
||||
icon: "planning",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Structural planning",
|
||||
"Approval planning",
|
||||
"Execution planning",
|
||||
"Structural planning (route optimization)",
|
||||
"Approval planning & authority management",
|
||||
"Detailed execution planning",
|
||||
"Consent planning"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "beratung",
|
||||
title: "Consulting & Management",
|
||||
title: "Control & Supervision",
|
||||
icon: "planning",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Tendering & awarding",
|
||||
"Construction supervision",
|
||||
"Project review",
|
||||
"Feasibility studies"
|
||||
"Tendering & awarding (AVA)",
|
||||
"Specialist construction supervision",
|
||||
"Project review & controlling",
|
||||
"Sound feasibility studies"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Planning in Practice" description="Successfully planned and accompanied infrastructure projects of our group." />
|
||||
|
||||
<CallToAction
|
||||
title="Do you need planning security?"
|
||||
text="Let's talk about your project. We are at your side with help and advice."
|
||||
text="Let's talk about your project. We are at your side with our engineering knowledge."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -86,5 +86,5 @@ layout: "fullBleed"
|
||||
title="Become part of our team"
|
||||
text="We are always looking for motivated talents for exciting infrastructure projects."
|
||||
buttonText="View open positions"
|
||||
buttonLink="/en/karriere"
|
||||
buttonLink="/en/career"
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,7 @@ layout: "fullBleed"
|
||||
description="Since 2015, we have been growing continuously to meet the demands of the energy transition and digitalization. As a corporate group, we pool specialized expertise under one roof."
|
||||
videoUrl="/assets/videos/web/hero-bahnkreuzung.mp4"
|
||||
linkText="Open Positions"
|
||||
linkHref="/en/karriere"
|
||||
linkHref="/en/career"
|
||||
secondaryCtaLabel="Discover Company"
|
||||
secondaryCtaHref="#structure"
|
||||
/>
|
||||
@@ -52,50 +52,9 @@ layout: "fullBleed"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CompanyTimeline
|
||||
badge="Our History"
|
||||
title="Milestones of Development"
|
||||
milestones={[
|
||||
{
|
||||
date: '16.12.2015',
|
||||
year: '2015',
|
||||
title: 'Foundation of E-TIB GmbH',
|
||||
desc: 'Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling.',
|
||||
iconName: 'Building2'
|
||||
},
|
||||
{
|
||||
date: '04.02.2019',
|
||||
year: '2019',
|
||||
title: 'Foundation of E-TIB Ingenieurgesellschaft',
|
||||
desc: 'Approval and execution planning, complex crossings, and grid connection planning.',
|
||||
iconName: 'Compass'
|
||||
},
|
||||
{
|
||||
date: '14.11.2019',
|
||||
year: '2019',
|
||||
title: 'Foundation of E-TIB Verwaltung GmbH',
|
||||
desc: 'Central services, acquisition, leasing, and management of real estate and machinery.',
|
||||
iconName: 'Layers'
|
||||
},
|
||||
{
|
||||
date: '21.10.2025',
|
||||
year: '2025',
|
||||
title: 'Foundation of E-TIB Bohrtechnik GmbH',
|
||||
desc: 'Specialization in precise horizontal directional drilling in all soil classes.',
|
||||
iconName: 'ArrowDownToLine'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<InteractiveGermanyMap isHero={true} badge="Nationwide Presence" title="Infrastructure for an entire country" description="Since 2023, we have proven in over 100 projects across Germany that we reliably implement infrastructure projects of any scale." />
|
||||
|
||||
<InteractiveGermanyMap
|
||||
badge="Operating Areas"
|
||||
title={<>Nationwide<br/>at your service.</>}
|
||||
description="From our strategic locations in Guben and Bülstedt, we control and realize complex infrastructure projects across the entire federal territory."
|
||||
stats={[
|
||||
{ value: '100', suffix: '%', label: 'Nationwide Reach' },
|
||||
{ value: '2', suffix: '+', label: 'Operating Locations' }
|
||||
]}
|
||||
/>
|
||||
<CompanyTimeline />
|
||||
|
||||
<div id="structure">
|
||||
<HomeSubCompanyTiles
|
||||
@@ -136,5 +95,5 @@ layout: "fullBleed"
|
||||
title="Ready for Your Project?"
|
||||
description="We are always looking for new challenges and strong partners. Contact us for a non-binding consultation on your project."
|
||||
ctaLabel="Contact Us Now"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Surveying and Documentation"
|
||||
date: "2024-03-20"
|
||||
excerpt: "Precise surveying, staking out, and digital documentation of line routes to ensure the highest safety and quality standards."
|
||||
excerpt: "Precise GIS surveying, staking out and digital documentation of line routes to ensure the highest construction and safety standards."
|
||||
layout: "fullBleed"
|
||||
---
|
||||
|
||||
@@ -12,27 +12,45 @@ layout: "fullBleed"
|
||||
backgroundImage={{ url: '/assets/photos/DSC08653.JPG' }}
|
||||
alignment="center"
|
||||
ctaLabel="Request a project"
|
||||
ctaHref="/en/kontakt"
|
||||
ctaHref="/en/contact"
|
||||
/>
|
||||
|
||||
<div className="container px-4 max-w-7xl mx-auto py-16">
|
||||
<blockquote>
|
||||
We work with highly precise GPS equipment for the seamless documentation of our clients' projects. For this purpose, we independently develop app integrations to automate surveying.
|
||||
</blockquote>
|
||||
|
||||
## Highest Precision Protects Against Surprises
|
||||
|
||||
In today's times, legal documentation requirements and strict safety standards are growing rapidly. Many conventional civil engineering companies are hardly able to cleanly meet these highly complex requirements - especially for funded broadband expansion. Missing or incorrect documentation inevitably leads to massive problems during construction acceptance and project financing.
|
||||
|
||||
We identified this bottleneck early on and proactively specialized in the highly precise surveying and digital documentation of cable routes.
|
||||
|
||||
## Digital Recording at the State of the Art
|
||||
|
||||
With state-of-the-art GPS technology and self-developed digital app integrations, we ensure highly automated, seamless and absolutely legally compliant recording of all laid lines and systems.
|
||||
|
||||
In addition to classic GPS staking and surveying, we create complete geodatabases for our clients for transparent project accounting and offer seamless 360° photo and video recording of the construction field. In this way, you keep full track of your underground infrastructure at all times.
|
||||
</div>
|
||||
|
||||
<ServiceDetailGrid
|
||||
badge="Precision"
|
||||
title="Highest Safety Standards"
|
||||
title="Digital Safety Standards"
|
||||
descriptionParagraphs={[
|
||||
"Increasing documentation requirements and strict safety standards demand **the highest precision on the construction site**.",
|
||||
"With state-of-the-art technology, we ensure seamless and legally compliant recording of all lines. This is the best protection against unpleasant surprises during construction acceptance."
|
||||
"We do not only deliver a clean route, but also the perfect data. Legally compliant, digital and precise to the centimeter."
|
||||
]}
|
||||
panels={[
|
||||
{
|
||||
id: "vermessung",
|
||||
title: "Geodesy",
|
||||
title: "Classic Geodesy",
|
||||
icon: "survey",
|
||||
fullWidth: false,
|
||||
lists: [
|
||||
[
|
||||
"Staking out of line routes",
|
||||
"Surveying of line routes",
|
||||
"GPS-supported terrain recording"
|
||||
"Staking out of line routes with centimeter precision",
|
||||
"Surveying of existing routes and new lines",
|
||||
"Highly precise GPS-supported terrain recording",
|
||||
"Automated app-based measuring procedures"
|
||||
]
|
||||
]
|
||||
},
|
||||
@@ -44,17 +62,20 @@ layout: "fullBleed"
|
||||
lists: [
|
||||
[
|
||||
"Creation of databases for project accounting",
|
||||
"360° photo/video recording of the construction site",
|
||||
"Integration into geographic information systems (GIS)"
|
||||
"360° photo and video recording of the construction field",
|
||||
"Integration and processing for Geographic Information Systems (GIS)",
|
||||
"Audit-proof inventory plans"
|
||||
]
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
<HomeReferencesSlider badge="Case Studies" title="Documented Projects" description="We have precisely surveyed and digitally documented hundreds of kilometers of routes." />
|
||||
|
||||
<CallToAction
|
||||
title="Are you facing high documentation requirements?"
|
||||
text="Contact us for professional surveying and documentation services."
|
||||
text="Contact us for professional surveying and GIS services."
|
||||
buttonText="Contact us"
|
||||
buttonLink="/en/kontakt"
|
||||
buttonLink="/en/contact"
|
||||
/>
|
||||
|
||||
@@ -40,6 +40,8 @@ services:
|
||||
NPM_TOKEN: ${NPM_TOKEN:-}
|
||||
CI: "true"
|
||||
WATCHPACK_POLLING: "true"
|
||||
ports:
|
||||
- "3001:3001"
|
||||
volumes:
|
||||
- .:/app
|
||||
- etib_node_modules:/app/node_modules
|
||||
|
||||
854
lib/map-data.ts
854
lib/map-data.ts
@@ -1,7 +1,7 @@
|
||||
export interface Location {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'hq' | 'branch' | 'project';
|
||||
type: 'hq' | 'branch' | 'project' | 'minor_node';
|
||||
x: number;
|
||||
y: number;
|
||||
description: string;
|
||||
@@ -16,7 +16,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Guben (Hauptsitz)',
|
||||
type: 'hq',
|
||||
x: 85,
|
||||
y: 48,
|
||||
y: 41.1,
|
||||
description: 'E-TIB GmbH Holding & Bohrtechnik GmbH',
|
||||
details: ['Zentrale Steuerung', 'Kabelleitungstiefbau', 'Maschinenpark'],
|
||||
},
|
||||
@@ -25,7 +25,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Bülstedt (Ingenieurgesellschaft)',
|
||||
type: 'branch',
|
||||
x: 37,
|
||||
y: 25,
|
||||
y: 25.33,
|
||||
description: 'E-TIB Ingenieurgesellschaft mbH',
|
||||
details: ['Planung & Projektierung', 'Vermessung', 'Dokumentation'],
|
||||
},
|
||||
@@ -34,7 +34,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Spreewald (FTTC)',
|
||||
type: 'project',
|
||||
x: 81,
|
||||
y: 42,
|
||||
y: 36.99,
|
||||
description: 'Breitbandausbau FTTC',
|
||||
href: '/referenzen/spreewald',
|
||||
},
|
||||
@@ -43,7 +43,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Bomlitz (FTTH)',
|
||||
type: 'project',
|
||||
x: 43,
|
||||
y: 30,
|
||||
y: 28.76,
|
||||
description: '88km Leitungstrasse (Tiefbau/HDD)',
|
||||
href: '/referenzen/bomlitz',
|
||||
},
|
||||
@@ -52,7 +52,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Forst (Kabeltrasse)',
|
||||
type: 'project',
|
||||
x: 87,
|
||||
y: 52,
|
||||
y: 43.85,
|
||||
description: 'Neubau MS-Kabeltrasse',
|
||||
href: '/referenzen/forst',
|
||||
},
|
||||
@@ -61,7 +61,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Eisenhüttenstadt (Kabel)',
|
||||
type: 'project',
|
||||
x: 86,
|
||||
y: 44,
|
||||
y: 38.36,
|
||||
description: 'Kabelverbindung U30 - T10',
|
||||
href: '/referenzen/eisenhuettenstadt',
|
||||
},
|
||||
@@ -70,7 +70,7 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Görne (PV-Anlage)',
|
||||
type: 'project',
|
||||
x: 65,
|
||||
y: 35,
|
||||
y: 32.19,
|
||||
description: 'Einspeisung PV-Anlage (MS-Kabel)',
|
||||
href: '/referenzen/goerne',
|
||||
},
|
||||
@@ -79,8 +79,844 @@ export const defaultLocations: Location[] = [
|
||||
name: 'Börde (FTTH-Ausbau)',
|
||||
type: 'project',
|
||||
x: 55,
|
||||
y: 38,
|
||||
y: 34.24,
|
||||
description: '109km Leitungstrasse für Breitband',
|
||||
href: '/referenzen/boerde',
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export const minorLocations: Location[] = [
|
||||
{
|
||||
id: 'breddin-16845',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 63.88,
|
||||
y: 29.36,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'bad-liebenwerda-04924',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 73.76,
|
||||
y: 46.48,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'hünfelden-65597',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 29.35,
|
||||
y: 61.68,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'zachow-19376',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 68.62,
|
||||
y: 34.44,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'schnarup-thumby-24891',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 41.96,
|
||||
y: 7.31,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'bartow-17089',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 73.39,
|
||||
y: 17.66,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'regesbostel-21649',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 42.09,
|
||||
y: 23.17,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'bomlitz-29699',
|
||||
name: 'Breitbandausbau / FTTx, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 42.22,
|
||||
y: 29.09,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'berzhahn-56459',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 28.1,
|
||||
y: 58.9,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'oschersleben-39387',
|
||||
name: 'Breitbandausbau / FTTx, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 55.74,
|
||||
y: 40.14,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'jessen-06917',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 70.21,
|
||||
y: 42.8,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dettmannsdorf-18334',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 66.38,
|
||||
y: 14.04,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'werneck-97440',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Main-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 45.73,
|
||||
y: 65.8,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'badendorf-23619',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 49.91,
|
||||
y: 17.02,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'hordorf-39387',
|
||||
name: 'Breitbandausbau / FTTx, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 55.17,
|
||||
y: 40.57,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'neißemünde-15898',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.95,
|
||||
y: 39.9,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'wiesenhagen-14959',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 72.47,
|
||||
y: 38.31,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'gersdorf-09355',
|
||||
name: 'Netzanbindung Windenergie, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 67.92,
|
||||
y: 56.13,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nauen-14641',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 68.95,
|
||||
y: 32.81,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'eggersdorf-15374',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.59,
|
||||
y: 40.82,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'guben-03172',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.55,
|
||||
y: 41.12,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'beratzhausen-93176',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 60.46,
|
||||
y: 76.77,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'felm-24161',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 45.58,
|
||||
y: 10.19,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'neukieritzsch-04575',
|
||||
name: 'Breitbandausbau / FTTx, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 65.5,
|
||||
y: 51.2,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'oberbarnim-15377',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 79.52,
|
||||
y: 33.09,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'berne-27804',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 32.1,
|
||||
y: 25.85,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dossow-16909',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 66.44,
|
||||
y: 26.41,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'krempe-25361',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.7,
|
||||
y: 17.42,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'lübars-39291',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 73.45,
|
||||
y: 32.78,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'taarstedt-24893',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 42.4,
|
||||
y: 8.17,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'rom-19372',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 61.55,
|
||||
y: 22.35,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'bosbüll-25899',
|
||||
name: 'PV-Infrastrukturprojekt, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 35.36,
|
||||
y: 5,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'schwarzhofen-92447',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 64.76,
|
||||
y: 73.46,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'göhlsdorf-14797',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 68.78,
|
||||
y: 35.98,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'zehdenick-16792',
|
||||
name: 'Trassenbau Hochspannung, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 73.35,
|
||||
y: 28.02,
|
||||
description: 'power',
|
||||
},
|
||||
{
|
||||
id: 'osterburg-39606',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.63,
|
||||
y: 30.48,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'holzheim-89438',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 49.58,
|
||||
y: 84.44,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'adelschlag-85111',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 55.23,
|
||||
y: 80.23,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'hoyerswerda-02977',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 80.8,
|
||||
y: 47.74,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'niederhummel-85416',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 60.98,
|
||||
y: 85.4,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'löffingen-79843',
|
||||
name: 'PV-Infrastrukturprojekt, Region Schwarzwald / Bodensee',
|
||||
type: 'minor_node',
|
||||
x: 31.06,
|
||||
y: 92.31,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'holste-27729',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 35.35,
|
||||
y: 23.24,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'lasbek-23847',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 48.1,
|
||||
y: 18.71,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'bernau-bei-berlin-16321',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 75.39,
|
||||
y: 31.83,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'gorgast-15328',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 83.44,
|
||||
y: 33.39,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'neustadt-dosse-16845',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 65.82,
|
||||
y: 29.83,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'halle-06132',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 61.88,
|
||||
y: 47.76,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'letschin-15324',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 82.02,
|
||||
y: 32.11,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'sallgast-03238',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 77.65,
|
||||
y: 45.69,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'rodleben-dessau-roßlau-06861',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 63.69,
|
||||
y: 41.69,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'gerbstedt-06347',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 58.88,
|
||||
y: 45.36,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'elsterwerda-04910',
|
||||
name: 'Breitbandausbau / FTTx, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 74.88,
|
||||
y: 47.26,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'magdeburg-39106',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.01,
|
||||
y: 38.7,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'staßfurt-39443',
|
||||
name: 'BESS-Anbindung, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 58.97,
|
||||
y: 41.85,
|
||||
description: 'battery',
|
||||
},
|
||||
{
|
||||
id: 'alsleben-06425',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.15,
|
||||
y: 44.01,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nienbüttel-25596',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.06,
|
||||
y: 14.97,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'werneuchen-16356',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 76.31,
|
||||
y: 32.58,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'stüdenitz-schönermark-16845',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 64.43,
|
||||
y: 29.18,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'gumtow-16866',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 64.12,
|
||||
y: 28.2,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nöbdenitz-04626',
|
||||
name: 'Breitbandausbau / FTTx, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 64.59,
|
||||
y: 54.41,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'kröpelin-18236',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 60.24,
|
||||
y: 14.56,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'laer-48366',
|
||||
name: 'Netzanbindung Windenergie, Region Münsterland',
|
||||
type: 'minor_node',
|
||||
x: 22.68,
|
||||
y: 39.78,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'ostbevern-48346',
|
||||
name: 'Netzanbindung Windenergie, Region Münsterland',
|
||||
type: 'minor_node',
|
||||
x: 26.64,
|
||||
y: 39.84,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'schwarmstedt-29690',
|
||||
name: 'Breitbandausbau / FTTx, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 41.89,
|
||||
y: 31.93,
|
||||
description: 'fiber',
|
||||
},
|
||||
{
|
||||
id: 'prötzel-15345',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 78.84,
|
||||
y: 32.5,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'blindheim-89434',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 50.23,
|
||||
y: 82.72,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'großräschen-01983',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 78.82,
|
||||
y: 45.64,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'wolkenstein-09429',
|
||||
name: 'Netzanbindung Windenergie, Region Leipzig / Dresden',
|
||||
type: 'minor_node',
|
||||
x: 71.1,
|
||||
y: 57.44,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'fronhausen-35112',
|
||||
name: 'Netzanbindung Windenergie, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 34.1,
|
||||
y: 56.83,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dammfleth-25554',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 39.62,
|
||||
y: 16.66,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'langwedel-24631',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 44.46,
|
||||
y: 12.6,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'jerchel-14715',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 56.22,
|
||||
y: 34.63,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'beeskow-15848',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 80.9,
|
||||
y: 38.43,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'perleberg-19348',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 60.84,
|
||||
y: 26.25,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'gilching-82205',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 55.84,
|
||||
y: 89.45,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'bodenwöhr-92439',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 64.41,
|
||||
y: 74.55,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'görne-14728',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 66.34,
|
||||
y: 31.66,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'agethorst-25560',
|
||||
name: 'PV-Infrastrukturprojekt, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.21,
|
||||
y: 15.07,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'ansbach-91522',
|
||||
name: 'Netzanbindung Windenergie, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 49.8,
|
||||
y: 74.59,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'schinne-39579',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.69,
|
||||
y: 32.23,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'neuengörs-23818',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 48.33,
|
||||
y: 16.63,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'denkendorf-85095',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 57.48,
|
||||
y: 79.18,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'lindhorst-31698',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 38.98,
|
||||
y: 35.98,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'rohrenfels-86701',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 54.89,
|
||||
y: 82.18,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'ingoldingen-88456',
|
||||
name: 'PV-Infrastrukturprojekt, Region Schwarzwald / Bodensee',
|
||||
type: 'minor_node',
|
||||
x: 42.75,
|
||||
y: 90.62,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'lichtenau-33165',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 35.64,
|
||||
y: 45.44,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'nochten-02943',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.03,
|
||||
y: 47.68,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'dalkendorf-17166',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 66.23,
|
||||
y: 17.67,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'schopfheim-79650',
|
||||
name: 'Netzanbindung Windenergie, Region Schwarzwald / Bodensee',
|
||||
type: 'minor_node',
|
||||
x: 26.98,
|
||||
y: 94.96,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'lunow-stolzenhagen-16248',
|
||||
name: 'Trassenbau Hochspannung, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 79.88,
|
||||
y: 28.84,
|
||||
description: 'power',
|
||||
},
|
||||
{
|
||||
id: 'petershagen-15326',
|
||||
name: 'PV-Infrastrukturprojekt, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 36.26,
|
||||
y: 35.79,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'krevese-39606',
|
||||
name: 'Netzanbindung Windenergie, Region Hannover / Braunschweig',
|
||||
type: 'minor_node',
|
||||
x: 59.37,
|
||||
y: 30.29,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'seckeritz-17440',
|
||||
name: 'PV-Infrastrukturprojekt, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 76.91,
|
||||
y: 15.43,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'tempelfelde-16230',
|
||||
name: 'Netzanbindung Windenergie, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 76.55,
|
||||
y: 31.59,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'altenmedingen-29575',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 50.28,
|
||||
y: 26.09,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'neiße-malxetal-03159',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.5,
|
||||
y: 45.15,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'eisenhüttenstadt-15890',
|
||||
name: 'PV-Infrastrukturprojekt, Region Berlin / Brandenburg',
|
||||
type: 'minor_node',
|
||||
x: 84.27,
|
||||
y: 38.7,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'groß-pankow-16928',
|
||||
name: 'Netzanbindung Windenergie, Region Mecklenburg-Vorpommern',
|
||||
type: 'minor_node',
|
||||
x: 62.41,
|
||||
y: 26.45,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'dahlhausen-16909',
|
||||
name: 'PV-Infrastrukturprojekt, Rhein-Ruhr-Gebiet',
|
||||
type: 'minor_node',
|
||||
x: 22.21,
|
||||
y: 50.39,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'gnarrenburg-27442',
|
||||
name: 'Netzanbindung Windenergie, Region Emsland / Ostfriesland',
|
||||
type: 'minor_node',
|
||||
x: 36.74,
|
||||
y: 23.17,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'greding-91171',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 56.38,
|
||||
y: 77.43,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'scheeßel-27383',
|
||||
name: 'Netzanbindung Windenergie, Metropolregion Hamburg',
|
||||
type: 'minor_node',
|
||||
x: 40.7,
|
||||
y: 25.88,
|
||||
description: 'wind',
|
||||
},
|
||||
{
|
||||
id: 'egling-an-der-paar-86492',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 53.41,
|
||||
y: 88.51,
|
||||
description: 'pv',
|
||||
},
|
||||
{
|
||||
id: 'ellingen-91792',
|
||||
name: 'PV-Infrastrukturprojekt, Großraum Stuttgart / Franken',
|
||||
type: 'minor_node',
|
||||
x: 52.92,
|
||||
y: 77.37,
|
||||
description: 'pv',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface ReferenceFrontmatter {
|
||||
dateString?: string;
|
||||
featuredImage?: string | null;
|
||||
location: string;
|
||||
category?: string | string[];
|
||||
}
|
||||
|
||||
export interface ReferenceData {
|
||||
|
||||
@@ -188,5 +188,59 @@
|
||||
"technical": "Technik",
|
||||
"sustainability": "Nachhaltigkeit"
|
||||
}
|
||||
},
|
||||
"ReferencesSlider": {
|
||||
"badge": "Ausgewählte Projekte",
|
||||
"title": "Referenzen & Erfolge",
|
||||
"description": "Ein Auszug unserer erfolgreich abgeschlossenen Projekte im Bereich Kabeltiefbau, Bohrtechnik und Netzinfrastruktur.",
|
||||
"ctaLabel": "Alle Referenzen ansehen"
|
||||
},
|
||||
"CompanyTimeline": {
|
||||
"badge": "Unsere Geschichte",
|
||||
"title": "Meilensteine der Entwicklung",
|
||||
"milestones": {
|
||||
"2015": {
|
||||
"title": "Gründung E-TIB GmbH",
|
||||
"desc": "Ausführung elektrischer Infrastrukturprojekte, Kabeltiefbau und Horizontalspülbohrungen."
|
||||
},
|
||||
"2019_ing": {
|
||||
"title": "Gründung E-TIB Ingenieurgesellschaft",
|
||||
"desc": "Genehmigungs- und Ausführungsplanung, komplexe Querungen sowie Netzanschlussplanung."
|
||||
},
|
||||
"2019_verw": {
|
||||
"title": "Gründung E-TIB Verwaltung GmbH",
|
||||
"desc": "Zentrale Dienste, Erwerb, Vermietung, Verpachtung und Verwaltung von Immobilien und Maschinen."
|
||||
},
|
||||
"2025": {
|
||||
"title": "Gründung E-TIB Bohrtechnik GmbH",
|
||||
"desc": "Spezialisierung auf präzise Horizontalspülbohrungen in allen Bodenklassen."
|
||||
}
|
||||
}
|
||||
},
|
||||
"InteractiveGermanyMap": {
|
||||
"types": {
|
||||
"hq": "Hauptsitz",
|
||||
"branch": "Niederlassung",
|
||||
"project": "Projekt",
|
||||
"reference": "Referenz"
|
||||
},
|
||||
"descriptions": {
|
||||
"wind": "Errichtung Leitungstrassen & Netzanbindung für Windpark-Infrastruktur.",
|
||||
"pv": "Bau von Mittelspannungstrassen zur Einspeisung der PV-Anlage.",
|
||||
"fiber": "Komplexer FTTx Breitbandausbau inkl. LWL-Montage & Tiefbau.",
|
||||
"power": "110kV Hochspannungstrasse Erdkabelverlegung.",
|
||||
"battery": "Infrastrukturausbau für BESS (Batteriespeicher).",
|
||||
"default": "Komplexes Infrastrukturprojekt (Tiefbau & HDD)."
|
||||
},
|
||||
"features": {
|
||||
"wind": "Netzanbindung Windpark",
|
||||
"pv": "Einspeisung PV-Anlage",
|
||||
"fiber": "FTTx Infrastruktur",
|
||||
"power": "110kV Trasse",
|
||||
"battery": "BESS Anschluss",
|
||||
"default": "Infrastrukturprojekt",
|
||||
"method": "Tiefbau & Spülbohrung (HDD)"
|
||||
},
|
||||
"learnMore": "Zum Projekt"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"about-us": "ueber-uns",
|
||||
"terms": "agb",
|
||||
"start": "start",
|
||||
"events": "messen"
|
||||
"trade-fairs": "messen"
|
||||
},
|
||||
"products": {},
|
||||
"categories": {}
|
||||
@@ -188,5 +188,59 @@
|
||||
"technical": "Technical",
|
||||
"sustainability": "Sustainability"
|
||||
}
|
||||
},
|
||||
"ReferencesSlider": {
|
||||
"badge": "Selected Projects",
|
||||
"title": "References & Successes",
|
||||
"description": "A selection of our successfully completed projects in underground cable engineering, drilling technology, and grid infrastructure.",
|
||||
"ctaLabel": "View All References"
|
||||
},
|
||||
"CompanyTimeline": {
|
||||
"badge": "Our History",
|
||||
"title": "Milestones of Development",
|
||||
"milestones": {
|
||||
"2015": {
|
||||
"title": "Foundation of E-TIB GmbH",
|
||||
"desc": "Execution of electrical infrastructure projects, cable civil engineering, and horizontal directional drilling."
|
||||
},
|
||||
"2019_ing": {
|
||||
"title": "Foundation of E-TIB Ingenieurgesellschaft",
|
||||
"desc": "Permit and execution planning, complex crossings, and grid connection planning."
|
||||
},
|
||||
"2019_verw": {
|
||||
"title": "Foundation of E-TIB Verwaltung GmbH",
|
||||
"desc": "Central services, acquisition, leasing, and management of real estate and machinery."
|
||||
},
|
||||
"2025": {
|
||||
"title": "Foundation of E-TIB Bohrtechnik GmbH",
|
||||
"desc": "Specialization in precise horizontal directional drilling in all soil classes."
|
||||
}
|
||||
}
|
||||
},
|
||||
"InteractiveGermanyMap": {
|
||||
"types": {
|
||||
"hq": "Headquarters",
|
||||
"branch": "Branch",
|
||||
"project": "Project",
|
||||
"reference": "Reference"
|
||||
},
|
||||
"descriptions": {
|
||||
"wind": "Construction of cable routes & grid connection for wind park infrastructure.",
|
||||
"pv": "Construction of medium-voltage routes for feeding PV systems.",
|
||||
"fiber": "Complex FTTx broadband expansion incl. fiber optic assembly & civil engineering.",
|
||||
"power": "110kV high-voltage underground cable laying.",
|
||||
"battery": "Infrastructure expansion for BESS (battery storage).",
|
||||
"default": "Complex infrastructure project (civil engineering & HDD)."
|
||||
},
|
||||
"features": {
|
||||
"wind": "Grid connection wind park",
|
||||
"pv": "Feeding PV system",
|
||||
"fiber": "FTTx Infrastructure",
|
||||
"power": "110kV Route",
|
||||
"battery": "BESS Connection",
|
||||
"default": "Infrastructure project",
|
||||
"method": "Civil engineering & HDD"
|
||||
},
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@
|
||||
"@tailwindcss/cli": "^4.1.18",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/node": "^22.19.3",
|
||||
|
||||
110
pnpm-lock.yaml
generated
110
pnpm-lock.yaml
generated
@@ -44,7 +44,7 @@ importers:
|
||||
version: 10.47.0(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0))(react@19.2.4)(webpack@5.105.4(esbuild@0.27.4))
|
||||
'@types/recharts':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
|
||||
version: 2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
||||
axios:
|
||||
specifier: ^1.13.5
|
||||
version: 1.14.0(debug@4.4.3)
|
||||
@@ -116,7 +116,7 @@ importers:
|
||||
version: 10.1.0(@types/react@19.2.14)(react@19.2.4)
|
||||
recharts:
|
||||
specifier: ^3.7.0
|
||||
version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
|
||||
version: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
||||
rehype-raw:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
@@ -181,6 +181,12 @@ importers:
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.19
|
||||
version: 0.5.19(tailwindcss@4.2.2)
|
||||
'@testing-library/dom':
|
||||
specifier: ^10.4.1
|
||||
version: 10.4.1
|
||||
'@testing-library/react':
|
||||
specifier: ^16.3.2
|
||||
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@types/geojson':
|
||||
specifier: ^7946.0.16
|
||||
version: 7946.0.16
|
||||
@@ -2936,6 +2942,25 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@testing-library/react@16.3.2':
|
||||
resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@testing-library/dom': ^10.0.0
|
||||
'@types/react': ^18.0.0 || ^19.0.0
|
||||
'@types/react-dom': ^18.0.0 || ^19.0.0
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0':
|
||||
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
|
||||
|
||||
@@ -2978,6 +3003,9 @@ packages:
|
||||
'@types/acorn@4.0.6':
|
||||
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
||||
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -3510,6 +3538,10 @@ packages:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@5.2.0:
|
||||
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ansi-styles@6.2.3:
|
||||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3523,6 +3555,9 @@ packages:
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
aria-query@5.3.0:
|
||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||
|
||||
aria-query@5.3.2:
|
||||
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4336,6 +4371,9 @@ packages:
|
||||
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
dom-accessibility-api@0.5.16:
|
||||
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
|
||||
@@ -5893,6 +5931,10 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
lz-string@1.5.0:
|
||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||
hasBin: true
|
||||
|
||||
maath@0.10.8:
|
||||
resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==}
|
||||
peerDependencies:
|
||||
@@ -6699,6 +6741,10 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-format@27.5.1:
|
||||
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
|
||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||
|
||||
prismjs@1.29.0:
|
||||
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -6820,6 +6866,9 @@ packages:
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-is@17.0.2:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
|
||||
react-leaflet@4.2.1:
|
||||
resolution: {integrity: sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==}
|
||||
peerDependencies:
|
||||
@@ -7502,8 +7551,8 @@ packages:
|
||||
third-party-web@0.26.7:
|
||||
resolution: {integrity: sha512-buUzX4sXC4efFX6xg2bw6/eZsCUh8qQwSavC4D9HpONMFlRbcHhD8Je5qwYdCpViR6q0qla2wPP+t91a2vgolg==}
|
||||
|
||||
third-party-web@0.29.0:
|
||||
resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==}
|
||||
third-party-web@0.29.2:
|
||||
resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==}
|
||||
|
||||
thread-stream@4.0.0:
|
||||
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
||||
@@ -9809,7 +9858,7 @@ snapshots:
|
||||
'@paulirish/trace_engine@0.0.53':
|
||||
dependencies:
|
||||
legacy-javascript: 0.0.1
|
||||
third-party-web: 0.29.0
|
||||
third-party-web: 0.29.2
|
||||
|
||||
'@pdf-lib/standard-fonts@1.0.0':
|
||||
dependencies:
|
||||
@@ -10822,6 +10871,27 @@ snapshots:
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 4.2.2
|
||||
|
||||
'@testing-library/dom@10.4.1':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
'@babel/runtime': 7.29.2
|
||||
'@types/aria-query': 5.0.4
|
||||
aria-query: 5.3.0
|
||||
dom-accessibility-api: 0.5.16
|
||||
lz-string: 1.5.0
|
||||
picocolors: 1.1.1
|
||||
pretty-format: 27.5.1
|
||||
|
||||
'@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@testing-library/dom': 10.4.1
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
||||
|
||||
'@turbo/darwin-64@2.9.3':
|
||||
@@ -10853,6 +10923,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.2
|
||||
@@ -10998,9 +11070,9 @@ snapshots:
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/recharts@2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)':
|
||||
'@types/recharts@2.0.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)':
|
||||
dependencies:
|
||||
recharts: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1)
|
||||
recharts: 3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- react
|
||||
@@ -11438,6 +11510,8 @@ snapshots:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ansi-styles@5.2.0: {}
|
||||
|
||||
ansi-styles@6.2.3: {}
|
||||
|
||||
arg@5.0.2: {}
|
||||
@@ -11448,6 +11522,10 @@ snapshots:
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
aria-query@5.3.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
aria-query@5.3.2: {}
|
||||
|
||||
array-buffer-byte-length@1.0.2:
|
||||
@@ -12317,6 +12395,8 @@ snapshots:
|
||||
dependencies:
|
||||
esutils: 2.0.3
|
||||
|
||||
dom-accessibility-api@0.5.16: {}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
@@ -14234,6 +14314,8 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
lz-string@1.5.0: {}
|
||||
|
||||
maath@0.10.8(@types/three@0.183.1)(three@0.183.2):
|
||||
dependencies:
|
||||
'@types/three': 0.183.1
|
||||
@@ -15338,6 +15420,12 @@ snapshots:
|
||||
|
||||
prettier@3.8.1: {}
|
||||
|
||||
pretty-format@27.5.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 17.0.2
|
||||
|
||||
prismjs@1.29.0: {}
|
||||
|
||||
prismjs@1.30.0: {}
|
||||
@@ -15501,6 +15589,8 @@ snapshots:
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-is@17.0.2: {}
|
||||
|
||||
react-leaflet@4.2.1(leaflet@1.9.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@react-leaflet/core': 2.1.0(leaflet@1.9.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -15553,7 +15643,7 @@ snapshots:
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
recharts@3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@16.13.1)(react@19.2.4)(redux@5.0.1):
|
||||
recharts@3.8.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1):
|
||||
dependencies:
|
||||
'@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1))(react@19.2.4)
|
||||
clsx: 2.1.1
|
||||
@@ -15563,7 +15653,7 @@ snapshots:
|
||||
immer: 10.2.0
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-is: 16.13.1
|
||||
react-is: 17.0.2
|
||||
react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.4)(redux@5.0.1)
|
||||
reselect: 5.1.1
|
||||
tiny-invariant: 1.3.3
|
||||
@@ -16409,7 +16499,7 @@ snapshots:
|
||||
|
||||
third-party-web@0.26.7: {}
|
||||
|
||||
third-party-web@0.29.0: {}
|
||||
third-party-web@0.29.2: {}
|
||||
|
||||
thread-stream@4.0.0:
|
||||
dependencies:
|
||||
|
||||
52
scripts/compare-mdx-parity.ts
Normal file
52
scripts/compare-mdx-parity.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const CONTENT_DIR = path.join(process.cwd(), 'content');
|
||||
const deDir = path.join(CONTENT_DIR, 'de');
|
||||
const enDir = path.join(CONTENT_DIR, 'en');
|
||||
|
||||
function extractComponents(filePath: string): string[] {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
// Match any tag starting with uppercase letter, e.g. <HomeHero or <InteractiveGermanyMap
|
||||
const matches = content.match(/<[A-Z][a-zA-Z0-9]*/g) || [];
|
||||
return matches.map(m => m.substring(1));
|
||||
}
|
||||
|
||||
function checkParity() {
|
||||
const deFiles = fs.readdirSync(deDir).filter(f => f.endsWith('.mdx'));
|
||||
let hasErrors = false;
|
||||
|
||||
console.log('🔍 Checking MDX Component Parity between DE and EN...');
|
||||
|
||||
deFiles.forEach(file => {
|
||||
const dePath = path.join(deDir, file);
|
||||
const enPath = path.join(enDir, file);
|
||||
|
||||
if (!fs.existsSync(enPath)) {
|
||||
console.log(`❌ Missing English file: ${file}`);
|
||||
hasErrors = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const deComponents = extractComponents(dePath);
|
||||
const enComponents = extractComponents(enPath);
|
||||
|
||||
const deStr = deComponents.join(', ');
|
||||
const enStr = enComponents.join(', ');
|
||||
|
||||
if (deStr !== enStr) {
|
||||
console.log(`❌ Mismatch in ${file}:`);
|
||||
console.log(` DE components: [${deStr}]`);
|
||||
console.log(` EN components: [${enStr}]`);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
console.log(`✅ ${file} is in perfect parity.`);
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasErrors) {
|
||||
console.log('🎉 PERFECT PARITY DETECTED! All MDX components are identical.');
|
||||
}
|
||||
}
|
||||
|
||||
checkParity();
|
||||
5
test-build-env.js
Normal file
5
test-build-env.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const val = process.env.NEXT_PUBLIC_APP_VERSION;
|
||||
console.log("val is:", val);
|
||||
if (val) {
|
||||
console.log("replace:", val.replace(/^v/, ''));
|
||||
}
|
||||
26
tests/mdx-links.test.ts
Normal file
26
tests/mdx-links.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
describe('English MDX Content Links', () => {
|
||||
it('should not contain German slug routes like /en/kontakt or /en/karriere', () => {
|
||||
const enContentDir = path.join(process.cwd(), 'content', 'en');
|
||||
const files = fs.readdirSync(enContentDir).filter(f => f.endsWith('.mdx'));
|
||||
|
||||
const forbiddenLinks = ['/en/kontakt', '/en/karriere'];
|
||||
const infractions: string[] = [];
|
||||
|
||||
files.forEach(file => {
|
||||
const filePath = path.join(enContentDir, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
forbiddenLinks.forEach(link => {
|
||||
if (content.includes(link)) {
|
||||
infractions.push(`${file} contains forbidden link "${link}"`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
expect(infractions).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user