Compare commits

...

17 Commits

Author SHA1 Message Date
BauHD 8c3f0ad9d3 chore: add service texts
Deploy / deploy (push) Successful in 34s
2026-07-11 05:54:01 +02:00
BauHD 4b4e618687 chore: add empty pages
Deploy / deploy (push) Successful in 38s
2026-07-11 05:41:09 +02:00
BauHD b10eec4be3 fix: use web network
Deploy / deploy (push) Successful in 33s
2026-07-08 04:53:22 +02:00
BauHD d53dc085aa fix: traefik service name 2026-06-30 22:48:29 +02:00
BauHD 044b703b18 feat: use docker-compose.yml
Deploy / deploy (push) Successful in 32s
2026-06-30 22:13:55 +02:00
BauHD 5f4b6748da feat: switch label
Deploy / deploy (push) Successful in 1m58s
2026-06-30 22:05:12 +02:00
BauHD a3586f5bac feat: add deploy yml
Deploy / deploy (push) Has been cancelled
2026-06-30 21:59:41 +02:00
BauHD f3dc7ea2b2 feat: first about version 2026-06-30 21:41:12 +02:00
BauHD 35549e9890 chore: restructure a bit 2026-05-24 21:55:09 +02:00
BauHD 1673651e22 chore: bump dependencies 2026-05-22 01:46:29 +02:00
BauHD 42553f1d8c fix: copy static with point 2026-02-19 05:23:00 +01:00
BauHD 862d589b88 fix: copy static 2026-02-19 05:18:23 +01:00
BauHD e7bb5e5123 fix: update command 2026-02-19 04:28:55 +01:00
BauHD ff6a177cfa disable telemetry 2026-02-19 04:14:49 +01:00
BauHD 823896bccb optimize Dockerfile 2026-02-19 04:13:16 +01:00
BauHD 24c68d611a Revert "test"
This reverts commit d2c30ed43a.
2026-02-19 04:05:54 +01:00
BauHD d2c30ed43a test 2026-02-19 04:01:10 +01:00
14 changed files with 431 additions and 483 deletions
+14
View File
@@ -0,0 +1,14 @@
name: Deploy
on:
push:
branches:
- production
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: docker compose up -d --build
+4 -4
View File
@@ -1,6 +1,7 @@
# --- Base image --- # --- Base image ---
FROM node:22-alpine AS base FROM node:22-alpine AS base
WORKDIR /app WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
# --- Install dependencies --- # --- Install dependencies ---
FROM base AS deps FROM base AS deps
@@ -24,12 +25,11 @@ RUN addgroup -g 1001 nodejs \
&& adduser -u 1001 -G nodejs -s /bin/sh -D nextjs && adduser -u 1001 -G nodejs -s /bin/sh -D nextjs
# Copy only what is needed for runtime # Copy only what is needed for runtime
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER nextjs USER nextjs
EXPOSE 3000 EXPOSE 3000
CMD ["npm", "start"] CMD ["node", "server.js"]
+18
View File
@@ -0,0 +1,18 @@
services:
kleinholz-site:
build:
context: .
dockerfile: Dockerfile
container_name: kleinholz-site
restart: unless-stopped
networks:
- web
labels:
- "traefik.enable=true"
- "traefik.http.routers.kleinholz.rule=Host(`kleinholz.bauhd.dev`)"
- "traefik.http.routers.kleinholz.entrypoints=websecure"
- "traefik.http.services.kleinholz.loadbalancer.server.port=3000"
networks:
web:
external: true
name: web
+1 -1
View File
@@ -1,7 +1,7 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ output: "standalone"
}; };
export default nextConfig; export default nextConfig;
+267 -333
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -9,9 +9,9 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "16.1.6", "next": "16.2.6",
"react": "19.2.4", "react": "19.2.6",
"react-dom": "19.2.4" "react-dom": "19.2.6"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
+63 -41
View File
@@ -1,51 +1,73 @@
"use client";
import { useEffect, useRef } from "react";
const sections = [ const sections = [
{ { year: "26.10.1966", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." },
title: "Mobilkran", { year: "1972", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." },
text: "Wir glauben an digitale Lösungen, die Menschen verbinden. Technologie soll nicht nur funktionieren, sondern begeistern.", { year: "1972", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." },
image: "/mobilkran.jpg", { year: "1990", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." },
}, { year: "1994", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." },
{ { year: "1996", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." },
title: "Transport", { year: "31.10.2019", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." },
text: "Ein bunter Mix aus Kreativen, Entwicklern und Strategen vereint durch die Leidenschaft für gute Ideen und sauberen Code.", { year: "01.11.2019", text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ullamcorper in ultricies vivamus nulla posuere elit tortor diam curabitur nec mattis commodo malesuada. Adipiscing pellentesque dapibus mattis dictumst dapibus ullamcorper elementum placerat ultricies quis bibendum aenean non." }
image: "/transport.jpg",
},
{
title: "ADAC Abschleppdienst",
text: "Transparenz, Empathie und Nachhaltigkeit sind keine Buzzwords für uns, sondern gelebte Prinzipien in jedem Projekt.",
image: "/abschleppdienst.jpg",
},
]; ];
export default function About() { export default function Page() {
const refs = useRef<(HTMLDivElement | null)[]>([]);
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("opacity-100", "translate-y-0");
}
});
},
{ threshold: 0.2 }
);
refs.current.forEach(el => el && observer.observe(el));
return () => observer.disconnect();
}, []);
return ( return (
<div className="bg-white py-20 px-6 lg:px-24"> <main className="min-h-screen bg-gray-100 py-10">
<h1 className="text-4xl font-bold text-center mb-20 text-gray-800">Über uns</h1> <h1 className="text-4xl font-bold text-center mb-20 text-gray-800">
Über uns
</h1>
<div className="space-y-28"> <div className="relative w-full flex justify-center py-20">
{sections.map((section, index) => ( <div className="relative w-full max-w-3xl">
<div
key={index}
className={`flex flex-col lg:flex-row ${
index % 2 === 1 ? "lg:flex-row-reverse" : ""
} items-center gap-12`}
>
{/* Bild */}
<div className="w-full lg:w-1/2">
<img
src={section.image}
alt={section.title}
className="w-full h-auto rounded-xl shadow-xl object-cover"
/>
</div>
{/* Text */} {/* Mittlere Linie */}
<div className="w-full lg:w-1/2"> <div className="absolute inset-0 flex justify-center">
<h2 className="text-3xl font-semibold text-gray-700 mb-4">{section.title}</h2> <div className="w-1 bg-gray-300"></div>
<p className="text-gray-600 text-lg leading-relaxed">{section.text}</p>
</div>
</div> </div>
))}
{sections.map((section, index) => (
<div
key={index}
ref={(el: HTMLDivElement | null): void => {
refs.current[index] = el;
}}
className="relative mb-20 opacity-0 translate-y-10 transition-all duration-700"
>
{/* Jahr */}
<div className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white text-[var(--color-primary)] px-6 py-2 text-3xl font-bold shadow z-10">
{section.year}
</div>
{/* Text */}
<div className="mt-20 text-center">
<p className="text-gray-700 text-lg">{section.text}</p>
</div>
</div>
))}
</div>
</div> </div>
</div> </main>
); );
} }
-10
View File
@@ -1,10 +0,0 @@
"use client"
import ContactForm from "@/components/ContactForm";
export default function Page() {
return (
<main className="min-h-screen bg-gray-100 py-10">
<ContactForm/>
</main>
);
};
+3
View File
@@ -0,0 +1,3 @@
export default function Page() {
return <></>
}
+1 -1
View File
@@ -17,7 +17,7 @@ export default function Home() {
</a> </a>
</section> </section>
</main> </main>
<div className="w-full h-[400px]"> <div className="w-full h-100">
<iframe <iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2489.1370108126625!2d6.875406576840839!3d51.400537171789225!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x47b8c14b986f8f25%3A0x8cc0d4e323228ee8!2sAdac%20Mobilit%C3%A4tspartner%20Kleinholz!5e0!3m2!1sde!2sde!4v1755638002671!5m2!1sde!2sde" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2489.1370108126625!2d6.875406576840839!3d51.400537171789225!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x47b8c14b986f8f25%3A0x8cc0d4e323228ee8!2sAdac%20Mobilit%C3%A4tspartner%20Kleinholz!5e0!3m2!1sde!2sde!4v1755638002671!5m2!1sde!2sde"
width="100%" width="100%"
+3
View File
@@ -0,0 +1,3 @@
export default function Page() {
return <></>
}
+51
View File
@@ -0,0 +1,51 @@
const sections = [
{
title: "Mobilkran",
text: "Ob Baustelleneinrichtung, Montagearbeiten oder Schwerlasttransport mit unseren Mobilkranen bewegen wir sicher und präzise, was andere nicht heben können. Unser erfahrenes Kranfahrer-Team sorgt für reibungslose Abläufe, kurze Standzeiten und höchste Sicherheitsstandards auf jeder Baustelle.",
image: "/mobilkran.jpg",
},
{
title: "Transport",
text: "Wir transportieren Ihre Güter, Maschinen und Baustoffe zuverlässig ans Ziel. Mit moderner Fahrzeugflotte und langjähriger Erfahrung im Schwer- und Spezialtransport sind wir Ihr Partner für logistische Herausforderungen jeder Größenordnung.",
image: "/transport.jpg",
},
{
title: "ADAC Abschleppdienst",
text: "Als zertifizierter ADAC-Partnerbetrieb stehen wir Ihnen bei Pannen und Bergungen kompetent und schnell zur Seite. Vertrauen Sie auf geprüfte Qualität und die Sicherheit, die eine ADAC-Partnerschaft mit sich bringt.",
image: "/abschleppdienst.jpg",
},
];
export default function Services() {
return (
<div className="bg-white py-20 px-6 lg:px-24">
<h1 className="text-4xl font-bold text-center mb-20 text-gray-800">Dienstleistungen</h1>
<div className="space-y-28">
{sections.map((section, index) => (
<div
key={index}
className={`flex flex-col lg:flex-row ${
index % 2 === 1 ? "lg:flex-row-reverse" : ""
} items-center gap-12`}
>
{/* Bild */}
<div className="w-full lg:w-1/2">
<img
src={section.image}
alt={section.title}
className="w-full h-auto rounded-xl shadow-xl object-cover"
/>
</div>
{/* Text */}
<div className="w-full lg:w-1/2">
<h2 className="text-3xl font-semibold text-gray-700 mb-4">{section.title}</h2>
<p className="text-gray-600 text-lg leading-relaxed">{section.text}</p>
</div>
</div>
))}
</div>
</div>
);
}
-87
View File
@@ -1,87 +0,0 @@
// components/ContactForm.tsx
import { useState } from 'react';
export default function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
});
const [submitted, setSubmitted] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Hier kannst du die Daten an dein Backend oder einen Dienst senden
console.log('Formulardaten:', formData);
setSubmitted(true);
};
return (
<div className="max-w-xl mx-auto p-6 bg-white shadow-md rounded-md">
<h2 className="text-2xl font-semibold mb-4">Kontaktformular</h2>
{submitted ? (
<p className="text-green-600">Vielen Dank für deine Nachricht!</p>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Name
</label>
<input
type="text"
name="name"
id="name"
required
value={formData.name}
onChange={handleChange}
className="mt-1 block w-full border border-gray-300 rounded-md p-2"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
E-Mail
</label>
<input
type="email"
name="email"
id="email"
required
value={formData.email}
onChange={handleChange}
className="mt-1 block w-full border border-gray-300 rounded-md p-2"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700">
Nachricht
</label>
<textarea
name="message"
id="message"
rows={5}
required
value={formData.message}
onChange={handleChange}
className="mt-1 block w-full border border-gray-300 rounded-md p-2"
/>
</div>
<button
type="submit"
className="bg-[var(--color-primary)] text-white px-4 py-2 rounded hover:bg-[var(--color-primary)] transition"
>
Absenden
</button>
</form>
)}
</div>
);
}
+3 -3
View File
@@ -9,13 +9,13 @@ const Navbar: React.FC = () => {
<nav className="bg-gray-800 text-white"> <nav className="bg-gray-800 text-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16 items-center"> <div className="flex justify-between h-16 items-center">
<div className="flex-shrink-0"> <div className="shrink-0">
<Link href="/" className="text-[var(--color-primary)] text-xl font-bold">MKV Kleinholz</Link> <Link href="/" className="text-[var(--color-primary)] text-xl font-bold">MKV Kleinholz</Link>
</div> </div>
<div className="hidden md:flex space-x-4"> <div className="hidden md:flex space-x-4">
<Link href="/" className="hover:text-gray-300">Home</Link> <Link href="/" className="hover:text-gray-300">Home</Link>
<Link href="/services" className="hover:text-gray-300">Dienstleistungen</Link>
<Link href="/about" className="hover:text-gray-300">Über uns</Link> <Link href="/about" className="hover:text-gray-300">Über uns</Link>
<Link href="/contact" className="hover:text-gray-300">Kontakt</Link>
</div> </div>
<div className="md:hidden"> <div className="md:hidden">
<button <button
@@ -31,8 +31,8 @@ const Navbar: React.FC = () => {
{isOpen && ( {isOpen && (
<div className="md:hidden px-4 pb-4 space-y-2"> <div className="md:hidden px-4 pb-4 space-y-2">
<Link href="/" className="block hover:text-gray-300">Home</Link> <Link href="/" className="block hover:text-gray-300">Home</Link>
<Link href="/services" className="block hover:text-gray-300">Dienstleistungen</Link>
<Link href="/about" className="block hover:text-gray-300">Über uns</Link> <Link href="/about" className="block hover:text-gray-300">Über uns</Link>
<Link href="/contact" className="block hover:text-gray-300">Kontakt</Link>
</div> </div>
)} )}
</nav> </nav>