Compare commits

...

3 Commits

8 changed files with 9772 additions and 0 deletions

8551
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

93
package.json Normal file
View File

@@ -0,0 +1,93 @@
{
"name": "vite_react_shadcn_ts",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build:dev": "vite build --mode development",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-aspect-ratio": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-context-menu": "^2.2.15",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-menubar": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.13",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toast": "^1.2.14",
"@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",
"cmdk": "^1.1.1",
"date-fns": "^3.6.0",
"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",
"react-dom": "^18.3.1",
"react-hook-form": "^7.61.1",
"react-resizable-panels": "^2.1.9",
"react-router-dom": "^6.30.1",
"recharts": "^2.15.4",
"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",
"@types/node": "^22.16.5",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@vitejs/plugin-react-swc": "^3.11.0",
"autoprefixer": "^10.4.21",
"eslint": "^9.32.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^15.15.0",
"jsdom": "^20.0.3",
"lovable-tagger": "^1.1.13",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"typescript-eslint": "^8.38.0",
"vite": "^5.4.19",
"vitest": "^3.2.4"
}
}

97
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,97 @@
import React from 'react';
const DevStudio: React.FC = () => {
return (
<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;

99
src/components/Header.tsx Normal file
View File

@@ -0,0 +1,99 @@
"use client";
import { Link } from "react-router-dom";
import { useState } from "react";
import {
Navbar,
NavBody,
NavItems,
MobileNav,
NavbarLogo,
NavbarButton,
MobileNavHeader,
MobileNavToggle,
MobileNavMenu,
} from "@/components/ui/resizable-navbar";
const Header = () => {
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 (
<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">
<NavbarButton
as="span"
variant="primary"
className="!text-black"
>
Kontakt
</NavbarButton>
</Link>
</div>
</NavBody>
{/* 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)}
>
{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)}>
<NavbarButton
as="span"
variant="primary"
className="block w-full text-center !text-black"
>
Kontakt
</NavbarButton>
</Link>
</div>
</MobileNavMenu>
</MobileNav>
</Navbar>
</div>
);
};
export default Header;

121
src/components/Hero.tsx Normal file
View File

@@ -0,0 +1,121 @@
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowRight } from "lucide-react";
import { useState, useEffect } from "react";
import Silk from "@/components/Silk";
const FOUNDING_DATE = new Date("2026-01-25"); // Samstag, 25. Januar 2026
const Hero = () => {
const [companyAge, setCompanyAge] = useState("");
useEffect(() => {
const calculateAge = () => {
const now = new Date();
const diff = now.getTime() - FOUNDING_DATE.getTime();
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 years = Math.floor(days / 365);
const remainingDays = days % 365;
if (years > 0) {
setCompanyAge(`${years}J ${remainingDays}T ${hours}h`);
} else {
setCompanyAge(`${days}T ${hours}h`);
}
};
calculateAge();
const interval = setInterval(calculateAge, 60 * 60 * 1000); // Update every hour (only days/hours shown)
return () => clearInterval(interval);
}, []);
return (
<section className="relative min-h-screen flex flex-col justify-center overflow-hidden pt-20">
{/* Silk animated background */}
<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="max-w-6xl">
{/* Label */}
<div className="label-tag mb-8 animate-fade-in" style={{ animationDelay: '0.1s' }}>
Webentwicklung & Design
</div>
{/* Main Heading - Large Uppercase */}
<h1 className="text-5xl sm:text-6xl md:text-7xl lg:text-8xl xl:text-9xl font-display font-medium text-foreground mb-6 leading-[0.95] tracking-tighter uppercase animate-slide-up" style={{ animationDelay: '0.2s' }}>
Wir digitalisieren<br />
<span className="text-muted-foreground">Ihr Unternehmen</span>
</h1>
{/* Subheadline */}
<p className="text-xl md:text-2xl text-foreground/90 max-w-3xl mb-6 font-medium animate-fade-in" style={{ animationDelay: '0.3s' }}>
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>
{/* CTA Buttons */}
<div className="flex flex-col sm:flex-row gap-4 mb-6 animate-fade-in" style={{ animationDelay: '0.5s' }}>
<Link to="/kontakt">
<Button
size="lg"
className="btn-minimal rounded-full px-8 py-6 text-base font-medium group"
>
Kostenlose Potenzialanalyse sichern
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
</Button>
</Link>
<Button
size="lg"
variant="outline"
className="btn-outline rounded-full px-8 py-6 text-base font-medium"
>
System-Demo anfordern
</Button>
</div>
{/* Trust Line */}
<p className="text-sm text-muted-foreground/70 uppercase tracking-wider animate-fade-in" style={{ animationDelay: '0.55s' }}>
Für Unternehmen, die nicht stehen bleiben wollen.
</p>
</div>
</div>
{/* Bottom Stats Row */}
<div className="container mx-auto px-6 mt-auto pb-12 pt-20 relative z-10">
<div className="divider mb-12" />
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12">
<div className="animate-fade-in" style={{ animationDelay: '0.6s' }}>
<div className="stat-number text-4xl md:text-5xl text-foreground mb-2">10+</div>
<div className="label-tag">Projekte</div>
</div>
<div className="animate-fade-in" style={{ animationDelay: '0.7s' }}>
<div className="stat-number text-4xl md:text-5xl text-foreground mb-2">{companyAge}</div>
<div className="label-tag">Am Markt</div>
</div>
<div className="animate-fade-in" style={{ animationDelay: '0.8s' }}>
<div className="stat-number text-4xl md:text-5xl text-foreground mb-2">99,9%</div>
<div className="label-tag">Systemverfügbarkeit</div>
</div>
<div className="animate-fade-in" style={{ animationDelay: '0.9s' }}>
<div className="stat-number text-4xl md:text-5xl text-foreground mb-2">🇩🇪</div>
<div className="label-tag">Serverstandort in Deutschland</div>
</div>
</div>
</div>
</section>
);
};
export default Hero;

158
src/components/Silk.tsx Normal file
View 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;

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>
);
};

