This commit is contained in:
2026-01-15 12:10:24 +01:00
parent 0cc0db81ba
commit e92b8e14f3
21 changed files with 5790 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Docker
.dockerignore

29
Dockerfile Normal file
View File

@@ -0,0 +1,29 @@
# Build Stage
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Build Frontend
RUN npm run build:frontend
# Build Backend
RUN npm run build:backend
# Runtime Stage
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/backend/server.js"]

38
context/about.md Normal file
View File

@@ -0,0 +1,38 @@
Die Köpfe, die Energie zum Laufen bringen
Wir verbinden Energie, Know-how und Innovation, um eine nachhaltigere Zukunft zu gestalten.
Michael Bodemer
„Herausforderungen sind da, um gelöst zu werden nicht, um über ihre Komplexität zu diskutieren.“
Michael Bodemer ist unser Mann, wenn es kompliziert wird und das ist bei Kabelnetzen oft der Fall. Mit seinem scharfen Blick und einem Händchen für praktikable Lösungen ist er eine unserer zentralen Säulen. Michael denkt nicht nur an Details, er treibt Projekte voran sei es in der Planung, im Kundengespräch oder bei der Auswahl der besten Kabel für jedes Vorhaben.
Michael's LinkedIn
Verbindungen, die Geschichte schreiben
Bei KLZ vereinen wir Tradition und Innovation zu zuverlässigen Energielösungen. Unsere Wurzeln reichen tief in die Geschichte der Kabeltechnologie zurück mit jeder Menge praktischer Erfahrung und einem Blick für zukunftsweisende Entwicklungen.
In jedem Projekt steckt nicht nur technisches Know-how, sondern auch das Bewusstsein für das Handwerk, das die Welt seit Generationen verbindet. Historische Illustrationen aus den frühen Tagen der Energiebranche erinnern uns daran, wie weit wir gekommen sind und dass echte Exzellenz immer mit Sorgfalt beginnt.
Klaus Mintel
„Manchmal braucht es nur einen klaren Kopf und das richtige Kabel, um die Welt ein Stück besser zu machen.“
Klaus ist der Fels in der Brandung selbst wenn das Kabelchaos überhandnimmt. Mit jahrzehntelanger Erfahrung und einem stabilen Netzwerk sorgt er dafür, dass alles glatt läuft. Er denkt nicht nur in Lösungen, sondern bringt auch Humor und den nötigen Weitblick mit, um selbst komplexe Themen locker auf den Punkt zu bringen.
Klaus' LinkedIn
Unser Manifest
1
Kompetenz
Jahrzehntelange Erfahrung und europaweites Know-how kombiniert mit Engagement und neuen Ideen. Produktionspartner bis zu 525 kV und modernsten Anlagen, Testlaboren und investitionsbereit für die Zukunft.
2
Verfügbarkeit
Immer für Sie da ohne Warten, ohne Verzögerung, einfach schnelle und verlässliche Unterstützung. Vielleicht liegt es daran, dass wir lieben, was wir tun.
3
Lösungen
Für Lösungen braucht es viele Fragen. Diese stellen wir. Ihnen, dem Hersteller und uns selbst. Wer nicht hinterfragt, zahlt später dafür. Das gilt es zu verhindern.
4
Logistik
Überwachung der Fertigung, regelmäßiger Austausch, Fracht-Tracking, Verzollung, Umladung, Zeittunnel der Anlieferung beachten, Rechnung, Lieferscheine unser Alltag. Wir haben das Team dazu.
5
Offen für Neues
Wir hören zu. Von der Anfrage, über das Angebot bis hin zur Auslieferung. Was besser gemacht werden kann, muss diskutiert werden. Wer seine Prozesse nicht anpasst, fährt irgendwann nicht mehr auf der Autobahn. Sondern in die Sackgasse.
6
Zuverlässigkeit
Wir halten, was wir versprechen jedes Mal und ohne Ausnahme.

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/assets/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MB Grid Solutions | Energiekabelprojekte bis 110 kV</title>
<meta name="description" content="Spezialisierter Partner für Energiekabelprojekte bis 110 kV. Herstellerneutrale technische Beratung und Projektbegleitung." />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

4617
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

50
package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "mb-grid-solutions.com",
"version": "1.0.0",
"type": "module",
"main": "dist/backend/server.js",
"directories": {
"doc": "docs"
},
"scripts": {
"dev:frontend": "vite",
"dev:backend": "nodemon --exec ts-node-esm server.ts",
"build:frontend": "vite build",
"build:backend": "tsc -p tsconfig.server.json",
"build": "npm run build:frontend && npm run build:backend",
"start": "node dist/backend/server.js",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@vitejs/plugin-react-swc": "^3.11.0",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
"framer-motion": "^12.26.2",
"helmet": "^8.1.0",
"lucide-react": "^0.562.0",
"nodemailer": "^7.0.12",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-router-dom": "^7.12.0",
"vite": "^6.4.1"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/node": "^25.0.8",
"@types/nodemailer": "^7.0.5",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"nodemon": "^3.1.11",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"vite-plugin-checker": "^0.12.0"
}
}

