This commit is contained in:
2026-02-01 22:34:47 +01:00
parent 6228945065
commit 1d4584e5d9
7 changed files with 2370 additions and 210 deletions

View File

@@ -1,105 +1,97 @@
const Footer = () => {
const currentYear = new Date().getFullYear();
const links = {
services: [
{ label: "Strategieberatung", href: "#services" },
{ label: "UX/UI Design", href: "#services" },
{ label: "Entwicklung", href: "#services" },
{ label: "SEO & Support", href: "#services" },
],
company: [
{ label: "Über uns", href: "#about" },
{ label: "Projekte", href: "#projects" },
{ label: "Ablauf", href: "#process" },
{ label: "Kontakt", href: "#contact" },
],
legal: [
{ label: "Impressum", href: "/impressum" },
{ label: "Datenschutz", href: "/datenschutz" },
{ label: "AGB", href: "/agb" },
],
};
import React from 'react';
const DevStudio: React.FC = () => {
return (
<footer className="bg-secondary/20 border-t border-border relative">
<div className="container mx-auto px-6 py-16">
<div className="grid md:grid-cols-4 gap-12 mb-16">
{/* Logo & Description */}
<div className="md:col-span-1">
<span className="text-xl font-display font-medium text-foreground tracking-tight mb-6 block">
webklar
</span>
<p className="text-muted-foreground text-sm leading-relaxed">
Maßgeschneiderte Weblösungen für Ihr Unternehmen. Sicher, zuverlässig und modern.
</p>
</div>
{/* Services */}
<div>
<h4 className="label-tag mb-6">Leistungen</h4>
<ul className="space-y-3">
{links.services.map((link) => (
<li key={link.label}>
<a
href={link.href}
className="text-muted-foreground hover:text-foreground transition-colors text-sm"
>
{link.label}
</a>
</li>
))}
</ul>
</div>
{/* Company */}
<div>
<h4 className="label-tag mb-6">Unternehmen</h4>
<ul className="space-y-3">
{links.company.map((link) => (
<li key={link.label}>
<a
href={link.href}
className="text-muted-foreground hover:text-foreground transition-colors text-sm"
>
{link.label}
</a>
</li>
))}
</ul>
</div>
{/* Legal */}
<div>
<h4 className="label-tag mb-6">Rechtliches</h4>
<ul className="space-y-3">
{links.legal.map((link) => (
<li key={link.label}>
<a
href={link.href}
className="text-muted-foreground hover:text-foreground transition-colors text-sm"
>
{link.label}
</a>
</li>
))}
</ul>
</div>
</div>
{/* Bottom Bar */}
<div className="divider mb-8" />
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
<p className="text-muted-foreground text-sm">
© {currentYear} webklar. Alle Rechte vorbehalten.
</p>
<div className="text-muted-foreground text-sm">
Made in Germany
</div>
</div>
</div>
</footer>
<p className="inset-x-0 mt-20 bg-gradient-to-b from-black via-neutral-950 to-neutral-900 bg-clip-text text-center text-5xl font-bold text-transparent md:text-9xl lg:text-[12rem] xl:text-[13rem]">
WEBklar
</p>
);
};
const Footer: React.FC = () => {
return (
<div className="relative w-full overflow-hidden border-t border-white/[0.1] bg-black px-8 py-20 dark:border-white/[0.1] dark:bg-black">
<div className="mx-auto flex max-w-7xl flex-col items-start justify-between text-sm text-neutral-500 sm:flex-row md:px-8">
<div>
<div className="mr-0 mb-4 md:mr-4 md:flex">
<a className="relative z-20 mr-4 flex items-center space-x-2 px-2 py-1 text-sm font-normal text-white" href="/">
<img alt="logo" width="30" height="30" src="https://assets.aceternity.com/logo-dark.png" />
<span className="font-medium text-white">WEBklar</span>
</a>
</div>
<div className="mt-2 ml-2">© copyright WEBklar 2024. All rights reserved.</div>
</div>
<div className="mt-10 grid grid-cols-2 items-start gap-10 sm:mt-0 md:mt-0 lg:grid-cols-4">
<div className="flex w-full flex-col justify-center space-y-4">
<p className="hover:text-neutral-300 font-bold text-neutral-600 transition-colors dark:text-neutral-300">Pages</p>
<ul className="hover:text-neutral-300 list-none space-y-4 text-neutral-600 transition-colors dark:text-neutral-300">
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">All Products</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Studio</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Clients</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Pricing</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Blog</a>
</li>
</ul>
</div>
<div className="flex flex-col justify-center space-y-4">
<p className="hover:text-neutral-300 font-bold text-neutral-600 transition-colors dark:text-neutral-300">Socials</p>
<ul className="hover:text-neutral-300 list-none space-y-4 text-neutral-600 transition-colors dark:text-neutral-300">
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Facebook</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Instagram</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Twitter</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">LinkedIn</a>
</li>
</ul>
</div>
<div className="flex flex-col justify-center space-y-4">
<p className="hover:text-neutral-300 font-bold text-neutral-600 transition-colors dark:text-neutral-300">Legal</p>
<ul className="hover:text-neutral-300 list-none space-y-4 text-neutral-600 transition-colors dark:text-neutral-300">
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/agb">AGBs</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/impressum">Impressum</a>
</li>
</ul>
</div>
<div className="flex flex-col justify-center space-y-4">
<p className="hover:text-neutral-300 font-bold text-neutral-600 transition-colors dark:text-neutral-300">Register</p>
<ul className="hover:text-neutral-300 list-none space-y-4 text-neutral-600 transition-colors dark:text-neutral-300">
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Sign Up</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Login</a>
</li>
<li className="list-none">
<a className="hover:text-neutral-300 transition-colors" href="/products">Forgot Password</a>
</li>
</ul>
</div>
</div>
</div>
<p className="inset-x-0 mt-20 bg-gradient-to-b from-black via-neutral-950 to-neutral-900 bg-clip-text text-center text-5xl font-bold text-transparent md:text-9xl lg:text-[12rem] xl:text-[13rem]">
WEBklar
</p>
</div>
);
};
export { DevStudio, Footer };
export default Footer;

View File

@@ -1,104 +1,98 @@
import { useState, useEffect } from "react";
"use client";
import { Link } from "react-router-dom";
import { Menu, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import {
Navbar,
NavBody,
NavItems,
MobileNav,
NavbarLogo,
NavbarButton,
MobileNavHeader,
MobileNavToggle,
MobileNavMenu,
} from "@/components/ui/resizable-navbar";
const Header = () => {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const navLinks = [
{ href: "#about", label: "Über uns" },
{ href: "#services", label: "Leistungen" },
{ href: "#projects", label: "Projekte" },
{ href: "#process", label: "Ablauf" },
const navItems = [
{ name: "Über uns", link: "#about" },
{ name: "Leistungen", link: "#services" },
{ name: "Projekte", link: "#projects" },
{ name: "Ablauf", link: "#process" },
];
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
return (
<header
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
isScrolled
? "glass-nav py-4"
: "bg-transparent py-6"
}`}
>
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
{/* Logo */}
<a href="#" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">Webklar</span>
</a>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-10">
{navLinks.map((link) => (
<a
key={link.href}
href={link.href}
className="text-muted-foreground hover:text-foreground transition-colors duration-300 text-sm font-medium uppercase tracking-wider"
>
{link.label}
</a>
))}
</nav>
{/* CTA Button */}
<div className="hidden md:block">
<div className="relative w-full">
<Navbar>
{/* Desktop Navigation */}
<NavBody>
<NavbarLogo href="#">
<span className="font-display text-lg font-medium tracking-tight">
Webklar
</span>
</NavbarLogo>
<NavItems items={navItems} />
<div className="navbar-actions flex items-center gap-4">
<Link to="/kontakt">
<Button
className="btn-minimal rounded-full px-6 py-5 text-sm font-medium"
<NavbarButton
as="span"
variant="primary"
className="!text-black"
>
Kontakt
</Button>
</NavbarButton>
</Link>
</div>
</NavBody>
{/* Mobile Menu Button */}
<button
className="md:hidden p-2 text-foreground hover:text-muted-foreground transition-colors"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-label="Toggle menu"
{/* Mobile Navigation */}
<MobileNav>
<MobileNavHeader>
<NavbarLogo href="#">
<span className="font-display text-lg font-medium tracking-tight">
Webklar
</span>
</NavbarLogo>
<MobileNavToggle
isOpen={isMobileMenuOpen}
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
/>
</MobileNavHeader>
<MobileNavMenu
isOpen={isMobileMenuOpen}
onClose={() => setIsMobileMenuOpen(false)}
>
{isMobileMenuOpen ? (
<X className="w-6 h-6" />
) : (
<Menu className="w-6 h-6" />
)}
</button>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="md:hidden absolute top-full left-0 right-0 bg-background/98 backdrop-blur-xl border-b border-border p-6 animate-fade-in">
<nav className="flex flex-col gap-6">
{navLinks.map((link) => (
<a
key={link.href}
href={link.href}
className="text-foreground hover:text-muted-foreground transition-colors text-lg font-medium uppercase tracking-wider"
onClick={() => setIsMobileMenuOpen(false)}
>
{link.label}
</a>
))}
{navItems.map((item, idx) => (
<a
key={`mobile-link-${idx}`}
href={item.link}
onClick={() => setIsMobileMenuOpen(false)}
className="relative text-neutral-600 dark:text-neutral-300"
>
<span className="block font-medium uppercase tracking-wider">
{item.name}
</span>
</a>
))}
<div className="flex w-full flex-col gap-4">
<Link to="/kontakt" onClick={() => setIsMobileMenuOpen(false)}>
<Button className="btn-minimal rounded-full mt-4 py-5 text-sm font-medium w-full">
<NavbarButton
as="span"
variant="primary"
className="block w-full text-center !text-black"
>
Kontakt
</Button>
</NavbarButton>
</Link>
</nav>
</div>
)}
</div>
</header>
</div>
</MobileNavMenu>
</MobileNav>
</Navbar>
</div>
);
};

View File

@@ -17,21 +17,19 @@ const Hero = () => {
const totalSeconds = Math.floor(diff / 1000);
const days = Math.floor(totalSeconds / (60 * 60 * 24));
const hours = Math.floor((totalSeconds % (60 * 60 * 24)) / (60 * 60));
const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
const seconds = totalSeconds % 60;
const years = Math.floor(days / 365);
const remainingDays = days % 365;
if (years > 0) {
setCompanyAge(`${years}J ${remainingDays}T ${hours}h ${minutes}m ${seconds}s`);
setCompanyAge(`${years}J ${remainingDays}T ${hours}h`);
} else {
setCompanyAge(`${days}T ${hours}h ${minutes}m ${seconds}s`);
setCompanyAge(`${days}T ${hours}h`);
}
};
calculateAge();
const interval = setInterval(calculateAge, 1000); // Update every second
const interval = setInterval(calculateAge, 60 * 60 * 1000); // Update every hour (only days/hours shown)
return () => clearInterval(interval);
}, []);
@@ -40,10 +38,11 @@ const Hero = () => {
{/* Silk animated background */}
<div className="absolute inset-0 z-0 w-full h-full">
<Silk
speed={5}
scale={1}
color="#7B7481"
noiseIntensity={1.5}
speed={3}
scale={0.5}
color="#373737"
noiseIntensity={4
}
rotation={0}
/>
</div>
@@ -66,11 +65,6 @@ const Hero = () => {
Wir digitalisieren, automatisieren und vernetzen Ihre gesamte Firma in einem einzigen System damit Ihr Unternehmen wachsen kann, ohne dass Sie mehr arbeiten müssen.
</p>
{/* Kurztext */}
<p className="text-lg md:text-xl text-muted-foreground max-w-2xl mb-8 animate-fade-in" style={{ animationDelay: '0.4s' }}>
Die meisten Unternehmen arbeiten mit zu vielen Tools, manuellen Prozessen und ineffizienten Abläufen. Wir ersetzen Chaos durch Struktur und bauen Ihnen eine digitale Infrastruktur, die Zeit spart, Fehler reduziert und Wachstum planbar macht.
</p>
{/* CTA Buttons */}
<div className="flex flex-col sm:flex-row gap-4 mb-6 animate-fade-in" style={{ animationDelay: '0.5s' }}>
<Link to="/kontakt">

View File

@@ -0,0 +1,317 @@
"use client";
import { cn } from "@/lib/utils";
import { IconMenu2, IconX } from "@tabler/icons-react";
import {
motion,
AnimatePresence,
useScroll,
useMotionValueEvent,
} from "motion/react";
import React, { useRef, useState } from "react";
interface NavbarProps {
children: React.ReactNode;
className?: string;
}
export const Navbar = ({ children, className }: NavbarProps) => {
const ref = useRef<HTMLDivElement>(null);
const { scrollY } = useScroll({
target: ref,
offset: ["start start", "end start"],
});
const [visible, setVisible] = useState(false);
useMotionValueEvent(scrollY, "change", (latest) => {
if (latest > 100) {
setVisible(true);
} else {
setVisible(false);
}
});
return (
<motion.div
ref={ref}
className={cn("fixed inset-x-0 top-0 z-40 w-full", className)}
>
{React.Children.map(children, (child) =>
React.isValidElement(child)
? React.cloneElement(child, { visible } as { visible: boolean })
: child
)}
</motion.div>
);
};
interface NavBodyProps {
children: React.ReactNode;
className?: string;
visible?: boolean;
}
export const NavBody = ({ children, className, visible }: NavBodyProps) => {
return (
<motion.div
animate={{
backdropFilter: visible ? "blur(10px)" : "none",
boxShadow: visible
? "0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset"
: "none",
width: visible ? "40%" : "100%",
y: visible ? 20 : 0,
}}
transition={{
type: "spring",
stiffness: 200,
damping: 50,
}}
style={{
minWidth: "800px",
}}
className={cn(
"relative z-[60] mx-auto hidden w-full max-w-7xl flex-row items-center justify-between self-start rounded-full bg-transparent px-4 py-2 lg:flex",
"text-white [&_a]:text-white [&_a:hover]:text-white/90 [&_.navbar-actions_a]:!text-black",
visible && "bg-black/90",
className
)}
>
{children}
</motion.div>
);
};
interface NavItem {
name: string;
link: string;
}
interface NavItemsProps {
items: NavItem[];
className?: string;
onItemClick?: () => void;
}
export const NavItems = ({
items,
className,
onItemClick,
}: NavItemsProps) => {
const [hovered, setHovered] = useState<number | null>(null);
return (
<motion.div
onMouseLeave={() => setHovered(null)}
className={cn(
"absolute inset-0 hidden flex-1 flex-row items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800 lg:flex lg:space-x-2",
className
)}
>
{items.map((item, idx) => (
<a
onMouseEnter={() => setHovered(idx)}
onClick={onItemClick}
className="relative px-4 py-2 text-neutral-600 dark:text-neutral-300"
key={`link-${idx}`}
href={item.link}
>
{hovered === idx && (
<motion.div
layoutId="hovered"
className="absolute inset-0 h-full w-full rounded-full bg-white/10"
/>
)}
<span className="relative z-20">{item.name}</span>
</a>
))}
</motion.div>
);
};
interface MobileNavProps {
children: React.ReactNode;
className?: string;
visible?: boolean;
}
export const MobileNav = ({
children,
className,
visible,
}: MobileNavProps) => {
return (
<motion.div
animate={{
backdropFilter: visible ? "blur(10px)" : "none",
boxShadow: visible
? "0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset"
: "none",
width: visible ? "90%" : "100%",
paddingRight: visible ? "12px" : "0px",
paddingLeft: visible ? "12px" : "0px",
borderRadius: visible ? "4px" : "2rem",
y: visible ? 20 : 0,
}}
transition={{
type: "spring",
stiffness: 200,
damping: 50,
}}
className={cn(
"relative z-50 mx-auto flex w-full max-w-[calc(100vw-2rem)] flex-col items-center justify-between bg-transparent px-0 py-2 lg:hidden",
"[&>div:first-child]:text-white [&>div:first-child_a]:text-white [&>div:first-child_svg]:text-white",
visible && "bg-black/90",
className
)}
>
{children}
</motion.div>
);
};
interface MobileNavHeaderProps {
children: React.ReactNode;
className?: string;
}
export const MobileNavHeader = ({
children,
className,
}: MobileNavHeaderProps) => {
return (
<div
className={cn(
"flex w-full flex-row items-center justify-between",
className
)}
>
{children}
</div>
);
};
interface MobileNavMenuProps {
children: React.ReactNode;
className?: string;
isOpen: boolean;
onClose: () => void;
}
export const MobileNavMenu = ({
children,
className,
isOpen,
}: MobileNavMenuProps) => {
return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={cn(
"absolute inset-x-0 top-16 z-50 flex w-full flex-col items-start justify-start gap-4 rounded-lg bg-white px-4 py-8 shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset] dark:bg-neutral-950",
className
)}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
};
interface MobileNavToggleProps {
isOpen: boolean;
onClick: () => void;
}
export const MobileNavToggle = ({ isOpen, onClick }: MobileNavToggleProps) => {
return isOpen ? (
<IconX
className="h-6 w-6 cursor-pointer text-black dark:text-white"
onClick={onClick}
/>
) : (
<IconMenu2
className="h-6 w-6 cursor-pointer text-black dark:text-white"
onClick={onClick}
/>
);
};
interface NavbarLogoProps {
href?: string;
logoSrc?: string;
logoAlt?: string;
children?: React.ReactNode;
className?: string;
}
export const NavbarLogo = ({
href = "#",
logoSrc,
logoAlt = "Logo",
children,
className,
}: NavbarLogoProps) => {
return (
<a
href={href}
className={cn(
"relative z-20 mr-4 flex items-center space-x-2 px-2 py-1 text-sm font-normal text-black dark:text-white",
className
)}
>
{logoSrc ? (
<img src={logoSrc} alt={logoAlt} width={30} height={30} />
) : null}
{children}
</a>
);
};
interface NavbarButtonProps
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
href?: string;
as?: "a" | "button";
children: React.ReactNode;
className?: string;
variant?: "primary" | "secondary" | "dark" | "gradient";
}
export const NavbarButton = ({
href,
as: Tag = "a",
children,
className,
variant = "primary",
...props
}: NavbarButtonProps) => {
const baseStyles =
"px-4 py-2 rounded-md bg-white text-black text-sm font-bold relative cursor-pointer hover:-translate-y-0.5 transition duration-200 inline-block text-center";
const variantStyles = {
primary:
"shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]",
secondary: "bg-transparent shadow-none dark:text-white",
dark: "bg-black text-white shadow-[0_0_24px_rgba(34,_42,_53,_0.06),_0_1px_1px_rgba(0,_0,_0,_0.05),_0_0_0_1px_rgba(34,_42,_53,_0.04),_0_0_4px_rgba(34,_42,_53,_0.08),_0_16px_68px_rgba(47,_48,_55,_0.05),_0_1px_0_rgba(255,_255,_255,_0.1)_inset]",
gradient:
"bg-gradient-to-b from-blue-500 to-blue-700 text-white shadow-[0px_2px_0px_0px_rgba(255,255,255,0.3)_inset]",
};
const componentProps =
Tag === "a"
? { href: href ?? undefined, ...props }
: { ...props };
return (
<Tag
className={cn(baseStyles, variantStyles[variant], className)}
{...(componentProps as React.ComponentProps<typeof Tag>)}
>
{children}
</Tag>
);
};

View File

@@ -17,7 +17,7 @@
@layer base {
:root {
/* Ultra Minimal Deep Black Theme - Muradov Inspired */
--background: 0 0% 3%;
--background: 0 0% 0%;
--foreground: 0 0% 92%;
--card: 0 0% 6%;