fixes
This commit is contained in:
1879
package-lock.json
generated
1879
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,8 @@
|
||||
"@radix-ui/react-toggle": "^1.1.9",
|
||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@react-three/fiber": "^8.18.0",
|
||||
"@tabler/icons-react": "^3.36.1",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -49,6 +51,7 @@
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.462.0",
|
||||
"motion": "^12.29.2",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
@@ -60,14 +63,15 @@
|
||||
"sonner": "^1.7.4",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"three": "^0.182.0",
|
||||
"vaul": "^0.9.9",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@testing-library/jest-dom": "^6.6.0",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^22.16.5",
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
317
src/components/ui/resizable-navbar.tsx
Normal file
317
src/components/ui/resizable-navbar.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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%;
|
||||
|
||||
Reference in New Issue
Block a user