111
server.ts Normal file
View File

@@ -0,0 +1,111 @@
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import nodemailer from 'nodemailer';
import cors from 'cors';
import dotenv from 'dotenv';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 3000;
// Security
app.use(helmet({
contentSecurityPolicy: {
directives: {
...helmet.contentSecurityPolicy.getDefaultDirectives(),
"img-src": ["'self'", "data:"],
},
},
}));
app.use(cors());
app.use(express.json());
// Rate limiting for API
const apiLimiter = rateLimit({
windowMs: 10 * 60 * 1000, // 10 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: 'Zu viele Anfragen von dieser IP, bitte versuchen Sie es später erneut.',
standardHeaders: true,
legacyHeaders: false,
});
// API Endpoint
app.post('/api/contact', apiLimiter, async (req, res) => {
const { name, email, company, message, website } = req.body;
// Honeypot check
if (website) {
console.log('Spam detected (honeypot)');
return res.status(200).json({ message: 'Ok' }); // Generic success
}
// Validation
if (!name || name.length < 2 || name.length > 100) {
return res.status(400).json({ error: 'Ungültiger Name' });
}
if (!email || !/^\S+@\S+\.\S+$/.test(email)) {
return res.status(400).json({ error: 'Ungültige E-Mail' });
}
if (!message || message.length < 20 || message.length > 4000) {
return res.status(400).json({ error: 'Nachricht zu kurz oder zu lang' });
}
try {
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587'),
secure: process.env.SMTP_SECURE === 'true',
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
await transporter.sendMail({
from: process.env.SMTP_FROM,
to: process.env.CONTACT_RECIPIENT,
replyTo: email,
subject: `Kontaktanfrage von ${name}`,
text: `
Name: ${name}
Firma: ${company || 'Nicht angegeben'}
E-Mail: ${email}
Zeitpunkt: ${new Date().toISOString()}
Nachricht:
${message}
`,
});
res.status(200).json({ message: 'Ok' });
} catch (error) {
console.error('SMTP Error:', error);
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
// Health check
app.get('/healthz', (req, res) => {
res.status(200).send('OK');
});
// Serve static files from the React app
const distPath = path.join(__dirname, 'dist/frontend');
app.use(express.static(distPath));
// The "catchall" handler: for any request that doesn't
// match one above, send back React's index.html file.
app.get('*', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

27
src/App.tsx Normal file
View File

@@ -0,0 +1,27 @@
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import Legal from './pages/Legal';
import Privacy from './pages/Privacy';
import AGB from './pages/AGB';
function App() {
return (
<Router>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/ueber-uns" element={<About />} />
<Route path="/kontakt" element={<Contact />} />
<Route path="/impressum" element={<Legal />} />
<Route path="/datenschutz" element={<Privacy />} />
<Route path="/agb" element={<AGB />} />
</Routes>
</Layout>
</Router>
);
}
export default App;

67
src/components/Layout.tsx Normal file
View File

@@ -0,0 +1,67 @@
import React from 'react';
import { Link } from 'react-router-dom';
const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="layout">
<header style={{
position: 'sticky',
top: 0,
zIndex: 100,
background: 'rgba(248, 249, 250, 0.95)',
backdropFilter: 'blur(10px)',
borderBottom: '1px solid var(--secondary-bg)'
}}>
<div className="container" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Link to="/" style={{ display: 'flex', alignItems: 'center' }}>
<img src="/assets/logo.png" alt="MB Grid Solutions" style={{ height: '60px' }} />
</Link>
<nav style={{ display: 'flex', gap: '2rem' }}>
<Link to="/" className="nav-link">Startseite</Link>
<Link to="/ueber-uns" className="nav-link">Über uns</Link>
<Link to="/kontakt" className="nav-link">Kontakt</Link>
</nav>
</div>
</header>
<main>
{children}
</main>
<footer style={{ borderTop: '1px solid var(--secondary-bg)', padding: '4rem 0', marginTop: '4rem', background: 'white' }}>
<div className="container">
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', marginBottom: '3rem' }}>
<div>
<img src="/assets/logo.png" alt="MB Grid Solutions" style={{ height: '80px', marginBottom: '1.5rem', filter: 'grayscale(1)' }} />
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
Ihr Partner für Energiekabelprojekte bis 110 kV.
</p>
</div>
<div>
<h4 style={{ fontSize: '1rem', marginBottom: '1rem' }}>Navigation</h4>
<nav style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<Link to="/">Startseite</Link>
<Link to="/ueber-uns">Über uns</Link>
<Link to="/kontakt">Kontakt</Link>
</nav>
</div>
<div>
<h4 style={{ fontSize: '1rem', marginBottom: '1rem' }}>Rechtliches</h4>
<nav style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<Link to="/impressum">Impressum</Link>
<Link to="/datenschutz">Datenschutz</Link>
<Link to="/agb">AGB</Link>
</nav>
</div>
</div>
<div style={{ borderTop: '1px solid var(--secondary-bg)', paddingTop: '2rem', display: 'flex', justifyContent: 'space-between', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
<div>&copy; {new Date().getFullYear()} MB Grid Solutions GmbH. Alle Rechte vorbehalten.</div>
<div>Made with precision.</div>
</div>
</div>
</footer>
</div>
);
};
export default Layout;

258
src/index.css Normal file
View File

@@ -0,0 +1,258 @@
:root {
--primary-color: #0E2A47;
--bg-color: #F8F9FA;
--secondary-bg: #E6E9ED;
--text-secondary: #6B7280;
--text-primary: #1F2933;
--accent-green: #2FA66A;
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--transition-fast: 0.2s ease;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--header-height: 80px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
-webkit-tap-highlight-color: transparent;
}
html {
scroll-behavior: smooth;
}
body {
font-family: var(--font-family);
background-color: var(--bg-color);
color: var(--text-primary);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
h1, h2, h3, h4, h5, h6 {
color: var(--primary-color);
font-weight: 700;
line-height: 1.1;
margin-bottom: 1.5rem;
letter-spacing: -0.03em;
}
h1 { font-size: clamp(2rem, 5vw, 3.5rem); hyphens: auto; }
h1.no-underline::after,
h2.no-underline::after {
display: none !important;
}
h2 { font-size: clamp(1.8rem, 4vw, 2.5rem); position: relative; display: block; margin-bottom: 2rem; }
h2:not(.no-underline)::after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
width: 40px;
height: 3px;
background: var(--accent-green);
}
h3 { font-size: 1.4rem; letter-spacing: -0.01em; }
a {
color: var(--primary-color);
text-decoration: none;
transition: all var(--transition-fast);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
section {
padding: 6rem 0;
}
.grid {
display: grid;
gap: 2.5rem;
}
.cta-button {
background-color: var(--primary-color);
color: white;
padding: 1rem 2rem;
border: none;
cursor: pointer;
font-weight: 700;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
transition: all var(--transition-fast);
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.8rem;
border-radius: 4px;
}
.cta-button:hover {
background-color: var(--text-primary);
color: white;
}
input, textarea {
width: 100%;
padding: 1rem;
border: 1px solid var(--secondary-bg);
background: white;
font-family: inherit;
margin-bottom: 1.5rem;
transition: all var(--transition-fast);
border-radius: 4px;
font-size: 16px; /* Prevent zoom on iOS */
}
input:focus, textarea:focus {
outline: none;
border-color: var(--primary-color);
}
.card {
background: white;
padding: 2.5rem;
border: 1px solid var(--secondary-bg);
transition: all var(--transition-fast);
border-radius: 8px;
}
.card:hover {
border-color: var(--primary-color);
}
.img-placeholder {
background: #e2e8f0;
width: 100%;
aspect-ratio: 16/9;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.2em;
margin-bottom: 2rem;
border: 1px solid var(--secondary-bg);
border-radius: 8px;
}
.nav-link {
position: relative;
font-weight: 600;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-primary);
}
.nav-link:hover {
color: var(--accent-green);
}
.badge {
display: block;
color: var(--accent-green);
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.15em;
margin-bottom: 0.75rem;
}
header .container {
height: var(--header-height);
}
@media (max-width: 768px) {
:root {
--header-height: 64px;
}
section {
padding: 4rem 0;
}
.grid {
gap: 1.5rem;
}
header .container {
height: var(--header-height);
padding: 0 1rem;
}
header img {
height: 40px !important;
}
nav {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: white;
border-top: 1px solid var(--secondary-bg);
padding: 0.75rem 1rem;
justify-content: space-around !important;
gap: 0 !important;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
z-index: 1000;
}
.nav-link {
font-size: 0.7rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.nav-link::after {
display: none;
}
main {
padding-bottom: 80px; /* Space for bottom nav */
}
.hero {
padding: 2rem 0 !important;
min-height: auto !important;
}
.hero .grid {
grid-template-columns: 1fr !important;
}
.hero div:first-child {
width: 100% !important;
clip-path: none !important;
background: #f1f5f9 !important;
padding: 2rem 1rem !important;
border-radius: 12px;
}
.hero .img-placeholder {
display: none;
}
.cta-button {
width: 100%;
}
.card {
padding: 1.5rem;
}
}

10
src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

13
src/pages/AGB.tsx Normal file
View File

@@ -0,0 +1,13 @@
const AGB = () => (
<div className="container">
<section>
<h1>Allgemeine Geschäftsbedingungen (AGB)</h1>
<div style={{ maxWidth: '800px' }}>
<p>Hier finden Sie unsere Allgemeinen Geschäftsbedingungen...</p>
<p style={{ marginTop: '1rem' }}>[Platzhalter für AGB-Inhalte]</p>
</div>
</section>
</div>
);
export default AGB;

135
src/pages/About.tsx Normal file
View File

@@ -0,0 +1,135 @@
import { motion } from 'framer-motion';
import { Target, Network, Award, Clock, Lightbulb, Truck, MessageSquare, ShieldCheck } from 'lucide-react';
const About = () => (
<div>
<section style={{ background: 'white' }}>
<div className="container">
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', alignItems: 'center', gap: '4rem' }}>
<div>
<h1 className="no-underline">Über uns</h1>
<p style={{ fontSize: '1.25rem', color: 'var(--text-secondary)', marginBottom: '2rem', lineHeight: 1.5 }}>
Wir verbinden Energie, Know-how und Innovation, um die Infrastruktur der Zukunft zu gestalten.
</p>
<p style={{ marginBottom: '1.5rem' }}>
MB Grid Solution steht für technische Exzellenz in der Energiekabeltechnologie. Wir verstehen uns als Ihr technischer Lotse, der mit jahrzehntelanger Erfahrung und einem klaren Blick für zukunftsweisende Entwicklungen komplexe Projekte sicher zum Ziel führt.
</p>
<p>
Unsere Wurzeln liegen in der tiefen praktischen Erfahrung. Wir vereinen Tradition mit modernster Innovation, um zuverlässige Energielösungen für Projekte bis 110 kV und darüber hinaus zu realisieren.
</p>
</div>
<div className="img-placeholder" style={{ height: '500px', marginBottom: 0 }}>
[ Engineering Excellence Visualization ]
</div>
</div>
</div>
</section>
<section>
<div className="container">
<div style={{ textAlign: 'center', marginBottom: '4rem' }}>
<h2 className="no-underline">Die Köpfe hinter MB Grid Solution</h2>
<p style={{ color: 'var(--text-secondary)' }}>Expertise, die Energie zum Laufen bringt.</p>
</div>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))' }}>
<div className="card">
<div style={{ display: 'flex', gap: '2rem', marginBottom: '1.5rem' }}>
<div style={{ width: '120px', height: '120px', background: 'var(--secondary-bg)', flexShrink: 0 }}></div>
<div>
<h3 style={{ marginBottom: '0.25rem' }}>Michael Bodemer</h3>
<p style={{ color: 'var(--accent-green)', fontWeight: 600, fontSize: '0.85rem', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Technische Leitung</p>
</div>
</div>
<p style={{ fontStyle: 'italic', marginBottom: '1.5rem', color: 'var(--primary-color)', fontWeight: 500 }}>
Herausforderungen sind da, um gelöst zu werden nicht, um über ihre Komplexität zu diskutieren.
</p>
<p style={{ fontSize: '0.95rem', color: 'var(--text-secondary)', lineHeight: 1.6 }}>
Michael Bodemer ist der Experte für komplexe Kabelnetze. Mit seinem scharfen Blick für praktikable Lösungen treibt er Projekte in der Planung und technischen Umsetzung voran. Er stellt sicher, dass für jedes Vorhaben die optimalen Komponenten ausgewählt werden.
</p>
</div>
<div className="card">
<div style={{ display: 'flex', gap: '2rem', marginBottom: '1.5rem' }}>
<div style={{ width: '120px', height: '120px', background: 'var(--secondary-bg)', flexShrink: 0 }}></div>
<div>
<h3 style={{ marginBottom: '0.25rem' }}>Klaus Mintel</h3>
<p style={{ color: 'var(--accent-green)', fontWeight: 600, fontSize: '0.85rem', textTransform: 'uppercase', letterSpacing: '0.05em' }}>Projektmanagement</p>
</div>
</div>
<p style={{ fontStyle: 'italic', marginBottom: '1.5rem', color: 'var(--primary-color)', fontWeight: 500 }}>
Manchmal braucht es nur einen klaren Kopf und das richtige Kabel, um die Welt ein Stück besser zu machen.
</p>
<p style={{ fontSize: '0.95rem', color: 'var(--text-secondary)', lineHeight: 1.6 }}>
Klaus Mintel bewahrt auch bei komplexesten Anforderungen den Weitblick. Mit jahrzehntelanger Erfahrung und einem stabilen Netzwerk sorgt er für reibungslose Abläufe. Er bringt technische Themen präzise auf den Punkt und sichert den Projekterfolg.
</p>
</div>
</div>
</div>
</section>
<section style={{ background: 'white' }}>
<div className="container">
<div style={{ textAlign: 'center', marginBottom: '5rem' }}>
<h2 className="no-underline">Unser Manifest</h2>
<p style={{ color: 'var(--text-secondary)' }}>Werte, die unsere tägliche Arbeit leiten.</p>
</div>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '3rem' }}>
<div style={{ display: 'flex', gap: '1.5rem' }}>
<div style={{ color: 'var(--accent-green)', flexShrink: 0 }}><Award size={32} /></div>
<div>
<h4 style={{ marginBottom: '0.5rem' }}>1. Kompetenz</h4>
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Jahrzehntelange Erfahrung kombiniert mit europaweitem Know-how. Wir arbeiten mit Partnern für modernste Anlagen und Testlabore bis 525 kV.</p>
</div>
</div>
<div style={{ display: 'flex', gap: '1.5rem' }}>
<div style={{ color: 'var(--accent-green)', flexShrink: 0 }}><Clock size={32} /></div>
<div>
<h4 style={{ marginBottom: '0.5rem' }}>2. Verfügbarkeit</h4>
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Schnelle und verlässliche Unterstützung ohne unnötige Verzögerungen. Wir sind für Sie da, wenn es darauf ankommt.</p>
</div>
</div>
<div style={{ display: 'flex', gap: '1.5rem' }}>
<div style={{ color: 'var(--accent-green)', flexShrink: 0 }}><Lightbulb size={32} /></div>
<div>
<h4 style={{ marginBottom: '0.5rem' }}>3. Lösungen</h4>
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Wir stellen die richtigen Fragen an Sie, an Hersteller und an uns selbst. Nur wer hinterfragt, findet die technisch und wirtschaftlich beste Lösung.</p>
</div>
</div>
<div style={{ display: 'flex', gap: '1.5rem' }}>
<div style={{ color: 'var(--accent-green)', flexShrink: 0 }}><Truck size={32} /></div>
<div>
<h4 style={{ marginBottom: '0.5rem' }}>4. Logistik & Überwachung</h4>
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Von der Fertigungsüberwachung bis zum Fracht-Tracking und der termingerechten Anlieferung wir steuern den gesamten Prozess professionell.</p>
</div>
</div>
<div style={{ display: 'flex', gap: '1.5rem' }}>
<div style={{ color: 'var(--accent-green)', flexShrink: 0 }}><MessageSquare size={32} /></div>
<div>
<h4 style={{ marginBottom: '0.5rem' }}>5. Offenheit</h4>
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Wir hören zu und passen unsere Prozesse kontinuierlich an. Stillstand ist für uns keine Option wir optimieren für Ihren Erfolg.</p>
</div>
</div>
<div style={{ display: 'flex', gap: '1.5rem' }}>
<div style={{ color: 'var(--accent-green)', flexShrink: 0 }}><ShieldCheck size={32} /></div>
<div>
<h4 style={{ marginBottom: '0.5rem' }}>6. Zuverlässigkeit</h4>
<p style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Wir halten, was wir versprechen ohne Ausnahme. Verbindlichkeit ist das Fundament unserer Zusammenarbeit.</p>
</div>
</div>
</div>
</div>
</section>
<section>
<div className="container">
<div className="card" style={{ background: 'var(--primary-color)', color: 'white', textAlign: 'center', padding: '4rem' }}>
<h2 style={{ color: 'white', border: 'none', padding: 0, marginBottom: '1.5rem' }}>Bereit für Ihr nächstes Projekt?</h2>
<p style={{ marginBottom: '2.5rem', opacity: 0.9, fontSize: '1.1rem' }}>Lassen Sie uns gemeinsam die optimale Lösung für Ihre Energieinfrastruktur finden.</p>
<a href="/kontakt" className="cta-button" style={{ background: 'white', color: 'var(--primary-color)' }}>Jetzt Kontakt aufnehmen</a>
</div>
</div>
</section>
</div>
);
export default About;

129
src/pages/Contact.tsx Normal file
View File

@@ -0,0 +1,129 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';
import { Mail, Phone, MapPin, Send, CheckCircle } from 'lucide-react';
const Contact = () => {
const [submitted, setSubmitted] = useState(false);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (response.ok) {
setSubmitted(true);
} else {
const err = await response.json();
alert(`Fehler: ${err.error || 'Es gab einen Fehler beim Senden Ihrer Nachricht.'}`);
}
} catch (error) {
alert('Es gab einen Fehler beim Senden Ihrer Nachricht.');
} finally {
setLoading(false);
}
};
return (
<div className="container">
<section>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', gap: '4rem' }}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<h1>Kontakt</h1>
<p style={{ fontSize: '1.1rem', color: 'var(--text-secondary)', marginBottom: '3rem' }}>
Haben Sie Fragen zu einem Projekt oder benötigen Sie technische Beratung? Wir freuen uns auf Ihre Nachricht.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
<div style={{ display: 'flex', gap: '1.5rem', alignItems: 'flex-start' }}>
<div style={{ color: 'var(--accent-green)', background: 'white', padding: '1rem', border: '1px solid var(--secondary-bg)' }}><Mail size={24} /></div>
<div>
<h4 style={{ marginBottom: '0.25rem', fontSize: '1rem' }}>E-Mail</h4>
<a href="mailto:info@mb-grid-solutions.com" style={{ fontSize: '1.1rem', fontWeight: 500 }}>info@mb-grid-solutions.com</a>
</div>
</div>
<div style={{ display: 'flex', gap: '1.5rem', alignItems: 'flex-start' }}>
<div style={{ color: 'var(--accent-green)', background: 'white', padding: '1rem', border: '1px solid var(--secondary-bg)' }}><Phone size={24} /></div>
<div>
<h4 style={{ marginBottom: '0.25rem', fontSize: '1rem' }}>Telefon</h4>
<a href="tel:+49123456789" style={{ fontSize: '1.1rem', fontWeight: 500 }}>+49 (0) 123 456789</a>
</div>
</div>
<div style={{ display: 'flex', gap: '1.5rem', alignItems: 'flex-start' }}>
<div style={{ color: 'var(--accent-green)', background: 'white', padding: '1rem', border: '1px solid var(--secondary-bg)' }}><MapPin size={24} /></div>
<div>
<h4 style={{ marginBottom: '0.25rem', fontSize: '1rem' }}>Anschrift</h4>
<p style={{ fontSize: '1.1rem', fontWeight: 500, color: 'var(--primary-color)' }}>
MB Grid Solutions GmbH<br />
Raiffeisenstraße 22<br />
73630 Remshalden
</p>
</div>
</div>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="card"
>
{submitted ? (
<div style={{ textAlign: 'center', padding: '3rem 0' }}>
<div style={{ color: 'var(--accent-green)', marginBottom: '1.5rem' }}><CheckCircle size={64} /></div>
<h3>Nachricht gesendet</h3>
<p style={{ color: 'var(--text-secondary)' }}>Vielen Dank für Ihre Anfrage. Wir werden uns in Kürze bei Ihnen melden.</p>
<button onClick={() => setSubmitted(false)} className="cta-button" style={{ marginTop: '2rem' }}>Weitere Nachricht</button>
</div>
) : (
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column' }}>
<div className="grid" style={{ gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div>
<label htmlFor="name" style={{ display: 'block', marginBottom: '0.5rem', fontSize: '0.9rem', fontWeight: 600 }}>Name *</label>
<input type="text" id="name" name="name" required minLength={2} maxLength={100} placeholder="Ihr Name" />
</div>
<div>
<label htmlFor="company" style={{ display: 'block', marginBottom: '0.5rem', fontSize: '0.9rem', fontWeight: 600 }}>Firma</label>
<input type="text" id="company" name="company" placeholder="Ihr Unternehmen" />
</div>
</div>
<label htmlFor="email" style={{ display: 'block', marginBottom: '0.5rem', fontSize: '0.9rem', fontWeight: 600 }}>E-Mail *</label>
<input type="email" id="email" name="email" required placeholder="ihre@email.de" />
<label htmlFor="message" style={{ display: 'block', marginBottom: '0.5rem', fontSize: '0.9rem', fontWeight: 600 }}>Nachricht *</label>
<textarea id="message" name="message" required minLength={20} maxLength={4000} rows={6} placeholder="Wie können wir Ihnen helfen?"></textarea>
<div className="visually-hidden">
<label htmlFor="website">Website (bitte leer lassen)</label>
<input type="text" id="website" name="website" tabIndex={-1} autoComplete="off" />
</div>
<button type="submit" disabled={loading} className="cta-button" style={{ alignSelf: 'flex-start' }}>
{loading ? 'Wird gesendet...' : 'Nachricht senden'} <Send size={18} />
</button>
<p style={{ fontSize: '0.75rem', marginTop: '1.5rem', color: 'var(--text-secondary)', lineHeight: 1.4 }}>
* Pflichtfelder. Mit dem Absenden erklären Sie sich mit unserer <Link to="/datenschutz" style={{ textDecoration: 'underline' }}>Datenschutzerklärung</Link> einverstanden.
</p>
</form>
)}
</motion.div>
</div>
</section>
</div>
);
};
export default Contact;

145
src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,145 @@
import { Link } from 'react-router-dom';
import { ArrowRight, Shield, Zap, BarChart3, CheckCircle2 } from 'lucide-react';
const Home = () => (
<div className="home-page">
<section className="hero" style={{ minHeight: '70vh', display: 'flex', alignItems: 'center', background: 'white', position: 'relative', overflow: 'hidden', padding: '4rem 0' }}>
<div style={{ position: 'absolute', top: 0, right: 0, width: '50%', height: '100%', background: '#f1f5f9', clipPath: 'polygon(20% 0, 100% 0, 100% 100%, 0% 100%)', zIndex: 0 }} />
<div className="container" style={{ position: 'relative', zIndex: 1 }}>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', alignItems: 'center', gap: '4rem' }}>
<div>
<span className="badge">Engineering Excellence</span>
<h1 style={{ marginBottom: '1.5rem' }}>Spezialisierter Partner für Energiekabelprojekte</h1>
<p style={{ fontSize: '1.2rem', marginBottom: '2.5rem', color: 'var(--text-secondary)', lineHeight: 1.5, maxWidth: '600px' }}>
Herstellerneutrale technische Beratung und Projektbegleitung für Hochspannungsnetze bis 110 kV.
</p>
<div style={{ display: 'flex', gap: '1.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
<Link to="/kontakt" className="cta-button">
Projekt anfragen <ArrowRight size={18} />
</Link>
<Link to="/ueber-uns" style={{ fontWeight: 700, fontSize: '0.8rem', textTransform: 'uppercase', letterSpacing: '0.1em', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
Mehr erfahren <ArrowRight size={16} />
</Link>
</div>
</div>
<div className="img-placeholder" style={{ height: '400px', marginBottom: 0, boxShadow: '0 20px 40px -10px rgba(0,0,0,0.1)' }}>
[ High-Voltage Infrastructure ]
</div>
</div>
</div>
</section>
<section style={{ background: '#f8fafc' }}>
<div className="container">
<div style={{ marginBottom: '5rem' }}>
<span className="badge">Portfolio</span>
<h2 style={{ border: 'none', padding: 0 }}>Unsere Leistungen</h2>
<p style={{ color: 'var(--text-secondary)', maxWidth: '600px' }}>Präzision und Unabhängigkeit in jeder Phase Ihres Projekts.</p>
</div>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '2.5rem' }}>
{[
{ icon: <Zap size={32} />, title: 'Technische Beratung', desc: 'Individuelle Konzepte und technische Spezifikationen für Ihre Kabelinfrastruktur.' },
{ icon: <Shield size={32} />, title: 'Projektbegleitung', desc: 'Professionelle Überwachung und Qualitätssicherung während der gesamten Ausführung.' },
{ icon: <BarChart3 size={32} />, title: 'Produktbeschaffung', desc: 'Herstellerneutrale Marktanalyse und Unterstützung bei der Komponentenwahl.' }
].map((item, i) => (
<div key={i} className="card">
<div style={{ color: 'var(--accent-green)', marginBottom: '1.5rem' }}>{item.icon}</div>
<h3 style={{ marginBottom: '1rem' }}>{item.title}</h3>
<p style={{ color: 'var(--text-secondary)', lineHeight: 1.6 }}>{item.desc}</p>
</div>
))}
</div>
</div>
</section>
<section style={{ background: 'white' }}>
<div className="container">
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', alignItems: 'center', gap: '4rem' }}>
<div className="img-placeholder" style={{ height: '400px', marginBottom: 0 }}>
[ Technical Drawing / CAD ]
</div>
<div>
<span className="badge">Expertise</span>
<h2>Anwendungen & Zielgruppen</h2>
<p style={{ marginBottom: '2.5rem', color: 'var(--text-secondary)', fontSize: '1.1rem' }}>Wir unterstützen Akteure der Energiewende bei der Realisierung komplexer Kabelprojekte.</p>
<div className="grid" style={{ gridTemplateColumns: '1fr 1fr', gap: '1.5rem' }}>
{[
'Energieversorger',
'Ingenieurbüros',
'Tiefbauunternehmen',
'Industrie',
'Projektierer EE',
'Planungsbüros'
].map((item, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', fontSize: '0.95rem', fontWeight: 600, color: 'var(--primary-color)' }}>
<CheckCircle2 size={18} style={{ color: 'var(--accent-green)' }} />
{item}
</div>
))}
</div>
</div>
</div>
</div>
</section>
<section style={{ background: '#0f172a', color: 'white' }}>
<div className="container">
<div style={{ marginBottom: '5rem' }}>
<span className="badge" style={{ color: 'white', opacity: 0.6 }}>Expertise</span>
<h2 style={{ color: 'white', border: 'none', padding: 0 }}>Technische Spezifikationen</h2>
</div>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '2rem' }}>
{[
{ label: 'Kabeltypen', value: 'N2XS(FL)2Y, N2X(F)KLD2Y, NA2XS(FL)2Y, NA2X(F)KLD2Y', desc: 'Umfassende Expertise in der Spezifikation gängiger Hochspannungskabel.' },
{ label: 'Spannungsebenen', value: '64/110 kV & Mittelspannung', desc: 'Spezialisierte Beratung für die 110-kV-Ebene und komplexe Mittelspannung.' },
{ label: 'Leitertechnologie', value: 'Massiv-, Mehrdraht- & Millikenleiter', desc: 'Optimierung des Leiterdesigns hinsichtlich Stromtragfähigkeit.' }
].map((item, i) => (
<div
key={i}
style={{ background: 'rgba(255,255,255,0.05)', padding: '2.5rem', border: '1px solid rgba(255,255,255,0.1)' }}
>
<h4 style={{ fontSize: '0.75rem', textTransform: 'uppercase', color: 'var(--accent-green)', marginBottom: '1rem', letterSpacing: '0.2em' }}>{item.label}</h4>
<p style={{ fontWeight: 700, fontSize: '1.15rem', marginBottom: '1rem', color: 'white' }}>{item.value}</p>
<p style={{ fontSize: '0.9rem', color: 'rgba(255,255,255,0.6)', lineHeight: 1.6 }}>{item.desc}</p>
</div>
))}
</div>
</div>
</section>
<section style={{ background: 'white' }}>
<div className="container">
<div style={{ marginBottom: '5rem' }}>
<span className="badge">Werte</span>
<h2 style={{ border: 'none', padding: 0 }}>Unsere Leitprinzipien</h2>
</div>
<div className="grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '3rem' }}>
{[
{ title: 'Exzellenz', desc: 'Höchste technische Präzision in jedem Detail. Wir suchen die optimale Lösung jenseits des Standards.' },
{ title: 'Nachhaltigkeit', desc: 'Zukunftssichere Lösungen für die Infrastruktur. Wir denken in Lebenszyklen und Zuverlässigkeit.' },
{ title: 'Transparenz', desc: 'Ehrliche Beratung auf Augenhöhe. Wir kommunizieren klar und herstellerneutral.' }
].map((item, i) => (
<div key={i} style={{ borderLeft: '3px solid var(--accent-green)', paddingLeft: '2rem' }}>
<h3 style={{ marginBottom: '1rem', textTransform: 'uppercase', letterSpacing: '0.1em', fontSize: '1.1rem' }}>{item.title}</h3>
<p style={{ color: 'var(--text-secondary)', lineHeight: 1.7, fontSize: '1rem' }}>{item.desc}</p>
</div>
))}
</div>
</div>
</section>
<section style={{ background: 'var(--primary-color)', padding: '6rem 0', position: 'relative', overflow: 'hidden' }}>
<div className="container" style={{ position: 'relative', zIndex: 1, textAlign: 'center' }}>
<h2 className="no-underline" style={{ color: 'white', border: 'none', padding: 0, marginBottom: '1.5rem', fontSize: 'clamp(1.8rem, 5vw, 2.5rem)' }}>Bereit für Ihr nächstes Projekt?</h2>
<p style={{ color: 'rgba(255,255,255,0.7)', marginBottom: '3rem', fontSize: '1.1rem', maxWidth: '700px', margin: '0 auto 3rem' }}>
Lassen Sie uns gemeinsam die optimale Lösung für Ihre Energieinfrastruktur finden.
</p>
<Link to="/kontakt" className="cta-button" style={{ background: 'white', color: 'var(--primary-color)' }}>
Jetzt Kontakt aufnehmen <ArrowRight size={18} />
</Link>
</div>
</section>
</div>
);
export default Home;

34
src/pages/Legal.tsx Normal file
View File

@@ -0,0 +1,34 @@
const Legal = () => (
<div className="container">
<section>
<h1>Impressum</h1>
<div style={{ maxWidth: '800px' }}>
<p><strong>Angaben gemäß § 5 TMG</strong></p>
<p style={{ marginBottom: '1.5rem' }}>
MB Grid Solutions GmbH<br />
Raiffeisenstraße 22<br />
73630 Remshalden
</p>
<p><strong>Vertreten durch:</strong></p>
<p style={{ marginBottom: '1.5rem' }}>Michael Bodemer</p>
<p><strong>Kontakt:</strong></p>
<p style={{ marginBottom: '1.5rem' }}>
E-Mail: info@mb-grid-solutions.com<br />
Web: www.mb-grid-solutions.com
</p>
<p><strong>Registereintrag:</strong></p>
<p style={{ marginBottom: '1.5rem' }}>
Eintragung im Handelsregister.<br />
Registergericht: Amtsgericht Stuttgart<br />
Registernummer: HRB 798037
</p>
<p><strong>Urheberrecht:</strong></p>
<p>
Alle auf der Website veröffentlichten Texte, Bilder und sonstigen Informationen unterliegen sofern nicht anders gekennzeichnet dem Urheberrecht. Jede Vervielfältigung, Verbreitung, Speicherung, Übermittlung, Wiedergabe bzw. Weitergabe der Inhalte ohne schriftliche Genehmigung ist ausdrücklich untersagt.
</p>
</div>
</section>
</div>
);
export default Legal;

22
src/pages/Privacy.tsx Normal file
View File

@@ -0,0 +1,22 @@
const Privacy = () => (
<div className="container">
<section>
<h1>Datenschutzerklärung</h1>
<div style={{ maxWidth: '800px' }}>
<h2>1. Datenschutz auf einen Blick</h2>
<p style={{ marginBottom: '1.5rem' }}>Wir nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.</p>
<h2>2. Hosting</h2>
<p style={{ marginBottom: '1.5rem' }}>Unsere Website wird bei Hetzner Online GmbH gehostet. Der Serverstandort ist Deutschland. Wir haben einen Vertrag über Auftragsverarbeitung (AVV) mit Hetzner geschlossen.</p>
<h2>3. Kontaktformular</h2>
<p style={{ marginBottom: '1.5rem' }}>Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.</p>
<h2>4. Server-Log-Dateien</h2>
<p style={{ marginBottom: '1.5rem' }}>Der Provider der Seiten erhebt und speichert automatisch Informationen in sogenannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind: Browsertyp und Browserversion, verwendetes Betriebssystem, Referrer URL, Hostname des zugreifenden Rechners, Uhrzeit der Serveranfrage, IP-Adresse.</p>
</div>
</section>
</div>
);
export default Privacy;

21
tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext", "ES2019"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

12
tsconfig.server.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
"outDir": "dist/backend"
},
"include": ["server.ts"]
}

15
vite.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist/frontend',
},
server: {
proxy: {
'/api': 'http://localhost:3000'
}
}
})