Merge test into main: Footer updates, black background, dark gradient WEBklar
This commit is contained in:
8551
package-lock.json
generated
Normal file
8551
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
93
package.json
Normal file
93
package.json
Normal 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
97
src/components/Footer.tsx
Normal 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
99
src/components/Header.tsx
Normal 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
121
src/components/Hero.tsx
Normal 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
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
336
src/index.css
Normal file
336
src/index.css
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user