336
src/index.css Normal file
View File

@@ -0,0 +1,336 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* webklar Design System - Muradov Inspired Minimal Dark Theme */
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap");
@layer base {
:root {
/* Ultra Minimal Deep Black Theme - Muradov Inspired */
--background: 0 0% 0%;
--foreground: 0 0% 92%;
--card: 0 0% 6%;
--card-foreground: 0 0% 92%;
--popover: 0 0% 8%;
--popover-foreground: 0 0% 92%;
/* Primary - Subtle cyan accent */
--primary: 180 40% 50%;
--primary-foreground: 0 0% 3%;
/* Secondary - Dark Gray */
--secondary: 0 0% 12%;
--secondary-foreground: 0 0% 92%;
/* Muted - Medium Gray */
--muted: 0 0% 45%;
--muted-foreground: 0 0% 65%;
/* Accent */
--accent: 0 0% 10%;
--accent-foreground: 0 0% 92%;
--destructive: 0 62% 50%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 15%;
--input: 0 0% 15%;
--ring: 180 40% 50%;
--radius: 0.5rem;
/* Shadows */
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1);
--sidebar-background: 0 0% 3%;
--sidebar-foreground: 222 47% 11%;
--sidebar-primary: 200 98% 39%;
--sidebar-primary-foreground: 204 100% 97%;
--sidebar-accent: 215 24% 26%;
--sidebar-accent-foreground: 210 40% 98%;
--sidebar-border: 212 26% 83%;
--sidebar-ring: 200 98% 39%;
--chart-1: 198 93% 59%;
--chart-2: 213 93% 67%;
--chart-3: 215 20% 65%;
--chart-4: 215 16% 46%;
--chart-5: 215 19% 34%;
--sidebar: 210 40% 98%;
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
--font-display: 'Space Grotesk', system-ui, sans-serif;
--spacing: 0.25rem;
--font-serif: 'Lora', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
--font-mono: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
--tracking-normal: 0em;
}
.dark {
--background: 222 47% 11%;
--foreground: 210 40% 98%;
--card: 217 32% 17%;
--card-foreground: 210 40% 98%;
--popover: 215 24% 26%;
--popover-foreground: 210 40% 98%;
--primary: 198 93% 59%;
--primary-foreground: 204 80% 15%;
--secondary: 212 26% 83%;
--secondary-foreground: 228 84% 4%;
--muted: 215 16% 46%;
--muted-foreground: 210 40% 98%;
--accent: 228 84% 4%;
--accent-foreground: 215 20% 65%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 85% 97%;
--border: 215 19% 34%;
--input: 215 19% 34%;
--ring: 198 93% 59%;
--chart-1: 199 95% 73%;
--chart-2: 211 96% 78%;
--chart-3: 215 20% 65%;
--chart-4: 215 16% 46%;
--chart-5: 215 19% 34%;
--sidebar: 217 32% 17%;
--sidebar-foreground: 210 40% 98%;
--sidebar-primary: 198 93% 59%;
--sidebar-primary-foreground: 204 80% 15%;
--sidebar-accent: 215 20% 65%;
--sidebar-accent-foreground: 228 84% 4%;
--sidebar-border: 215 19% 34%;
--sidebar-ring: 198 93% 59%;
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 1px 2px -1px hsl(0 0% 0% / 0.1);
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 2px 4px -1px hsl(0 0% 0% / 0.1);
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 4px 6px -1px hsl(0 0% 0% / 0.1);
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.1), 0 8px 10px -1px hsl(0 0% 0% / 0.1);
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
--radius: 0rem;
}
}
@layer base {
* {
@apply border-border;
}
html {
scroll-behavior: smooth;
}
body {
@apply bg-background text-foreground antialiased;
font-family: 'Inter', system-ui, sans-serif;
letter-spacing: -0.01em;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Space Grotesk', 'Inter', system-ui, sans-serif;
letter-spacing: -0.04em;
font-weight: 500;
}
h1 {
font-weight: 500;
letter-spacing: -0.05em;
}
}
@layer components {
/* Minimal glass nav */
.glass-nav {
@apply backdrop-blur-xl border-b;
background: hsl(0 0% 3% / 0.9);
border-color: hsl(0 0% 15% / 0.5);
}
/* Card minimal */
.card-minimal {
@apply transition-all duration-500;
background: hsl(var(--card));
border: 1px solid hsl(var(--border));
}
.card-minimal:hover {
border-color: hsl(0 0% 25%);
}
/* Text gradient - subtle */
.text-gradient {
background: linear-gradient(135deg, hsl(0 0% 100%) 0%, hsl(0 0% 70%) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Animated underline for links */
.link-underline {
@apply relative;
}
.link-underline::after {
content: '';
@apply absolute bottom-0 left-0 w-full h-px scale-x-0 origin-right transition-transform duration-300;
background: hsl(0 0% 98%);
}
.link-underline:hover::after {
@apply scale-x-100 origin-left;
}
/* Fade up on scroll */
.fade-up {
@apply opacity-0 translate-y-8 transition-all duration-700 ease-out;
}
.fade-up.visible {
@apply opacity-100 translate-y-0;
}
/* Partner logo marquee */
.marquee {
@apply flex gap-16;
animation: marquee 30s linear infinite;
}
/* Minimal project card */
.project-card {
@apply transition-all duration-500 overflow-hidden;
background: hsl(0 0% 6%);
border: 1px solid hsl(0 0% 12%);
}
.project-card:hover {
border-color: hsl(0 0% 25%);
transform: translateY(-4px);
}
/* Minimal button */
.btn-minimal {
@apply relative transition-all duration-300;
background: hsl(0 0% 98%);
color: hsl(0 0% 3%);
}
.btn-minimal:hover {
background: hsl(0 0% 85%);
}
/* Outline button */
.btn-outline {
@apply relative transition-all duration-300 border;
background: transparent;
color: hsl(0 0% 98%);
border-color: hsl(0 0% 25%);
}
.btn-outline:hover {
border-color: hsl(0 0% 50%);
background: hsl(0 0% 10%);
}
/* Stats number */
.stat-number {
font-family: 'Space Grotesk', system-ui, sans-serif;
font-weight: 500;
letter-spacing: -0.05em;
line-height: 1;
}
/* Grid line decoration */
.grid-lines {
background-image:
linear-gradient(hsl(0 0% 12%) 1px, transparent 1px),
linear-gradient(90deg, hsl(0 0% 12%) 1px, transparent 1px);
background-size: 80px 80px;
}
/* Horizontal divider */
.divider {
@apply w-full h-px;
background: linear-gradient(90deg, transparent 0%, hsl(0 0% 20%) 50%, transparent 100%);
}
/* Label/Tag */
.label-tag {
@apply text-xs uppercase tracking-widest font-medium;
color: hsl(0 0% 50%);
}
}
@layer utilities {
.shadow-soft {
box-shadow: var(--shadow-sm);
}
.shadow-elevated {
box-shadow: var(--shadow-md);
}
.shadow-floating {
box-shadow: var(--shadow-lg);
}
}
/* Keyframes */
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(60px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fade-in 0.8s ease-out forwards;
}
.animate-slide-up {
animation: slide-up 0.8s ease-out forwards;
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out forwards;
}