Compare commits

..

3 Commits

Author SHA1 Message Date
Basilosaurusrex
6af24d28e7 impressum 2026-02-02 22:03:15 +01:00
Basilosaurusrex
217bbdc6a7 new version 2026-02-02 21:42:41 +01:00
Basilosaurusrex
7e8d40878b light 2026-02-02 16:17:08 +01:00
16 changed files with 1205 additions and 29 deletions

7
package-lock.json generated
View File

@@ -48,6 +48,7 @@
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"motion": "^12.29.2", "motion": "^12.29.2",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"ogl": "^1.0.11",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@@ -6590,6 +6591,12 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/ogl": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz",
"integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==",
"license": "Unlicense"
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",

View File

@@ -53,6 +53,7 @@
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"motion": "^12.29.2", "motion": "^12.29.2",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"ogl": "^1.0.11",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -5,6 +5,8 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index"; import Index from "./pages/Index";
import ContactPage from "./pages/Contact"; import ContactPage from "./pages/Contact";
import AGBPage from "./pages/AGB";
import ImpressumPage from "./pages/Impressum";
import NotFound from "./pages/NotFound"; import NotFound from "./pages/NotFound";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@@ -18,6 +20,8 @@ const App = () => (
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<Index />} />
<Route path="/kontakt" element={<ContactPage />} /> <Route path="/kontakt" element={<ContactPage />} />
<Route path="/agb" element={<AGBPage />} />
<Route path="/impressum" element={<ImpressumPage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Routes> </Routes>

View File

@@ -36,13 +36,13 @@ const Contact = () => {
{/* Contact Info */} {/* Contact Info */}
<div className="divider mb-12" /> <div className="divider mb-12" />
<div className="flex flex-col sm:flex-row gap-8 text-muted-foreground"> <div className="flex flex-col sm:flex-row gap-8 text-muted-foreground">
<a href="mailto:hello@webklar.de" className="flex items-center gap-3 hover:text-foreground transition-colors group"> <a href="mailto:support@webklar.com" className="flex items-center gap-3 hover:text-foreground transition-colors group">
<Mail className="w-5 h-5" /> <Mail className="w-5 h-5" />
<span>hello@webklar.de</span> <span>support@webklar.com</span>
</a> </a>
<a href="tel:+4912345678" className="flex items-center gap-3 hover:text-foreground transition-colors group"> <a href="tel:+491704969375" className="flex items-center gap-3 hover:text-foreground transition-colors group">
<Phone className="w-5 h-5" /> <Phone className="w-5 h-5" />
<span>+49 123 456 78</span> <span>0170 4969375</span>
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { ArrowRight } from "lucide-react"; import { ArrowRight } from "lucide-react";
import { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } from "react";
import Silk from "@/components/Silk"; import Silk from "@/components/Silk";
import CountUp from "@/components/CountUp"; import CountUp from "@/components/CountUp";
@@ -15,10 +15,28 @@ const SPARKLE_SVG = (
); );
function DemoButtonLetters({ text }: { text: string }) { function DemoButtonLetters({ text }: { text: string }) {
// #region agent log
const chars = text.split("");
const spaceIndex = chars.findIndex((c) => c === " ");
const lastIndex = chars.length - 1;
const lastChar = chars[lastIndex];
fetch("http://127.0.0.1:7244/ingest/72f53105-0a54-4d4c-a295-fb93aa72afcc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
location: "Hero.tsx:DemoButtonLetters",
message: "Letter split for button text",
data: { text, len: chars.length, spaceIndex, spaceChar: spaceIndex >= 0 ? chars[spaceIndex] : null, lastIndex, lastChar },
timestamp: Date.now(),
sessionId: "debug-session",
hypothesisId: "A,C",
}),
}).catch(() => {});
// #endregion
return ( return (
<> <>
{text.split("").map((char, i) => ( {chars.map((char, i) => (
<span key={i} className="btn-letter"> <span key={i} className={char === " " ? "btn-letter btn-letter-space" : "btn-letter"}>
{char} {char}
</span> </span>
))} ))}
@@ -31,6 +49,31 @@ const FOUNDING_DATE = new Date("2026-01-25"); // Samstag, 25. Januar 2026
const Hero = () => { const Hero = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [companyAge, setCompanyAge] = useState(""); const [companyAge, setCompanyAge] = useState("");
const secondBtnRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
const el = secondBtnRef.current;
if (!el) return;
const firstTxtWrapper = el.querySelector(".txt-wrapper");
const letters = firstTxtWrapper ? firstTxtWrapper.querySelectorAll(".btn-letter") : [];
const spaceIdx = 8;
const lastIdx = 16;
const wSpace = letters[spaceIdx]?.getBoundingClientRect?.()?.width ?? -1;
const wLast = letters[lastIdx]?.getBoundingClientRect?.()?.width ?? -1;
fetch("http://127.0.0.1:7244/ingest/72f53105-0a54-4d4c-a295-fb93aa72afcc", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
location: "Hero.tsx:useEffect:measure",
message: "Measured btn-letter widths (space + last)",
data: { letterCount: letters.length, wSpace, wLast, spaceIdx, lastIdx },
timestamp: Date.now(),
sessionId: "debug-session",
runId: "post-fix",
hypothesisId: "B,D,E",
}),
}).catch(() => {});
}, []);
useEffect(() => { useEffect(() => {
const calculateAge = () => { const calculateAge = () => {
@@ -63,7 +106,7 @@ const Hero = () => {
<Silk <Silk
speed={3} speed={3}
scale={0.5} scale={0.5}
color="#373737" color="#6a6a6a"
noiseIntensity={4 noiseIntensity={4
} }
rotation={0} rotation={0}
@@ -113,21 +156,26 @@ const Hero = () => {
</div> </div>
<div className="btn-wrapper w-full sm:w-auto"> <div className="btn-wrapper w-full sm:w-auto">
<button <button
ref={secondBtnRef}
type="button" type="button"
className="btn w-full sm:w-auto justify-center" className="btn w-full sm:w-auto justify-center"
onClick={() => navigate("/kontakt")} onClick={() => {
aria-label="System-Demo anfordern" const el = document.getElementById("projects");
if (el) el.scrollIntoView({ behavior: "smooth" });
else navigate("/#projects");
}}
aria-label="Projekte ansehen"
> >
{SPARKLE_SVG} {SPARKLE_SVG}
<div className="txt-wrapper"> <div className="txt-wrapper">
<span className="txt-width-helper" aria-hidden="true"> <span className="txt-width-helper" aria-hidden="true">
<DemoButtonLetters text="System-Demo anfordern" /> <DemoButtonLetters text="Projekte ansehen" />
</span> </span>
<div className="txt-1"> <div className="txt-1">
<DemoButtonLetters text="System-Demo anfordern" /> <DemoButtonLetters text="Projekte ansehen" />
</div> </div>
<div className="txt-2"> <div className="txt-2">
<DemoButtonLetters text="Wird angefordert..." /> <DemoButtonLetters text="Wird geladen..." />
</div> </div>
</div> </div>
</button> </button>

View File

@@ -0,0 +1,16 @@
.light-rays-container {
width: 100%;
height: 100%;
position: relative;
pointer-events: none;
z-index: 3;
overflow: hidden;
}
.light-rays-fallback {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}

View File

@@ -0,0 +1,486 @@
"use client";
import { useRef, useEffect, useState } from "react";
// @ts-expect-error ogl has no type definitions
import { Renderer, Program, Triangle, Mesh } from "ogl";
import "./LightRays.css";
const DEFAULT_COLOR = "#ffffff";
const hexToRgb = (hex: string): [number, number, number] => {
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return m
? [
parseInt(m[1], 16) / 255,
parseInt(m[2], 16) / 255,
parseInt(m[3], 16) / 255,
]
: [1, 1, 1];
};
type RaysOrigin =
| "top-left"
| "top-right"
| "top-center"
| "left"
| "right"
| "bottom-left"
| "bottom-center"
| "bottom-right";
const getAnchorAndDir = (
origin: RaysOrigin,
w: number,
h: number
): { anchor: [number, number]; dir: [number, number] } => {
const outside = 0.2;
switch (origin) {
case "top-left":
return { anchor: [0, -outside * h], dir: [0, 1] };
case "top-right":
return { anchor: [w, -outside * h], dir: [0, 1] };
case "left":
return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] };
case "right":
return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] };
case "bottom-left":
return { anchor: [0, (1 + outside) * h], dir: [0, -1] };
case "bottom-center":
return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] };
case "bottom-right":
return { anchor: [w, (1 + outside) * h], dir: [0, -1] };
default:
return { anchor: [0.5 * w, -outside * h], dir: [0, 1] };
}
};
export interface LightRaysProps {
raysOrigin?: RaysOrigin;
raysColor?: string;
raysSpeed?: number;
lightSpread?: number;
rayLength?: number;
pulsating?: boolean;
fadeDistance?: number;
saturation?: number;
followMouse?: boolean;
mouseInfluence?: number;
noiseAmount?: number;
distortion?: number;
className?: string;
}
export default function LightRays({
raysOrigin = "top-center",
raysColor = DEFAULT_COLOR,
raysSpeed = 1,
lightSpread = 1,
rayLength = 2,
pulsating = false,
fadeDistance = 1.0,
saturation = 1.0,
followMouse = true,
mouseInfluence = 0.1,
noiseAmount = 0.0,
distortion = 0.0,
className = "",
}: LightRaysProps) {
const containerRef = useRef<HTMLDivElement>(null);
const uniformsRef = useRef<Record<string, { value: unknown }> | null>(null);
const rendererRef = useRef<InstanceType<typeof Renderer> | null>(null);
const mouseRef = useRef({ x: 0.5, y: 0.5 });
const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });
const animationIdRef = useRef<number | null>(null);
const meshRef = useRef<InstanceType<typeof Mesh> | null>(null);
const cleanupFunctionRef = useRef<(() => void) | null>(null);
const [isVisible, setIsVisible] = useState(false);
const [useFallback, setUseFallback] = useState(false);
const observerRef = useRef<IntersectionObserver | null>(null);
useEffect(() => {
if (!containerRef.current) return;
observerRef.current = new IntersectionObserver(
(entries) => {
const entry = entries[0];
setIsVisible(entry.isIntersecting);
},
{ threshold: 0.1 }
);
observerRef.current.observe(containerRef.current);
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = null;
}
};
}, []);
useEffect(() => {
if (!isVisible || !containerRef.current) return;
setUseFallback(false);
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
const initializeWebGL = async () => {
if (!containerRef.current) return;
await new Promise((resolve) => setTimeout(resolve, 10));
if (!containerRef.current) return;
const isMobile =
typeof window !== "undefined" &&
(window.innerWidth <= 768 || "ontouchstart" in window);
const dpr = isMobile ? 1 : Math.min(window.devicePixelRatio, 2);
try {
const renderer = new Renderer({
dpr,
alpha: true,
});
rendererRef.current = renderer;
const gl = renderer.gl;
gl.canvas.style.width = "100%";
gl.canvas.style.height = "100%";
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
containerRef.current.appendChild(gl.canvas);
const vert = `
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}`;
const frag = `precision mediump float;
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 rayPos;
uniform vec2 rayDir;
uniform vec3 raysColor;
uniform float raysSpeed;
uniform float lightSpread;
uniform float rayLength;
uniform float pulsating;
uniform float fadeDistance;
uniform float saturation;
uniform vec2 mousePos;
uniform float mouseInfluence;
uniform float noiseAmount;
uniform float distortion;
varying vec2 vUv;
float noise(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord,
float seedA, float seedB, float speed) {
vec2 sourceToCoord = coord - raySource;
vec2 dirNorm = normalize(sourceToCoord);
float cosAngle = dot(dirNorm, rayRefDirection);
float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2;
float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));
float distance = length(sourceToCoord);
float maxDistance = iResolution.x * rayLength;
float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);
float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0);
float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0;
float baseStrength = clamp(
(0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +
(0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),
0.0, 1.0
);
return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
vec2 finalRayDir = rayDir;
if (mouseInfluence > 0.0) {
vec2 mouseScreenPos = mousePos * iResolution.xy;
vec2 mouseDirection = normalize(mouseScreenPos - rayPos);
finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));
}
vec4 rays1 = vec4(1.0) *
rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349,
1.5 * raysSpeed);
vec4 rays2 = vec4(1.0) *
rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234,
1.1 * raysSpeed);
fragColor = rays1 * 0.5 + rays2 * 0.4;
if (noiseAmount > 0.0) {
float n = noise(coord * 0.01 + iTime * 0.1);
fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n);
}
float brightness = 1.0 - (coord.y / iResolution.y);
fragColor.x *= 0.1 + brightness * 0.8;
fragColor.y *= 0.3 + brightness * 0.6;
fragColor.z *= 0.5 + brightness * 0.5;
if (saturation != 1.0) {
float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation);
}
fragColor.rgb *= raysColor;
}
void main() {
vec4 color;
mainImage(color, gl_FragCoord.xy);
gl_FragColor = color;
}`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: [1, 1] as [number, number] },
rayPos: { value: [0, 0] as [number, number] },
rayDir: { value: [0, 1] as [number, number] },
raysColor: { value: hexToRgb(raysColor) },
raysSpeed: { value: raysSpeed },
lightSpread: { value: lightSpread },
rayLength: { value: rayLength },
pulsating: { value: pulsating ? 1.0 : 0.0 },
fadeDistance: { value: fadeDistance },
saturation: { value: saturation },
mousePos: { value: [0.5, 0.5] as [number, number] },
mouseInfluence: { value: mouseInfluence },
noiseAmount: { value: noiseAmount },
distortion: { value: distortion },
};
uniformsRef.current = uniforms as Record<string, { value: unknown }>;
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vert,
fragment: frag,
uniforms,
});
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const updatePlacement = () => {
if (!containerRef.current || !renderer) return;
renderer.dpr = isMobile ? 1 : Math.min(window.devicePixelRatio, 2);
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
renderer.setSize(wCSS, hCSS);
const dpr = renderer.dpr;
const w = wCSS * dpr;
const h = hCSS * dpr;
uniforms.iResolution.value = [w, h];
const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h);
uniforms.rayPos.value = anchor;
uniforms.rayDir.value = dir;
};
const loop = (t: number) => {
if (!rendererRef.current || !uniformsRef.current || !meshRef.current) {
return;
}
const uniforms = uniformsRef.current as typeof uniforms;
uniforms.iTime.value = t * 0.001;
if (followMouse && mouseInfluence > 0.0) {
const smoothing = 0.92;
smoothMouseRef.current.x =
smoothMouseRef.current.x * smoothing +
mouseRef.current.x * (1 - smoothing);
smoothMouseRef.current.y =
smoothMouseRef.current.y * smoothing +
mouseRef.current.y * (1 - smoothing);
uniforms.mousePos.value = [
smoothMouseRef.current.x,
smoothMouseRef.current.y,
];
}
try {
renderer.render({ scene: mesh });
animationIdRef.current = requestAnimationFrame(loop);
} catch (error) {
console.warn("WebGL rendering error:", error);
return;
}
};
window.addEventListener("resize", updatePlacement);
const resizeObserver =
typeof ResizeObserver !== "undefined" &&
new ResizeObserver(() => updatePlacement());
if (resizeObserver && containerRef.current) {
resizeObserver.observe(containerRef.current);
}
updatePlacement();
animationIdRef.current = requestAnimationFrame(loop);
cleanupFunctionRef.current = () => {
if (resizeObserver && containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
if (animationIdRef.current) {
cancelAnimationFrame(animationIdRef.current);
animationIdRef.current = null;
}
window.removeEventListener("resize", updatePlacement);
if (renderer) {
try {
const canvas = renderer.gl.canvas;
const loseContextExt =
renderer.gl.getExtension("WEBGL_lose_context");
if (loseContextExt) {
loseContextExt.loseContext();
}
if (canvas && canvas.parentNode) {
canvas.parentNode.removeChild(canvas);
}
} catch (error) {
console.warn("Error during WebGL cleanup:", error);
}
}
rendererRef.current = null;
uniformsRef.current = null;
meshRef.current = null;
};
} catch (error) {
console.warn("LightRays WebGL init failed (e.g. on mobile):", error);
setUseFallback(true);
}
};
initializeWebGL();
return () => {
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
};
}, [
isVisible,
raysOrigin,
raysColor,
raysSpeed,
lightSpread,
rayLength,
pulsating,
fadeDistance,
saturation,
followMouse,
mouseInfluence,
noiseAmount,
distortion,
]);
useEffect(() => {
if (!uniformsRef.current || !containerRef.current || !rendererRef.current)
return;
const u = uniformsRef.current as Record<string, { value: unknown }>;
const renderer = rendererRef.current;
u.raysColor.value = hexToRgb(raysColor);
u.raysSpeed.value = raysSpeed;
u.lightSpread.value = lightSpread;
u.rayLength.value = rayLength;
u.pulsating.value = pulsating ? 1.0 : 0.0;
u.fadeDistance.value = fadeDistance;
u.saturation.value = saturation;
u.mouseInfluence.value = mouseInfluence;
u.noiseAmount.value = noiseAmount;
u.distortion.value = distortion;
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
const dpr = renderer.dpr;
const { anchor, dir } = getAnchorAndDir(
raysOrigin,
wCSS * dpr,
hCSS * dpr
);
u.rayPos.value = anchor;
u.rayDir.value = dir;
}, [
raysColor,
raysSpeed,
lightSpread,
raysOrigin,
rayLength,
pulsating,
fadeDistance,
saturation,
mouseInfluence,
noiseAmount,
distortion,
]);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!containerRef.current || !rendererRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
mouseRef.current = { x, y };
};
if (followMouse) {
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}
}, [followMouse]);
return (
<div
ref={containerRef}
className={`light-rays-container ${className}`.trim()}
>
{useFallback && (
<div
className="light-rays-fallback"
style={{
background: `linear-gradient(to bottom, ${raysColor}50 0%, ${raysColor}20 25%, ${raysColor}08 50%, transparent 85%)`,
}}
aria-hidden
/>
)}
</div>
);
}

