Compare commits
2 Commits
bd07af10b5
...
1d4584e5d9
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d4584e5d9 | |||
| 6228945065 |
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": "^1.1.9",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||||
"@radix-ui/react-tooltip": "^1.2.7",
|
"@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",
|
"@tanstack/react-query": "^5.83.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -49,6 +51,7 @@
|
|||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
|
"motion": "^12.29.2",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
@@ -60,14 +63,15 @@
|
|||||||
"sonner": "^1.7.4",
|
"sonner": "^1.7.4",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"three": "^0.182.0",
|
||||||
"vaul": "^0.9.9",
|
"vaul": "^0.9.9",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.32.0",
|
"@eslint/js": "^9.32.0",
|
||||||
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@testing-library/jest-dom": "^6.6.0",
|
"@testing-library/jest-dom": "^6.6.0",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
|
||||||
"@types/node": "^22.16.5",
|
"@types/node": "^22.16.5",
|
||||||
"@types/react": "^18.3.23",
|
"@types/react": "^18.3.23",
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
|
|||||||
@@ -1,105 +1,97 @@
|
|||||||
const Footer = () => {
|
import React from 'react';
|
||||||
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" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const DevStudio: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-secondary/20 border-t border-border relative">
|
<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]">
|
||||||
<div className="container mx-auto px-6 py-16">
|
WEBklar
|
||||||
<div className="grid md:grid-cols-4 gap-12 mb-16">
|
</p>
|
||||||
{/* 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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
export default Footer;
|
||||||
|
|||||||
@@ -1,104 +1,98 @@
|
|||||||
import { useState, useEffect } from "react";
|
"use client";
|
||||||
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Menu, X } from "lucide-react";
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import {
|
||||||
|
Navbar,
|
||||||
|
NavBody,
|
||||||
|
NavItems,
|
||||||
|
MobileNav,
|
||||||
|
NavbarLogo,
|
||||||
|
NavbarButton,
|
||||||
|
MobileNavHeader,
|
||||||
|
MobileNavToggle,
|
||||||
|
MobileNavMenu,
|
||||||
|
} from "@/components/ui/resizable-navbar";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const navItems = [
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
{ name: "Über uns", link: "#about" },
|
||||||
|
{ name: "Leistungen", link: "#services" },
|
||||||
useEffect(() => {
|
{ name: "Projekte", link: "#projects" },
|
||||||
const handleScroll = () => {
|
{ name: "Ablauf", link: "#process" },
|
||||||
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 [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<div className="relative w-full">
|
||||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
|
<Navbar>
|
||||||
isScrolled
|
{/* Desktop Navigation */}
|
||||||
? "glass-nav py-4"
|
<NavBody>
|
||||||
: "bg-transparent py-6"
|
<NavbarLogo href="#">
|
||||||
}`}
|
<span className="font-display text-lg font-medium tracking-tight">
|
||||||
>
|
Webklar
|
||||||
<div className="container mx-auto px-6">
|
</span>
|
||||||
<div className="flex items-center justify-between">
|
</NavbarLogo>
|
||||||
{/* Logo */}
|
<NavItems items={navItems} />
|
||||||
<a href="#" className="flex items-center gap-2 group">
|
<div className="navbar-actions flex items-center gap-4">
|
||||||
<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">
|
|
||||||
<Link to="/kontakt">
|
<Link to="/kontakt">
|
||||||
<Button
|
<NavbarButton
|
||||||
className="btn-minimal rounded-full px-6 py-5 text-sm font-medium"
|
as="span"
|
||||||
|
variant="primary"
|
||||||
|
className="!text-black"
|
||||||
>
|
>
|
||||||
Kontakt
|
Kontakt
|
||||||
</Button>
|
</NavbarButton>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</NavBody>
|
||||||
|
|
||||||
{/* Mobile Menu Button */}
|
{/* Mobile Navigation */}
|
||||||
<button
|
<MobileNav>
|
||||||
className="md:hidden p-2 text-foreground hover:text-muted-foreground transition-colors"
|
<MobileNavHeader>
|
||||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
<NavbarLogo href="#">
|
||||||
aria-label="Toggle menu"
|
<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 ? (
|
{navItems.map((item, idx) => (
|
||||||
<X className="w-6 h-6" />
|
<a
|
||||||
) : (
|
key={`mobile-link-${idx}`}
|
||||||
<Menu className="w-6 h-6" />
|
href={item.link}
|
||||||
)}
|
onClick={() => setIsMobileMenuOpen(false)}
|
||||||
</button>
|
className="relative text-neutral-600 dark:text-neutral-300"
|
||||||
</div>
|
>
|
||||||
|
<span className="block font-medium uppercase tracking-wider">
|
||||||
{/* Mobile Menu */}
|
{item.name}
|
||||||
{isMobileMenuOpen && (
|
</span>
|
||||||
<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">
|
</a>
|
||||||
<nav className="flex flex-col gap-6">
|
))}
|
||||||
{navLinks.map((link) => (
|
<div className="flex w-full flex-col gap-4">
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
<Link to="/kontakt" onClick={() => setIsMobileMenuOpen(false)}>
|
<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
|
Kontakt
|
||||||
</Button>
|
</NavbarButton>
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</div>
|
||||||
</div>
|
</MobileNavMenu>
|
||||||
)}
|
</MobileNav>
|
||||||
</div>
|
</Navbar>
|
||||||
</header>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Link } from "react-router-dom";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import Silk from "@/components/Silk";
|
||||||
|
|
||||||
const FOUNDING_DATE = new Date("2026-01-25"); // Samstag, 25. Januar 2026
|
const FOUNDING_DATE = new Date("2026-01-25"); // Samstag, 25. Januar 2026
|
||||||
|
|
||||||
@@ -16,28 +17,35 @@ const Hero = () => {
|
|||||||
const totalSeconds = Math.floor(diff / 1000);
|
const totalSeconds = Math.floor(diff / 1000);
|
||||||
const days = Math.floor(totalSeconds / (60 * 60 * 24));
|
const days = Math.floor(totalSeconds / (60 * 60 * 24));
|
||||||
const hours = Math.floor((totalSeconds % (60 * 60 * 24)) / (60 * 60));
|
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 years = Math.floor(days / 365);
|
||||||
const remainingDays = days % 365;
|
const remainingDays = days % 365;
|
||||||
|
|
||||||
if (years > 0) {
|
if (years > 0) {
|
||||||
setCompanyAge(`${years}J ${remainingDays}T ${hours}h ${minutes}m ${seconds}s`);
|
setCompanyAge(`${years}J ${remainingDays}T ${hours}h`);
|
||||||
} else {
|
} else {
|
||||||
setCompanyAge(`${days}T ${hours}h ${minutes}m ${seconds}s`);
|
setCompanyAge(`${days}T ${hours}h`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
calculateAge();
|
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);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative min-h-screen flex flex-col justify-center bg-background overflow-hidden pt-20">
|
<section className="relative min-h-screen flex flex-col justify-center overflow-hidden pt-20">
|
||||||
{/* Subtle grid lines */}
|
{/* Silk animated background */}
|
||||||
<div className="absolute inset-0 grid-lines opacity-30" />
|
<div className="absolute inset-0 z-0 w-full h-full">
|
||||||
|
<Silk
|
||||||
|
speed={3}
|
||||||
|
scale={0.5}
|
||||||
|
color="#373737"
|
||||||
|
noiseIntensity={4
|
||||||
|
}
|
||||||
|
rotation={0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="container mx-auto px-6 relative z-10">
|
<div className="container mx-auto px-6 relative z-10">
|
||||||
<div className="max-w-6xl">
|
<div className="max-w-6xl">
|
||||||
@@ -57,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.
|
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>
|
</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 */}
|
{/* CTA Buttons */}
|
||||||
<div className="flex flex-col sm:flex-row gap-4 mb-6 animate-fade-in" style={{ animationDelay: '0.5s' }}>
|
<div className="flex flex-col sm:flex-row gap-4 mb-6 animate-fade-in" style={{ animationDelay: '0.5s' }}>
|
||||||
<Link to="/kontakt">
|
<Link to="/kontakt">
|
||||||
|
|||||||
158
src/components/Silk.tsx
Normal file
158
src/components/Silk.tsx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/* eslint-disable react/no-unknown-property */
|
||||||
|
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
||||||
|
import { forwardRef, useRef, useMemo, useLayoutEffect } from "react";
|
||||||
|
import { Color, type Mesh, type ShaderMaterial } from "three";
|
||||||
|
|
||||||
|
const hexToNormalizedRGB = (hex: string): [number, number, number] => {
|
||||||
|
hex = hex.replace("#", "");
|
||||||
|
return [
|
||||||
|
parseInt(hex.slice(0, 2), 16) / 255,
|
||||||
|
parseInt(hex.slice(2, 4), 16) / 255,
|
||||||
|
parseInt(hex.slice(4, 6), 16) / 255,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const vertexShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying vec3 vPosition;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vPosition = position;
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragmentShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying vec3 vPosition;
|
||||||
|
|
||||||
|
uniform float uTime;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform float uSpeed;
|
||||||
|
uniform float uScale;
|
||||||
|
uniform float uRotation;
|
||||||
|
uniform float uNoiseIntensity;
|
||||||
|
|
||||||
|
const float e = 2.71828182845904523536;
|
||||||
|
|
||||||
|
float noise(vec2 texCoord) {
|
||||||
|
float G = e;
|
||||||
|
vec2 r = (G * sin(G * texCoord));
|
||||||
|
return fract(r.x * r.y * (1.0 + texCoord.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 rotateUvs(vec2 uv, float angle) {
|
||||||
|
float c = cos(angle);
|
||||||
|
float s = sin(angle);
|
||||||
|
mat2 rot = mat2(c, -s, s, c);
|
||||||
|
return rot * uv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float rnd = noise(gl_FragCoord.xy);
|
||||||
|
vec2 uv = rotateUvs(vUv * uScale, uRotation);
|
||||||
|
vec2 tex = uv * uScale;
|
||||||
|
float tOffset = uSpeed * uTime;
|
||||||
|
|
||||||
|
tex.y += 0.03 * sin(8.0 * tex.x - tOffset);
|
||||||
|
|
||||||
|
float pattern = 0.6 +
|
||||||
|
0.4 * sin(5.0 * (tex.x + tex.y +
|
||||||
|
cos(3.0 * tex.x + 5.0 * tex.y) +
|
||||||
|
0.02 * tOffset) +
|
||||||
|
sin(20.0 * (tex.x + tex.y - 0.1 * tOffset)));
|
||||||
|
|
||||||
|
vec4 col = vec4(uColor, 1.0) * vec4(pattern) - rnd / 15.0 * uNoiseIntensity;
|
||||||
|
col.a = 1.0;
|
||||||
|
gl_FragColor = col;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type SilkPlaneProps = {
|
||||||
|
uniforms: {
|
||||||
|
uSpeed: { value: number };
|
||||||
|
uScale: { value: number };
|
||||||
|
uNoiseIntensity: { value: number };
|
||||||
|
uColor: { value: Color };
|
||||||
|
uRotation: { value: number };
|
||||||
|
uTime: { value: number };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const SilkPlane = forwardRef<Mesh, SilkPlaneProps>(function SilkPlane(
|
||||||
|
{ uniforms },
|
||||||
|
ref
|
||||||
|
) {
|
||||||
|
const { viewport } = useThree();
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (ref && typeof ref !== "function" && ref.current) {
|
||||||
|
ref.current.scale.set(viewport.width, viewport.height, 1);
|
||||||
|
}
|
||||||
|
}, [ref, viewport]);
|
||||||
|
|
||||||
|
useFrame((_, delta) => {
|
||||||
|
if (ref && typeof ref !== "function" && ref.current) {
|
||||||
|
const mat = ref.current.material as ShaderMaterial & {
|
||||||
|
uniforms: { uTime: { value: number } };
|
||||||
|
};
|
||||||
|
if (mat.uniforms?.uTime) mat.uniforms.uTime.value += 0.1 * delta;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh ref={ref}>
|
||||||
|
<planeGeometry args={[1, 1, 1, 1]} />
|
||||||
|
<shaderMaterial
|
||||||
|
uniforms={uniforms}
|
||||||
|
vertexShader={vertexShader}
|
||||||
|
fragmentShader={fragmentShader}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
SilkPlane.displayName = "SilkPlane";
|
||||||
|
|
||||||
|
type SilkProps = {
|
||||||
|
speed?: number;
|
||||||
|
scale?: number;
|
||||||
|
color?: string;
|
||||||
|
noiseIntensity?: number;
|
||||||
|
rotation?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Silk = ({
|
||||||
|
speed = 5,
|
||||||
|
scale = 1,
|
||||||
|
color = "#7B7481",
|
||||||
|
noiseIntensity = 1.5,
|
||||||
|
rotation = 0,
|
||||||
|
}: SilkProps) => {
|
||||||
|
const meshRef = useRef<Mesh>(null);
|
||||||
|
|
||||||
|
const uniforms = useMemo(
|
||||||
|
() => ({
|
||||||
|
uSpeed: { value: speed },
|
||||||
|
uScale: { value: scale },
|
||||||
|
uNoiseIntensity: { value: noiseIntensity },
|
||||||
|
uColor: { value: new Color(...hexToNormalizedRGB(color)) },
|
||||||
|
uRotation: { value: rotation },
|
||||||
|
uTime: { value: 0 },
|
||||||
|
}),
|
||||||
|
[speed, scale, noiseIntensity, color, rotation]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Canvas
|
||||||
|
dpr={[1, 2]}
|
||||||
|
frameloop="always"
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
camera={{ position: [0, 0, 5], fov: 75 }}
|
||||||
|
>
|
||||||
|
<SilkPlane ref={meshRef} uniforms={uniforms} />
|
||||||
|
</Canvas>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Silk;
|
||||||
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 {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
/* Ultra Minimal Deep Black Theme - Muradov Inspired */
|
/* Ultra Minimal Deep Black Theme - Muradov Inspired */
|
||||||
--background: 0 0% 3%;
|
--background: 0 0% 0%;
|
||||||
--foreground: 0 0% 92%;
|
--foreground: 0 0% 92%;
|
||||||
|
|
||||||
--card: 0 0% 6%;
|
--card: 0 0% 6%;
|
||||||
|
|||||||
Reference in New Issue
Block a user