View File

@@ -1,4 +1,6 @@
import { Calendar, MessageSquareOff, TrendingDown, Folders } from "lucide-react"; import { Calendar, MessageSquareOff, TrendingDown, Folders } from "lucide-react";
import { LampTop } from "@/components/ui/lamp";
import LightRays from "@/components/LightRays";
const ProblemSection = () => { const ProblemSection = () => {
const problems = [ const problems = [
@@ -21,8 +23,25 @@ const ProblemSection = () => {
]; ];
return ( return (
<section className="py-24 md:py-32 bg-background relative"> <section className="section-problem-solution py-24 md:py-32 relative overflow-hidden">
<div className="container mx-auto px-6"> <div className="absolute inset-0 w-full overflow-hidden z-0">
<LightRays
raysOrigin="top-center"
raysColor="#ef4444"
raysSpeed={1}
lightSpread={0.5}
rayLength={3}
followMouse={false}
mouseInfluence={0}
noiseAmount={0}
distortion={0}
pulsating
fadeDistance={2}
saturation={2}
/>
</div>
<LampTop />
<div className="container mx-auto px-6 relative z-10">
{/* Section Header */} {/* Section Header */}
<div className="mb-16 md:mb-20 max-w-4xl"> <div className="mb-16 md:mb-20 max-w-4xl">
<div className="label-tag mb-4">Das Problem</div> <div className="label-tag mb-4">Das Problem</div>
@@ -39,7 +58,7 @@ const ProblemSection = () => {
{problems.map((problem, index) => ( {problems.map((problem, index) => (
<div <div
key={index} key={index}
className="flex items-start gap-4 p-6 border border-border rounded-lg bg-card/50 hover:border-foreground/20 transition-colors" className="problem-section-tint flex items-start gap-4 p-6 border border-border rounded-lg bg-card/50 hover:border-foreground/20 transition-colors"
> >
<div className="w-10 h-10 rounded-full border border-destructive/30 bg-destructive/10 flex items-center justify-center flex-shrink-0"> <div className="w-10 h-10 rounded-full border border-destructive/30 bg-destructive/10 flex items-center justify-center flex-shrink-0">
<problem.icon className="w-5 h-5 text-destructive" /> <problem.icon className="w-5 h-5 text-destructive" />

View File

@@ -1,30 +1,42 @@
import { ArrowUpRight } from "lucide-react"; import { ArrowUpRight } from "lucide-react";
const projects = [ type Project = {
title: string;
description: string;
image: string;
url: string;
};
const projects: Project[] = [
{ {
title: "Triple AI", title: "Email Sorter",
description: "Webentwicklung / UI Design / Custom Code", description: "E-Mails automatisch sortieren",
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&h=600&fit=crop", image: "/project%20pics/emailsorter.png",
url: "https://emailsorter.webklar.com/",
}, },
{ {
title: "Neutral", title: "Neutral",
description: "Webentwicklung / Custom Code", description: "Webentwicklung / Custom Code",
image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&h=600&fit=crop",
url: "#",
}, },
{ {
title: "Verbatim Labs", title: "Verbatim Labs",
description: "Webentwicklung / UI Design / Custom Code", description: "Webentwicklung / UI Design / Custom Code",
image: "https://images.unsplash.com/photo-1559028012-481c04fa702d?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1559028012-481c04fa702d?w=800&h=600&fit=crop",
url: "#",
}, },
{ {
title: "JMK Engineers", title: "JMK Engineers",
description: "Webentwicklung / UI Design / Custom Code", description: "Webentwicklung / UI Design / Custom Code",
image: "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?w=800&h=600&fit=crop",
url: "#",
}, },
{ {
title: "GOODZ Club", title: "GOODZ Club",
description: "Webentwicklung / Custom Code / Lokalisierung", description: "Webentwicklung / Custom Code / Lokalisierung",
image: "https://images.unsplash.com/photo-1542744094-3a31f272c490?w=800&h=600&fit=crop", image: "https://images.unsplash.com/photo-1542744094-3a31f272c490?w=800&h=600&fit=crop",
url: "#",
}, },
]; ];
@@ -45,7 +57,9 @@ const ProjectShowcase = () => {
{projects.map((project, index) => ( {projects.map((project, index) => (
<a <a
key={project.title} key={project.title}
href="#" href={project.url}
target={project.url.startsWith("http") ? "_blank" : undefined}
rel={project.url.startsWith("http") ? "noopener noreferrer" : undefined}
className="group block project-card rounded-lg p-6 md:p-8" className="group block project-card rounded-lg p-6 md:p-8"
style={{ animationDelay: `${index * 0.1}s` }} style={{ animationDelay: `${index * 0.1}s` }}
> >

View File

@@ -1,6 +1,8 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ArrowRight, CheckCircle2 } from "lucide-react"; import { ArrowRight, CheckCircle2 } from "lucide-react";
import { LampTop } from "@/components/ui/lamp";
import LightRays from "@/components/LightRays";
const SolutionSection = () => { const SolutionSection = () => {
const benefits = [ const benefits = [
@@ -10,8 +12,25 @@ const SolutionSection = () => {
]; ];
return ( return (
<section className="py-24 md:py-32 bg-background relative"> <section className="section-problem-solution py-24 md:py-32 relative overflow-hidden">
<div className="container mx-auto px-6"> <div className="absolute inset-0 w-full overflow-hidden z-0">
<LightRays
raysOrigin="top-center"
raysColor="#22d3ee"
raysSpeed={1}
lightSpread={0.5}
rayLength={3}
followMouse={false}
mouseInfluence={0}
noiseAmount={0}
distortion={0}
pulsating
fadeDistance={2}
saturation={2}
/>
</div>
<LampTop lineClassName="bg-cyan-400" />
<div className="container mx-auto px-6 relative z-10">
<div className="grid lg:grid-cols-2 gap-16 items-center"> <div className="grid lg:grid-cols-2 gap-16 items-center">
{/* Left Content */} {/* Left Content */}
<div> <div>
@@ -47,7 +66,7 @@ const SolutionSection = () => {
{/* Right Content - Visual Element */} {/* Right Content - Visual Element */}
<div className="relative"> <div className="relative">
<div className="aspect-square bg-secondary/50 rounded-2xl border border-border p-8 md:p-12 flex flex-col justify-center"> <div className="solution-section-tint aspect-square bg-secondary/50 rounded-2xl border border-border p-8 md:p-12 flex flex-col justify-center">
<div className="space-y-6"> <div className="space-y-6">
<div className="text-sm uppercase tracking-wider text-muted-foreground">Das Ergebnis</div> <div className="text-sm uppercase tracking-wider text-muted-foreground">Das Ergebnis</div>
<h3 className="text-2xl md:text-3xl font-display font-medium text-foreground uppercase tracking-tight"> <h3 className="text-2xl md:text-3xl font-display font-medium text-foreground uppercase tracking-tight">

View File

@@ -0,0 +1,39 @@
"use client";
import React from "react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
const S = 2.5;
export const LampTop = ({
className,
lineClassName = "bg-red-500",
children,
}: {
className?: string;
lineClassName?: string;
children?: React.ReactNode;
}) => {
return (
<div
className={cn(
"absolute top-0 left-0 right-0 w-full min-h-0 pointer-events-none z-50 flex items-start justify-center",
className
)}
>
<motion.div
initial={{ width: `${15 * S}rem` }}
whileInView={{ width: `${30 * S}rem` }}
transition={{
delay: 0.3,
duration: 0.8,
ease: "easeInOut",
}}
className={cn("absolute top-0 left-1/2 -translate-x-1/2 h-0.5", lineClassName)}
style={{ width: `${30 * S}rem` }}
/>
{children}
</div>
);
};

View File

@@ -154,6 +154,37 @@
} }
@layer components { @layer components {
/* Gemeinsamer Hintergrund für Problem- und Lösungs-Sektion */
.section-problem-solution {
background-color: hsl(var(--background));
}
/* Leichter roter Tint auf Inhaltsblöcken der Problem-Sektion */
.problem-section-tint {
position: relative;
}
.problem-section-tint::before {
content: "";
position: absolute;
inset: 0;
background-color: rgb(239 68 68 / 0.06);
pointer-events: none;
border-radius: inherit;
}
/* Leichter blauer Tint auf dem Ergebnis-Block der Lösungs-Sektion */
.solution-section-tint {
position: relative;
}
.solution-section-tint::before {
content: "";
position: absolute;
inset: 0;
background-color: rgb(34 211 238 / 0.06);
pointer-events: none;
border-radius: inherit;
}
/* Minimal glass nav */ /* Minimal glass nav */
.glass-nav { .glass-nav {
@apply backdrop-blur-xl border-b; @apply backdrop-blur-xl border-b;
@@ -370,7 +401,7 @@
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
min-width: 0; min-width: 0;
padding-right: 2px; padding-right: 6px;
} }
.txt-width-helper { .txt-width-helper {
@@ -385,6 +416,10 @@
animation: none; animation: none;
} }
.btn-letter-space {
min-width: 0.25em;
}
.txt-1, .txt-1,
.txt-2 { .txt-2 {
position: absolute; position: absolute;

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

@@ -0,0 +1,292 @@
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowLeft, FileText } from "lucide-react";
const contractDivider = (
<div className="my-8 border-t border-border" aria-hidden />
);
const AGB = () => {
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 glass-nav py-4">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">
Webklar
</span>
</Link>
<Link to="/">
<Button
variant="ghost"
className="text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Zurück
</Button>
</Link>
</div>
</div>
</header>
{/* Main Content */}
<main className="pt-32 pb-24">
<div className="container mx-auto px-6">
<div className="max-w-3xl mx-auto">
{/* Page Header */}
<div className="mb-12">
<div className="label-tag mb-4 flex items-center gap-2">
<FileText className="w-4 h-4" />
Vertrag
</div>
<h1 className="text-4xl md:text-5xl font-display font-medium text-foreground tracking-tight uppercase mb-2">
Kaufvertrag WEBklar
</h1>
<p className="text-muted-foreground text-lg mb-8">
(Modularer Projektvertrag)
</p>
<div className="text-muted-foreground space-y-1">
<p><strong className="text-foreground">zwischen</strong></p>
<p>WEBklar<br /><span className="text-sm">(im Folgenden Anbieter)</span></p>
<p className="pt-2"><strong className="text-foreground">und</strong></p>
<p>Kunde laut Angebot<br /><span className="text-sm">(im Folgenden Kunde)</span></p>
</div>
</div>
{/* Contract Content */}
<article className="space-y-8 text-foreground">
{/* 1. Vertragsgegenstand */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
1. Vertragsgegenstand
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Gegenstand dieses Vertrages ist die Erbringung der im Angebot definierten Leistungen.</li>
<li>Der Vertrag besteht aus diesem Grundvertrag sowie den ausgewählten Leistungsmodulen.</li>
<li>Maßgeblich ist das jeweils angenommene Angebot von WEBklar.</li>
</ol>
</section>
{contractDivider}
{/* 2. Leistungsart */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
2. Leistungsart
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Sämtliche Leistungen von WEBklar stellen Dienst- und Entwicklungsleistungen dar.</li>
<li>Ein bestimmter wirtschaftlicher, technischer oder rechtlicher Erfolg wird nicht geschuldet.</li>
<li>WEBklar erbringt keinen laufenden Betrieb, sofern dieser nicht explizit vereinbart wurde.</li>
</ol>
</section>
{contractDivider}
{/* 3. Leistungsmodul A Webseite */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-2">
3. Leistungsmodul A Webseite (einmalig)
</h2>
<p className="text-sm text-muted-foreground mb-4">(aktiv, wenn im Angebot enthalten)</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar erstellt eine individuelle Webseite gemäß Angebot.</li>
<li>Die Umsetzung erfolgt nach den vom Kunden gelieferten Inhalten und Vorgaben.</li>
<li>Zusätzliche Leistungen wie Domain, Hosting, Wartung oder SEO sind nicht Bestandteil, sofern sie nicht gesondert beauftragt wurden.</li>
<li>Der Kunde ist nicht berechtigt, Änderungen am Quellcode selbst vorzunehmen.</li>
<li>Änderungen erfolgen ausschließlich durch WEBklar gegen gesonderte Vergütung.</li>
</ol>
</section>
{contractDivider}
{/* 4. Leistungsmodul B Automatisierung / Virtualisierung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-2">
4. Leistungsmodul B Automatisierung / Virtualisierung (einmalig)
</h2>
<p className="text-sm text-muted-foreground mb-4">(aktiv, wenn im Angebot enthalten)</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar entwickelt individuelle Automatisierungen, Apps oder Virtualisierungssysteme.</li>
<li>Die Leistung stellt eine reine Entwicklungsleistung dar.</li>
<li>Optional kann eine Beratungsleistung Bestandteil des Projektes sein.</li>
<li>Ein laufender Betrieb, Monitoring oder Wartung ist nicht geschuldet, außer dies wurde explizit vereinbart.</li>
<li>Der Kunde entscheidet über Inhalte, Daten und Prozesse und trägt dafür die rechtliche Verantwortung.</li>
</ol>
</section>
{contractDivider}
{/* 5. Leistungsmodul C Hosting */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-2">
5. Leistungsmodul C Hosting (jährlich)
</h2>
<p className="text-sm text-muted-foreground mb-4">(aktiv, wenn im Angebot enthalten)</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar stellt optional Hosting-Leistungen zur Verfügung.</li>
<li>Hostingverträge haben eine jährliche Laufzeit und verlängern sich automatisch, sofern nicht fristgerecht gekündigt wird.</li>
<li>WEBklar ist berechtigt, externe Anbieter (z.B. Rechenzentren) einzusetzen.</li>
<li>WEBklar übernimmt keine Haftung für Ausfälle externer Anbieter.</li>
</ol>
</section>
{contractDivider}
{/* 6. Quellcode und Eigentum */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
6. Quellcode und Eigentum
</h2>
<div className="space-y-4 text-muted-foreground">
<div>
<h3 className="text-foreground font-medium mb-2">6.1 Webseite</h3>
<ol className="list-decimal list-inside space-y-2 leading-relaxed [&>li]:pl-2">
<li>Der Kunde erhält ein einfaches, zeitlich unbegrenztes Nutzungsrecht an der fertigen Webseite.</li>
<li>Der Quellcode der Webseite wird nur auf ausdrückliche Anfrage und nach Vereinbarung herausgegeben.</li>
<li>Ohne Vereinbarung verbleibt der Quellcode bei WEBklar und wird archiviert.</li>
</ol>
</div>
<div>
<h3 className="text-foreground font-medium mb-2">6.2 Apps, Automatisierungen und Backend</h3>
<ol className="list-decimal list-inside space-y-2 leading-relaxed [&>li]:pl-2">
<li>Der Quellcode von Apps, Automatisierungen und Backend-Systemen verbleibt vollständig bei WEBklar.</li>
<li>Eine Herausgabe erfolgt ausschließlich nach gesonderter schriftlicher Vereinbarung.</li>
<li>Der Kunde erhält lediglich Zugriff auf die Bedienoberfläche bzw. das Frontend.</li>
</ol>
</div>
</div>
</section>
{contractDivider}
{/* 7. Mitwirkungspflichten */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
7. Mitwirkungspflichten des Kunden
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Der Kunde stellt alle benötigten Inhalte, Daten und Freigaben rechtzeitig bereit.</li>
<li>Verzögerungen durch fehlende Mitwirkung gehen nicht zu Lasten von WEBklar.</li>
<li>WEBklar ist nicht verpflichtet, rechtliche Prüfungen der Inhalte vorzunehmen.</li>
</ol>
</section>
{contractDivider}
{/* 8. Abnahme */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
8. Abnahme
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Nach Fertigstellung wird dem Kunden die Leistung zur Abnahme bereitgestellt.</li>
<li>Erfolgt innerhalb von 14 Tagen keine Rückmeldung, gilt die Leistung als abgenommen.</li>
<li>Nach Abnahme sind nur noch kostenpflichtige Änderungen möglich.</li>
</ol>
</section>
{contractDivider}
{/* 9. Vergütung und Zahlung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
9. Vergütung und Zahlung
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Die Vergütung ergibt sich aus dem Angebot.</li>
<li>Projektleistungen sind nach Vereinbarung fällig.</li>
<li>Hosting-Leistungen sind jährlich im Voraus zu zahlen.</li>
</ol>
</section>
{contractDivider}
{/* 10. Zahlungsverzug */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
10. Zahlungsverzug
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Bei Zahlungsverzug erfolgen bis zu zwei Mahnungen.</li>
<li>Danach ist WEBklar berechtigt:
<ul className="list-disc list-inside mt-2 space-y-1 pl-2">
<li>Leistungen zu sperren</li>
<li>Verzugszinsen zu berechnen</li>
<li>den Vertrag außerordentlich zu kündigen</li>
</ul>
</li>
</ol>
</section>
{contractDivider}
{/* 11. Haftung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
11. Haftung
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar haftet nur bei Vorsatz und grober Fahrlässigkeit.</li>
<li>Keine Haftung für:
<ul className="list-disc list-inside mt-2 space-y-1 pl-2">
<li>Umsatzausfälle</li>
<li>Datenverlust</li>
<li>Systemausfälle</li>
<li>externe Dienste</li>
</ul>
</li>
<li>Die Haftung ist der Höhe nach auf den Auftragswert begrenzt.</li>
</ol>
</section>
{contractDivider}
{/* 12. Kündigung */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
12. Kündigung
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Laufzeitverträge (z.B. Hosting, Wartung) können zum Ende der jeweiligen Laufzeit gekündigt werden.</li>
<li>Das Recht zur außerordentlichen Kündigung bleibt unberührt.</li>
</ol>
</section>
{contractDivider}
{/* 13. Referenzen */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
13. Referenzen
</h2>
<p className="text-muted-foreground leading-relaxed">
WEBklar darf das Projekt nur nach ausdrücklicher Zustimmung des Kunden als Referenz verwenden.
</p>
</section>
{contractDivider}
{/* 14. Schlussbestimmungen */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-4">
14. Schlussbestimmungen
</h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Es gilt deutsches Recht.</li>
<li>Gerichtsstand ist der Sitz von WEBklar, soweit zulässig.</li>
<li>Sollten einzelne Bestimmungen unwirksam sein, bleibt der Vertrag im Übrigen wirksam.</li>
</ol>
</section>
</article>
{/* Back / Contact */}
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<Link to="/">
<Button variant="outline" className="rounded-full">
Zur Startseite
</Button>
</Link>
<Link to="/kontakt">
<Button className="btn-minimal rounded-full">
Kontakt
</Button>
</Link>
</div>
</div>
</div>
</main>
</div>
);
};
export default AGB;

View File

@@ -171,19 +171,19 @@ const Contact = () => {
<div> <div>
<div className="label-tag mb-2">E-Mail</div> <div className="label-tag mb-2">E-Mail</div>
<a <a
href="mailto:hello@webklar.de" href="mailto:support@webklar.com"
className="text-foreground hover:text-muted-foreground transition-colors" className="text-foreground hover:text-muted-foreground transition-colors"
> >
hello@webklar.de support@webklar.com
</a> </a>
</div> </div>
<div> <div>
<div className="label-tag mb-2">Telefon</div> <div className="label-tag mb-2">Telefon</div>
<a <a
href="tel:+4912345678" href="tel:+491704969375"
className="text-foreground hover:text-muted-foreground transition-colors" className="text-foreground hover:text-muted-foreground transition-colors"
> >
+49 123 456 78 0170 4969375
</a> </a>
</div> </div>
</div> </div>

196
src/pages/Impressum.tsx Normal file
View File

@@ -0,0 +1,196 @@
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowLeft, Scale } from "lucide-react";
const impressumDivider = (
<div className="my-8 border-t border-border" aria-hidden />
);
const Impressum = () => {
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 glass-nav py-4">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">
Webklar
</span>
</Link>
<Link to="/">
<Button
variant="ghost"
className="text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="w-4 h-4 mr-2" />
Zurück
</Button>
</Link>
</div>
</div>
</header>
{/* Main Content */}
<main className="pt-32 pb-24">
<div className="container mx-auto px-6">
<div className="max-w-3xl mx-auto">
{/* Page Header */}
<div className="mb-12">
<div className="label-tag mb-4 flex items-center gap-2">
<Scale className="w-4 h-4" />
Rechtliches
</div>
<h1 className="text-4xl md:text-5xl font-display font-medium text-foreground tracking-tight uppercase mb-2">
Impressum
</h1>
<p className="text-muted-foreground text-lg">
Angaben gemäß § 5 TMG
</p>
</div>
{/* Impressum Content */}
<article className="space-y-8 text-foreground">
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
WEBklar GbR
</h2>
<p className="text-muted-foreground leading-relaxed whitespace-pre-line">
Am Schwimmbad 10<br />
67722 Winnweiler<br />
Deutschland
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Vertreten durch
</h2>
<p className="text-muted-foreground leading-relaxed">
Geschäftsführer:<br />
Kenso Gri
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Kontakt
</h2>
<p className="text-muted-foreground leading-relaxed">
Telefon: 0176 23726355<br />
E-Mail: kenso.gri@gmail.com
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Rechtsform
</h2>
<p className="text-muted-foreground leading-relaxed">
Gesellschaft bürgerlichen Rechts (GbR)
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Umsatzsteuer
</h2>
<p className="text-muted-foreground leading-relaxed">
Gemäß § 19 UStG wird keine Umsatzsteuer erhoben (Kleinunternehmerregelung).<br />
(Falls das nicht stimmt oder sich ändert, unbedingt sagen.)
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Verantwortlich für den Inhalt nach § 55 Absatz 2 RStV
</h2>
<p className="text-muted-foreground leading-relaxed whitespace-pre-line">
Kenso Gri<br />
Schliertal 21<br />
67468 Frankenstein<br />
Deutschland
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Haftung für Inhalte
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Als Diensteanbieter sind wir gemäß § 7 Absatz 1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich.<br />
Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Haftung für Links
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Unsere Webseite enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben.<br />
Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen.<br />
Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich.
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Urheberrecht
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht.<br />
Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.
</p>
</section>
{impressumDivider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-3">
Online-Streitbeilegung
</h2>
<p className="text-muted-foreground leading-relaxed space-y-2">
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:<br />
<a
href="https://ec.europa.eu/consumers/odr"
target="_blank"
rel="noopener noreferrer"
className="text-foreground underline hover:no-underline"
>
https://ec.europa.eu/consumers/odr
</a>
<br /><br />
Wir sind nicht verpflichtet und nicht bereit, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.
</p>
</section>
</article>
{/* Back / Contact */}
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<Link to="/">
<Button variant="outline" className="rounded-full">
Zur Startseite
</Button>
</Link>
<Link to="/kontakt">
<Button className="btn-minimal rounded-full">
Kontakt
</Button>
</Link>
</div>
</div>
</div>
</main>
</div>
);
};
export default Impressum;