initial commit
This commit is contained in:
1
.automancer/context
Submodule
1
.automancer/context
Submodule
Submodule .automancer/context added at 1f0cf387c3
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
|
||||
# env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule ".automancer/context"]
|
||||
path = .automancer/context
|
||||
url = ssh://gitea@git.cynarski.pl:65522/automancer/context.git
|
||||
6
.tasks/build.yml
Normal file
6
.tasks/build.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: "Zbuduj projekt produkcyjnie"
|
||||
cmd: npx next build
|
||||
6
.tasks/clean.yml
Normal file
6
.tasks/clean.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: "Wyczyść artefakty budowania"
|
||||
cmd: rm -rf .next out
|
||||
10
.tasks/dev.yml
Normal file
10
.tasks/dev.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
run:
|
||||
desc: "Uruchom serwer deweloperski"
|
||||
cmd: npx next dev --turbopack
|
||||
|
||||
watch:
|
||||
desc: "Uruchom serwer deweloperski z hot-reload (alias dla run)"
|
||||
cmd: npx next dev --turbopack
|
||||
12
.tasks/lint.yml
Normal file
12
.tasks/lint.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
check:
|
||||
desc: "Sprawdź kod (ESLint + TypeScript)"
|
||||
cmds:
|
||||
- npx next lint
|
||||
- npx tsc --noEmit
|
||||
|
||||
fix:
|
||||
desc: "Napraw problemy z lintingiem"
|
||||
cmd: npx next lint --fix
|
||||
10
.tasks/test.yml
Normal file
10
.tasks/test.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
version: "3"
|
||||
|
||||
tasks:
|
||||
unit:
|
||||
desc: "Uruchom testy jednostkowe"
|
||||
cmd: echo "No tests configured yet"
|
||||
|
||||
all:
|
||||
desc: "Uruchom wszystkie testy"
|
||||
cmd: echo "No tests configured yet"
|
||||
24
CLAUDE.md
Normal file
24
CLAUDE.md
Normal file
@@ -0,0 +1,24 @@
|
||||
<!-- extends: .automancer/context/CLAUDE.md -->
|
||||
|
||||
# Automancer Site
|
||||
|
||||
Strona projektu Automancer.
|
||||
|
||||
## Kontekst projektu
|
||||
|
||||
Wspólny kontekst (architektura, konwencje, słownik, workflow) znajduje się w submodule:
|
||||
`.automancer/context/` — patrz `.automancer/context/CLAUDE.md`
|
||||
|
||||
## Design System
|
||||
|
||||
Pełna dokumentacja design systemu:
|
||||
- Wytyczne: `.automancer/context/design-system/guidelines.md`
|
||||
- Tokeny: `.automancer/context/design-system/tokens.json`
|
||||
- Logo (SVG): `.automancer/context/design-system/logo/`
|
||||
- Mockupy: `.automancer/context/assets/mockups/`
|
||||
|
||||
## Stack
|
||||
|
||||
- Dark mode jako domyślny motyw
|
||||
- Paleta: burgundy primary (`#7A3B4E`), terracotta accent (`#C2703E`), warm stone neutrals
|
||||
- Typografia: Inter (UI), JetBrains Mono (kod)
|
||||
11
Taskfile.yml
Normal file
11
Taskfile.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
vars:
|
||||
PROJECT_NAME: automancer-site
|
||||
|
||||
includes:
|
||||
dev: .tasks/dev.yml
|
||||
build: .tasks/build.yml
|
||||
lint: .tasks/lint.yml
|
||||
test: .tasks/test.yml
|
||||
clean: .tasks/clean.yml
|
||||
18
eslint.config.mjs
Normal file
18
eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
7
next.config.ts
Normal file
7
next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
6614
package-lock.json
generated
Normal file
6614
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "automancer-site",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"next": "16.1.6",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
7
postcss.config.mjs
Normal file
7
postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
5
public/favicon.svg
Normal file
5
public/favicon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024">
|
||||
<g transform="translate(0,1024) scale(0.1,-0.1)" stroke="none">
|
||||
<path fill="#C07790" fill-rule="evenodd" d="M4285 5939 c-918 -14 -1184 -46 -1420 -168 -89 -46 -199 -150 -249 -235 l-39 -66 -146 0 -147 0 -30 -31 -29 -30 0 -339 0 -340 28 -27 c27 -27 30 -28 170 -33 l142 -5 9 -40 c17 -82 49 -181 71 -225 55 -108 154 -200 290 -268 304 -152 913 -199 1260 -97 174 51 324 154 419 288 24 34 85 144 135 244 101 200 146 258 236 303 42 21 64 25 135 25 72 0 92 -4 135 -27 98 -51 145 -113 240 -314 36 -75 85 -166 109 -202 124 -187 310 -303 559 -349 132 -25 517 -25 677 0 328 51 527 133 659 272 94 99 133 186 167 373 4 22 7 22 145 22 140 0 141 0 168 28 l28 28 7 194 c4 107 5 261 4 342 l-3 147 -29 30 -30 31 -147 0 -148 0 -31 55 c-115 202 -324 315 -670 365 -207 29 -426 40 -1035 50 -618 10 -939 10 -1640 -1z M7040 4965 l0 -515 -510 0 -510 0 0 515 0 515 510 0 510 0 0 -515z M6160 4971 l0 -370 363 -3 c199 -2 365 -2 370 0 4 2 7 169 7 373 l0 369 -370 0 -370 0 0 -369z M3585 5360 c150 -98 466 -303 543 -351 92 -57 88 -64 -113 -191 -93 -60 -251 -160 -350 -222 -99 -63 -188 -118 -198 -122 -9 -3 -24 -1 -32 6 -13 11 -15 78 -15 474 0 465 2 487 35 486 5 -1 64 -36 130 -80z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
15
src/app/(dashboard)/layout.tsx
Normal file
15
src/app/(dashboard)/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar — placeholder for future implementation */}
|
||||
<aside className="hidden w-64 border-r border-surface-border bg-surface-alt lg:block">
|
||||
<div className="p-6 text-sm text-text-muted">Dashboard sidebar</div>
|
||||
</aside>
|
||||
<main className="flex-1 p-6">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
11
src/app/(dashboard)/overview/page.tsx
Normal file
11
src/app/(dashboard)/overview/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
export default function OverviewPage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<h1 className="mb-4 text-2xl font-bold text-neutral-50">Overview</h1>
|
||||
<p className="text-text-secondary">
|
||||
Dashboard w budowie. Tu pojawi się monitoring agentów, pipeline'ów
|
||||
i wizualizacje.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
src/app/(marketing)/layout.tsx
Normal file
18
src/app/(marketing)/layout.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Header } from "@/components/layout/header";
|
||||
import { Footer } from "@/components/layout/footer";
|
||||
import { Background } from "@/components/layout/background";
|
||||
|
||||
export default function MarketingLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<Background />
|
||||
<Header />
|
||||
<main className="relative z-10">{children}</main>
|
||||
<Footer className="relative z-10" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
17
src/app/(marketing)/page.tsx
Normal file
17
src/app/(marketing)/page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Hero } from "@/components/landing/hero";
|
||||
import { Architecture } from "@/components/landing/architecture";
|
||||
import { Features } from "@/components/landing/features";
|
||||
import { TechStack } from "@/components/landing/tech-stack";
|
||||
import { CTA } from "@/components/landing/cta";
|
||||
|
||||
export default function LandingPage() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<Architecture />
|
||||
<Features />
|
||||
<TechStack />
|
||||
<CTA />
|
||||
</>
|
||||
);
|
||||
}
|
||||
BIN
src/app/favicon.ico
Normal file
BIN
src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
97
src/app/globals.css
Normal file
97
src/app/globals.css
Normal file
@@ -0,0 +1,97 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&family=Oxanium:wght@200..800&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme inline {
|
||||
/* Primary — Burgundy */
|
||||
--color-primary-50: #F9F0F3;
|
||||
--color-primary-100: #F0D9E0;
|
||||
--color-primary-200: #DBA8B8;
|
||||
--color-primary-300: #C07790;
|
||||
--color-primary-400: #9C4E6A;
|
||||
--color-primary-500: #7A3B4E;
|
||||
--color-primary-600: #632F40;
|
||||
--color-primary-700: #4D2332;
|
||||
--color-primary-800: #381824;
|
||||
--color-primary-900: #230E17;
|
||||
|
||||
/* Accent — Terracotta */
|
||||
--color-accent-50: #FFF7ED;
|
||||
--color-accent-100: #FFEDD5;
|
||||
--color-accent-200: #FED7AA;
|
||||
--color-accent-300: #FDBA74;
|
||||
--color-accent-400: #FB923C;
|
||||
--color-accent-500: #C2703E;
|
||||
--color-accent-600: #A35A2F;
|
||||
--color-accent-700: #7C4422;
|
||||
--color-accent-800: #5C3318;
|
||||
--color-accent-900: #3D210F;
|
||||
|
||||
/* Neutral — Warm Stone */
|
||||
--color-neutral-50: #FAFAF9;
|
||||
--color-neutral-100: #F5F5F4;
|
||||
--color-neutral-200: #E7E5E4;
|
||||
--color-neutral-300: #D6D3D1;
|
||||
--color-neutral-400: #A8A29E;
|
||||
--color-neutral-500: #78716C;
|
||||
--color-neutral-600: #57534E;
|
||||
--color-neutral-700: #44403C;
|
||||
--color-neutral-800: #292524;
|
||||
--color-neutral-900: #1C1917;
|
||||
|
||||
/* Semantic */
|
||||
--color-success: #4A7A5B;
|
||||
--color-success-light: #D1FAE5;
|
||||
--color-success-dark: #2D4F38;
|
||||
--color-warning: #A38A2F;
|
||||
--color-warning-light: #FEF3C7;
|
||||
--color-warning-dark: #6B5A1E;
|
||||
--color-error: #9B3B3B;
|
||||
--color-error-light: #FEE2E2;
|
||||
--color-error-dark: #6B2525;
|
||||
--color-info: #4A6B82;
|
||||
--color-info-light: #DBEAFE;
|
||||
--color-info-dark: #2E4454;
|
||||
|
||||
/* Surface */
|
||||
--color-surface-bg: #1C1917;
|
||||
--color-surface-alt: #292524;
|
||||
--color-surface-card: #292524;
|
||||
--color-surface-card-hover: #44403C;
|
||||
--color-surface-border: #44403C;
|
||||
--color-surface-border-subtle: #292524;
|
||||
|
||||
/* Text */
|
||||
--color-text-primary: #FAFAF9;
|
||||
--color-text-secondary: #A8A29E;
|
||||
--color-text-muted: #78716C;
|
||||
--color-text-inverse: #1C1917;
|
||||
--color-text-accent: #DBA8B8;
|
||||
--color-text-link: #C07790;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(28, 25, 23, 0.3);
|
||||
--shadow-md: 0 4px 6px rgba(28, 25, 23, 0.4);
|
||||
--shadow-lg: 0 10px 15px rgba(28, 25, 23, 0.5);
|
||||
--shadow-glow: 0 0 20px rgba(122, 59, 78, 0.3);
|
||||
|
||||
/* Typography */
|
||||
--font-sans: "Oxanium", system-ui, -apple-system, sans-serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", monospace;
|
||||
--font-display: "Orbitron", system-ui, sans-serif;
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--color-surface-bg);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-display);
|
||||
}
|
||||
36
src/app/layout.tsx
Normal file
36
src/app/layout.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter, JetBrains_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
subsets: ["latin", "latin-ext"],
|
||||
});
|
||||
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
variable: "--font-jetbrains-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Automancer — Agentic AI Platform",
|
||||
description:
|
||||
"Platforma agentic AI oparta o Kubernetes, OpenSandbox i Claude. Orkiestracja agentów, pipeline'y CI/CD, izolowane środowiska.",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="pl" className="dark">
|
||||
<body
|
||||
className={`${inter.variable} ${jetbrainsMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
81
src/components/brand/logo.tsx
Normal file
81
src/components/brand/logo.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type LogoVariant = "dark" | "light" | "colored";
|
||||
type LogoSize = "sm" | "md" | "lg" | "xl" | "2xl";
|
||||
|
||||
interface LogoProps {
|
||||
variant?: LogoVariant;
|
||||
size?: LogoSize;
|
||||
showWordmark?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const sizeMap: Record<LogoSize, number> = {
|
||||
sm: 32,
|
||||
md: 48,
|
||||
lg: 64,
|
||||
xl: 96,
|
||||
"2xl": 160,
|
||||
};
|
||||
|
||||
const fillMap: Record<LogoVariant, string> = {
|
||||
dark: "#C07790",
|
||||
light: "#7A3B4E",
|
||||
colored: "#C07790",
|
||||
};
|
||||
|
||||
export function Logo({ variant = "dark", size = "md", showWordmark = false, className }: LogoProps) {
|
||||
const px = sizeMap[size];
|
||||
const fill = fillMap[variant];
|
||||
|
||||
return (
|
||||
<div className={cn("inline-flex items-center", className)}>
|
||||
<svg
|
||||
width={px}
|
||||
height={px}
|
||||
viewBox="0 0 1024 1024"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Automancer logo"
|
||||
>
|
||||
<g transform="translate(0,1024) scale(0.1,-0.1)" stroke="none">
|
||||
<path
|
||||
fill={fill}
|
||||
d="M4890 8184 c-529 -65 -843 -162 -1405 -434 -241 -117 -427 -195 -568 -240 -73 -23 -141 -46 -152 -50 -16 -6 -5 -15 65 -49 47 -22 123 -50 170 -62 47 -12 91 -23 99 -25 8 -3 -28 -47 -92 -114 -229 -239 -389 -467 -448 -639 -22 -64 -22 -61 -6 -57 6 3 24 7 40 10 l27 6 0 -130 c0 -72 5 -174 11 -228 9 -86 53 -337 64 -370 3 -8 30 5 80 37 42 28 112 64 155 81 68 25 243 65 380 85 35 5 36 7 64 93 39 118 112 290 168 393 74 133 57 124 173 100 492 -100 962 -99 1455 5 152 31 373 95 693 198 363 117 378 121 388 105 5 -8 9 -19 9 -25 0 -6 9 -39 21 -74 70 -213 224 -449 445 -679 l103 -108 108 -13 c237 -28 439 -95 557 -185 52 -40 48 -44 71 68 76 379 51 778 -64 1032 -129 284 -364 461 -656 491 l-60 7 -42 86 c-166 346 -566 577 -1158 668 -96 14 -611 27 -695 17z"
|
||||
/>
|
||||
<path
|
||||
fill={fill}
|
||||
fillRule="evenodd"
|
||||
d="M4285 5939 c-918 -14 -1184 -46 -1420 -168 -89 -46 -199 -150 -249 -235 l-39 -66 -146 0 -147 0 -30 -31 -29 -30 0 -339 0 -340 28 -27 c27 -27 30 -28 170 -33 l142 -5 9 -40 c17 -82 49 -181 71 -225 55 -108 154 -200 290 -268 304 -152 913 -199 1260 -97 174 51 324 154 419 288 24 34 85 144 135 244 101 200 146 258 236 303 42 21 64 25 135 25 72 0 92 -4 135 -27 98 -51 145 -113 240 -314 36 -75 85 -166 109 -202 124 -187 310 -303 559 -349 132 -25 517 -25 677 0 328 51 527 133 659 272 94 99 133 186 167 373 4 22 7 22 145 22 140 0 141 0 168 28 l28 28 7 194 c4 107 5 261 4 342 l-3 147 -29 30 -30 31 -147 0 -148 0 -31 55 c-115 202 -324 315 -670 365 -207 29 -426 40 -1035 50 -618 10 -939 10 -1640 -1z M7040 4965 l0 -515 -510 0 -510 0 0 515 0 515 510 0 510 0 0 -515z M6160 4971 l0 -370 363 -3 c199 -2 365 -2 370 0 4 2 7 169 7 373 l0 369 -370 0 -370 0 0 -369z M3585 5360 c150 -98 466 -303 543 -351 92 -57 88 -64 -113 -191 -93 -60 -251 -160 -350 -222 -99 -63 -188 -118 -198 -122 -9 -3 -24 -1 -32 6 -13 11 -15 78 -15 474 0 465 2 487 35 486 5 -1 64 -36 130 -80z"
|
||||
/>
|
||||
<path
|
||||
fill={fill}
|
||||
d="M7120 3959 c-30 -10 -64 -18 -75 -19 -36 -1 -45 -16 -70 -116 -102 -421 -306 -788 -600 -1079 -266 -263 -587 -441 -935 -517 -130 -29 -477 -32 -616 -5 -591 113 -1118 542 -1399 1140 -59 125 -127 319 -160 453 l-27 110 -85 23 c-46 13 -86 22 -88 19 -7 -7 52 -243 92 -363 114 -348 281 -644 501 -888 322 -358 728 -586 1177 -663 123 -21 423 -24 540 -5 825 131 1490 768 1744 1670 40 139 65 251 59 255 -1 1 -28 -5 -58 -15z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
{showWordmark && (
|
||||
<span
|
||||
className={cn(
|
||||
"font-display font-bold uppercase tracking-wider",
|
||||
size === "sm" && "-ml-1",
|
||||
size === "md" && "-ml-2",
|
||||
size === "lg" && "-ml-2",
|
||||
size === "xl" && "-ml-3",
|
||||
size === "2xl" && "-ml-5",
|
||||
size === "sm" && "text-base",
|
||||
size === "md" && "text-xl",
|
||||
size === "lg" && "text-2xl",
|
||||
size === "xl" && "text-4xl",
|
||||
size === "2xl" && "text-6xl",
|
||||
variant === "dark" && "text-primary-300",
|
||||
variant === "light" && "text-primary-500",
|
||||
variant === "colored" && "text-primary-300"
|
||||
)}
|
||||
>
|
||||
Automancer
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
src/components/landing/architecture.tsx
Normal file
46
src/components/landing/architecture.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
export function Architecture() {
|
||||
const nodes = [
|
||||
{ label: "User", color: "bg-neutral-700 text-neutral-50", col: "col-start-2" },
|
||||
{ label: "Pipeline CI/CD", color: "bg-success text-neutral-50", col: "col-start-2" },
|
||||
{ label: "Agent Runtime", color: "bg-primary-500 text-neutral-50", col: "col-start-1" },
|
||||
{ label: "Kubernetes", color: "bg-info text-neutral-50", col: "col-start-2" },
|
||||
{ label: "Claude AI", color: "bg-primary-700 text-primary-200", col: "col-start-3" },
|
||||
{ label: "OpenSandbox", color: "bg-accent-500 text-neutral-50", col: "col-start-1" },
|
||||
{ label: "Containers", color: "bg-accent-600 text-neutral-50", col: "col-start-3" },
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="architecture" className="px-6 py-24">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<h2 className="mb-4 text-center text-3xl font-bold text-neutral-50 md:text-4xl">
|
||||
Architektura
|
||||
</h2>
|
||||
<p className="mx-auto mb-16 max-w-2xl text-center text-text-secondary">
|
||||
Przepływ od użytkownika przez pipeline, agenty AI aż do izolowanych
|
||||
kontenerów na Kubernetes.
|
||||
</p>
|
||||
<div className="mx-auto max-w-md">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{nodes.map((node) => (
|
||||
<div
|
||||
key={node.label}
|
||||
className={`${node.color} ${node.col} flex items-center justify-center rounded-[var(--radius-md)] px-4 py-3 text-sm font-medium shadow-[var(--shadow-md)]`}
|
||||
>
|
||||
{node.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Connection lines via border decorations */}
|
||||
<div className="mt-8 flex justify-center">
|
||||
<div className="flex items-center gap-2 text-xs text-text-muted">
|
||||
<span className="inline-block h-px w-8 bg-primary-300" />
|
||||
<span>Active flow</span>
|
||||
<span className="ml-4 inline-block h-px w-8 bg-neutral-600" />
|
||||
<span>Connection</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
22
src/components/landing/cta.tsx
Normal file
22
src/components/landing/cta.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function CTA() {
|
||||
return (
|
||||
<section className="px-6 py-24">
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<h2 className="mb-4 text-3xl font-bold text-neutral-50 md:text-4xl">
|
||||
Gotowy na start?
|
||||
</h2>
|
||||
<p className="mb-8 text-text-secondary">
|
||||
Zacznij orkiestrować agentów AI już dziś.
|
||||
</p>
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
|
||||
<Button size="lg">Rozpocznij</Button>
|
||||
<Button variant="ghost" size="lg">
|
||||
Zobacz dokumentację
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
72
src/components/landing/features.tsx
Normal file
72
src/components/landing/features.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Card } from "@/components/ui/card";
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Agent Runtime",
|
||||
description:
|
||||
"Uruchamiaj agentów AI w izolowanych środowiskach z pełną kontrolą nad cyklem życia i zasobami.",
|
||||
icon: (
|
||||
<svg className="h-8 w-8 text-primary-300" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09ZM18.259 8.715 18 9.75l-.259-1.035a3.375 3.375 0 0 0-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 0 0 2.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 0 0 2.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 0 0-2.456 2.456Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Pipeline Orchestration",
|
||||
description:
|
||||
"Definiuj pipeline'y CI/CD do budowania, testowania i wdrażania agentów z pełnym observability.",
|
||||
icon: (
|
||||
<svg className="h-8 w-8 text-success" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Kubernetes Native",
|
||||
description:
|
||||
"Natywna integracja z Kubernetes — auto-scaling, health checks, rolling updates dla agentów.",
|
||||
icon: (
|
||||
<svg className="h-8 w-8 text-info" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Claude-Powered",
|
||||
description:
|
||||
"Claude jako silnik agentów — rozumowanie, analiza kodu, podejmowanie decyzji w kontekście pipeline'u.",
|
||||
icon: (
|
||||
<svg className="h-8 w-8 text-accent-400" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export function Features() {
|
||||
return (
|
||||
<section id="features" className="px-6 py-24">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<h2 className="mb-4 text-center text-3xl font-bold text-neutral-50 md:text-4xl">
|
||||
Funkcje
|
||||
</h2>
|
||||
<p className="mx-auto mb-16 max-w-2xl text-center text-text-secondary">
|
||||
Wszystko czego potrzebujesz do orkiestracji agentów AI w produkcji.
|
||||
</p>
|
||||
<div className="grid gap-6 sm:grid-cols-2">
|
||||
{features.map((feature) => (
|
||||
<Card key={feature.title} hoverable>
|
||||
<div className="mb-4">{feature.icon}</div>
|
||||
<h3 className="mb-2 text-xl font-semibold text-neutral-50">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-text-secondary leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
22
src/components/landing/hero.tsx
Normal file
22
src/components/landing/hero.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Logo } from "@/components/brand/logo";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="relative flex min-h-[80vh] flex-col items-center justify-center px-6 py-24 text-center">
|
||||
<div className="relative z-10 flex flex-col items-center gap-8">
|
||||
<Logo variant="colored" size="2xl" showWordmark />
|
||||
<p className="max-w-2xl text-lg leading-relaxed text-text-secondary md:text-xl">
|
||||
Platforma agentic AI — orkiestracja agentów, pipeline'y CI/CD
|
||||
i izolowane środowiska uruchomieniowe oparte o Kubernetes.
|
||||
</p>
|
||||
<div className="flex flex-col gap-4 sm:flex-row">
|
||||
<Button size="lg">Rozpocznij</Button>
|
||||
<Button variant="secondary" size="lg">
|
||||
Dokumentacja
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
32
src/components/landing/tech-stack.tsx
Normal file
32
src/components/landing/tech-stack.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
const stack = [
|
||||
{ label: "Kubernetes", variant: "k8s" as const },
|
||||
{ label: "Containers", variant: "container" as const },
|
||||
{ label: "GitLab CI/CD", variant: "pipeline" as const },
|
||||
{ label: "Claude AI", variant: "agent" as const },
|
||||
{ label: "OpenSandbox", variant: "container" as const },
|
||||
{ label: "Pipeline Engine", variant: "pipeline" as const },
|
||||
];
|
||||
|
||||
export function TechStack() {
|
||||
return (
|
||||
<section id="tech" className="px-6 py-24">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<h2 className="mb-4 text-center text-3xl font-bold text-neutral-50 md:text-4xl">
|
||||
Tech Stack
|
||||
</h2>
|
||||
<p className="mx-auto mb-12 max-w-2xl text-center text-text-secondary">
|
||||
Sprawdzone technologie połączone w spójną platformę.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||
{stack.map((item) => (
|
||||
<Badge key={item.label} variant={item.variant}>
|
||||
{item.label}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
356
src/components/layout/background.tsx
Normal file
356
src/components/layout/background.tsx
Normal file
@@ -0,0 +1,356 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
|
||||
interface Node {
|
||||
x: number;
|
||||
y: number;
|
||||
baseX: number;
|
||||
baseY: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
isHub: boolean;
|
||||
pulsePhase: number;
|
||||
radius: number;
|
||||
}
|
||||
|
||||
interface Pulse {
|
||||
edgeIdx: number;
|
||||
progress: number;
|
||||
speed: number;
|
||||
direction: 1 | -1;
|
||||
}
|
||||
|
||||
const NODE_COUNT = 80;
|
||||
const MAX_EDGE_DIST = 180;
|
||||
const MOUSE_RADIUS = 250;
|
||||
const MOUSE_REPEL = 0.3;
|
||||
const DRIFT_SPEED = 0.15;
|
||||
const RETURN_FORCE = 0.005;
|
||||
|
||||
function createNodes(w: number, h: number): Node[] {
|
||||
const nodes: Node[] = [];
|
||||
for (let i = 0; i < NODE_COUNT; i++) {
|
||||
const x = Math.random() * w;
|
||||
const y = Math.random() * h;
|
||||
nodes.push({
|
||||
x,
|
||||
y,
|
||||
baseX: x,
|
||||
baseY: y,
|
||||
vx: (Math.random() - 0.5) * DRIFT_SPEED,
|
||||
vy: (Math.random() - 0.5) * DRIFT_SPEED,
|
||||
isHub: i < 12,
|
||||
pulsePhase: Math.random() * Math.PI * 2,
|
||||
radius: i < 12 ? 2.5 : 1.2,
|
||||
});
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function buildEdges(nodes: Node[]): [number, number][] {
|
||||
const edges: [number, number][] = [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const dx = nodes[i].x - nodes[j].x;
|
||||
const dy = nodes[i].y - nodes[j].y;
|
||||
if (dx * dx + dy * dy < MAX_EDGE_DIST * MAX_EDGE_DIST) {
|
||||
edges.push([i, j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
export function Background() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const nodesRef = useRef<Node[]>([]);
|
||||
const pulsesRef = useRef<Pulse[]>([]);
|
||||
const mouseRef = useRef({ x: -1000, y: -1000 });
|
||||
const frameRef = useRef<number>(0);
|
||||
const timeRef = useRef(0);
|
||||
|
||||
const spawnPulse = useCallback((edges: [number, number][]) => {
|
||||
if (edges.length === 0) return;
|
||||
const idx = Math.floor(Math.random() * edges.length);
|
||||
pulsesRef.current.push({
|
||||
edgeIdx: idx,
|
||||
progress: 0,
|
||||
speed: 0.008 + Math.random() * 0.012,
|
||||
direction: Math.random() > 0.5 ? 1 : -1,
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
let w = window.innerWidth;
|
||||
let h = window.innerHeight;
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
|
||||
nodesRef.current = createNodes(w, h);
|
||||
pulsesRef.current = [];
|
||||
|
||||
const handleResize = () => {
|
||||
w = window.innerWidth;
|
||||
h = window.innerHeight;
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
nodesRef.current = createNodes(w, h);
|
||||
pulsesRef.current = [];
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
mouseRef.current = { x: e.clientX, y: e.clientY };
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
mouseRef.current = { x: -1000, y: -1000 };
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseleave", handleMouseLeave);
|
||||
|
||||
const animate = () => {
|
||||
timeRef.current += 1;
|
||||
const nodes = nodesRef.current;
|
||||
const mx = mouseRef.current.x;
|
||||
const my = mouseRef.current.y;
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
// Update node positions
|
||||
for (const node of nodes) {
|
||||
// Drift
|
||||
node.x += node.vx;
|
||||
node.y += node.vy;
|
||||
|
||||
// Return to base
|
||||
node.vx += (node.baseX - node.x) * RETURN_FORCE;
|
||||
node.vy += (node.baseY - node.y) * RETURN_FORCE;
|
||||
|
||||
// Damping
|
||||
node.vx *= 0.99;
|
||||
node.vy *= 0.99;
|
||||
|
||||
// Mouse interaction — repel
|
||||
const dmx = node.x - mx;
|
||||
const dmy = node.y - my;
|
||||
const distMouse = Math.sqrt(dmx * dmx + dmy * dmy);
|
||||
if (distMouse < MOUSE_RADIUS && distMouse > 0) {
|
||||
const force = (1 - distMouse / MOUSE_RADIUS) * MOUSE_REPEL;
|
||||
node.vx += (dmx / distMouse) * force;
|
||||
node.vy += (dmy / distMouse) * force;
|
||||
}
|
||||
|
||||
// Boundary wrap
|
||||
if (node.x < -20) node.x = w + 20;
|
||||
if (node.x > w + 20) node.x = -20;
|
||||
if (node.y < -20) node.y = h + 20;
|
||||
if (node.y > h + 20) node.y = -20;
|
||||
}
|
||||
|
||||
// Build edges dynamically
|
||||
const edges = buildEdges(nodes);
|
||||
|
||||
// Draw edges
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
const [a, b] = edges[i];
|
||||
const dx = nodes[a].x - nodes[b].x;
|
||||
const dy = nodes[a].y - nodes[b].y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
const alpha = (1 - dist / MAX_EDGE_DIST) * 0.12;
|
||||
|
||||
// Brighter edges near mouse
|
||||
const edgeMx = (nodes[a].x + nodes[b].x) / 2;
|
||||
const edgeMy = (nodes[a].y + nodes[b].y) / 2;
|
||||
const edgeMouseDist = Math.sqrt(
|
||||
(edgeMx - mx) ** 2 + (edgeMy - my) ** 2
|
||||
);
|
||||
const mouseBoost =
|
||||
edgeMouseDist < MOUSE_RADIUS
|
||||
? (1 - edgeMouseDist / MOUSE_RADIUS) * 0.2
|
||||
: 0;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(nodes[a].x, nodes[a].y);
|
||||
ctx.lineTo(nodes[b].x, nodes[b].y);
|
||||
ctx.strokeStyle = `rgba(192, 119, 144, ${alpha + mouseBoost})`;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Spawn pulses
|
||||
if (timeRef.current % 8 === 0) {
|
||||
spawnPulse(edges);
|
||||
}
|
||||
|
||||
// Draw & update pulses
|
||||
const activePulses: Pulse[] = [];
|
||||
for (const pulse of pulsesRef.current) {
|
||||
pulse.progress += pulse.speed;
|
||||
if (pulse.progress > 1) {
|
||||
// Chain reaction — sometimes spawn a new pulse from the destination node
|
||||
if (Math.random() < 0.4 && pulse.edgeIdx < edges.length) {
|
||||
const destNode =
|
||||
pulse.direction === 1
|
||||
? edges[pulse.edgeIdx][1]
|
||||
: edges[pulse.edgeIdx][0];
|
||||
// Find connected edges from destination
|
||||
const connected = edges
|
||||
.map((e, idx) => ({ e, idx }))
|
||||
.filter(
|
||||
({ e, idx }) =>
|
||||
idx !== pulse.edgeIdx &&
|
||||
(e[0] === destNode || e[1] === destNode)
|
||||
);
|
||||
if (connected.length > 0) {
|
||||
const next =
|
||||
connected[Math.floor(Math.random() * connected.length)];
|
||||
activePulses.push({
|
||||
edgeIdx: next.idx,
|
||||
progress: 0,
|
||||
speed: 0.01 + Math.random() * 0.01,
|
||||
direction:
|
||||
next.e[0] === destNode ? 1 : -1,
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pulse.edgeIdx >= edges.length) continue;
|
||||
|
||||
const [a, b] = edges[pulse.edgeIdx];
|
||||
const na = nodes[a];
|
||||
const nb = nodes[b];
|
||||
const t = pulse.direction === 1 ? pulse.progress : 1 - pulse.progress;
|
||||
const px = na.x + (nb.x - na.x) * t;
|
||||
const py = na.y + (nb.y - na.y) * t;
|
||||
|
||||
// Pulse head glow
|
||||
const gradient = ctx.createRadialGradient(px, py, 0, px, py, 12);
|
||||
gradient.addColorStop(0, "rgba(192, 119, 144, 0.6)");
|
||||
gradient.addColorStop(0.5, "rgba(192, 119, 144, 0.15)");
|
||||
gradient.addColorStop(1, "rgba(192, 119, 144, 0)");
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, 12, 0, Math.PI * 2);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
|
||||
// Bright core
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, 2, 0, Math.PI * 2);
|
||||
ctx.fillStyle = "rgba(219, 168, 184, 0.8)";
|
||||
ctx.fill();
|
||||
|
||||
// Trail
|
||||
const trailLen = 0.15;
|
||||
const tStart = Math.max(0, t - trailLen);
|
||||
const sx = na.x + (nb.x - na.x) * tStart;
|
||||
const sy = na.y + (nb.y - na.y) * tStart;
|
||||
const trailGrad = ctx.createLinearGradient(sx, sy, px, py);
|
||||
trailGrad.addColorStop(0, "rgba(192, 119, 144, 0)");
|
||||
trailGrad.addColorStop(1, "rgba(192, 119, 144, 0.3)");
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sx, sy);
|
||||
ctx.lineTo(px, py);
|
||||
ctx.strokeStyle = trailGrad;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
activePulses.push(pulse);
|
||||
}
|
||||
pulsesRef.current = activePulses;
|
||||
|
||||
// Draw nodes
|
||||
for (const node of nodes) {
|
||||
const pulse =
|
||||
Math.sin(timeRef.current * 0.03 + node.pulsePhase) * 0.5 + 0.5;
|
||||
|
||||
// Mouse proximity glow
|
||||
const dMouse = Math.sqrt(
|
||||
(node.x - mx) ** 2 + (node.y - my) ** 2
|
||||
);
|
||||
const mouseGlow =
|
||||
dMouse < MOUSE_RADIUS ? (1 - dMouse / MOUSE_RADIUS) : 0;
|
||||
|
||||
if (node.isHub) {
|
||||
// Hub outer glow
|
||||
const glowR = node.radius * (3 + pulse * 2) + mouseGlow * 8;
|
||||
const glowAlpha = 0.06 + pulse * 0.04 + mouseGlow * 0.1;
|
||||
const gradient = ctx.createRadialGradient(
|
||||
node.x, node.y, 0,
|
||||
node.x, node.y, glowR
|
||||
);
|
||||
gradient.addColorStop(0, `rgba(192, 119, 144, ${glowAlpha})`);
|
||||
gradient.addColorStop(1, "rgba(192, 119, 144, 0)");
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, glowR, 0, Math.PI * 2);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
|
||||
// Hub core
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(192, 119, 144, ${0.4 + pulse * 0.2 + mouseGlow * 0.3})`;
|
||||
ctx.fill();
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, node.radius + mouseGlow * 1.5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(120, 113, 108, ${0.15 + mouseGlow * 0.4})`;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
frameRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
frameRef.current = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseleave", handleMouseLeave);
|
||||
};
|
||||
}, [spawnPulse]);
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none fixed inset-0 z-0 overflow-hidden">
|
||||
<canvas ref={canvasRef} className="absolute inset-0 h-full w-full" />
|
||||
|
||||
{/* Primary glow — top center */}
|
||||
<div
|
||||
className="absolute -top-[20%] left-1/2 h-[800px] w-[800px] -translate-x-1/2 rounded-full"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(circle, rgba(122,59,78,0.15) 0%, rgba(122,59,78,0.05) 40%, transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Accent glow — bottom right */}
|
||||
<div
|
||||
className="absolute -bottom-[10%] -right-[10%] h-[600px] w-[600px] rounded-full"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(circle, rgba(194,112,62,0.08) 0%, rgba(194,112,62,0.03) 40%, transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Secondary glow — left */}
|
||||
<div
|
||||
className="absolute top-[40%] -left-[10%] h-[500px] w-[500px] rounded-full"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(circle, rgba(74,107,130,0.06) 0%, transparent 60%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
src/components/layout/footer.tsx
Normal file
22
src/components/layout/footer.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Logo } from "@/components/brand/logo";
|
||||
|
||||
export function Footer({ className }: { className?: string }) {
|
||||
return (
|
||||
<footer className={cn("border-t border-surface-border bg-surface-bg/80 py-12", className)}>
|
||||
<div className="mx-auto max-w-6xl px-6">
|
||||
<div className="flex flex-col items-center gap-6 md:flex-row md:justify-between">
|
||||
<Logo variant="dark" size="sm" showWordmark />
|
||||
<div className="flex items-center gap-6 text-sm text-text-muted">
|
||||
<a href="#" className="hover:text-text-primary transition-colors">Dokumentacja</a>
|
||||
<a href="https://gitlab.com/automancer" className="hover:text-text-primary transition-colors">GitLab</a>
|
||||
<a href="#" className="hover:text-text-primary transition-colors">Kontakt</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 text-center text-xs text-text-muted">
|
||||
© 2026 Automancer. Agentic AI Platform.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
24
src/components/layout/header.tsx
Normal file
24
src/components/layout/header.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Logo } from "@/components/brand/logo";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b border-surface-border bg-surface-bg/80 backdrop-blur-md">
|
||||
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
|
||||
<Logo variant="dark" size="sm" showWordmark />
|
||||
<nav className="hidden items-center gap-6 md:flex">
|
||||
<a href="#features" className="text-sm text-text-secondary hover:text-text-primary transition-colors">
|
||||
Funkcje
|
||||
</a>
|
||||
<a href="#architecture" className="text-sm text-text-secondary hover:text-text-primary transition-colors">
|
||||
Architektura
|
||||
</a>
|
||||
<a href="#tech" className="text-sm text-text-secondary hover:text-text-primary transition-colors">
|
||||
Stack
|
||||
</a>
|
||||
</nav>
|
||||
<Button size="sm">Rozpocznij</Button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
31
src/components/ui/badge.tsx
Normal file
31
src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type BadgeVariant = "pipeline" | "k8s" | "container" | "agent" | "default";
|
||||
|
||||
interface BadgeProps {
|
||||
variant?: BadgeVariant;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const variantStyles: Record<BadgeVariant, string> = {
|
||||
pipeline: "bg-primary-700 text-primary-200",
|
||||
k8s: "bg-info-dark text-info-light",
|
||||
container: "bg-accent-700 text-accent-200",
|
||||
agent: "bg-primary-500 text-neutral-50",
|
||||
default: "bg-neutral-700 text-neutral-300",
|
||||
};
|
||||
|
||||
export function Badge({ variant = "default", children, className }: BadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 px-3 py-1 text-sm font-medium rounded-[var(--radius-sm)]",
|
||||
variantStyles[variant],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
51
src/components/ui/button.tsx
Normal file
51
src/components/ui/button.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type ButtonHTMLAttributes, forwardRef } from "react";
|
||||
|
||||
type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
|
||||
type ButtonSize = "sm" | "md" | "lg";
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
as?: "button" | "a";
|
||||
href?: string;
|
||||
}
|
||||
|
||||
const variantStyles: Record<ButtonVariant, string> = {
|
||||
primary:
|
||||
"bg-primary-500 text-neutral-50 hover:bg-primary-400 active:bg-primary-600",
|
||||
secondary:
|
||||
"bg-transparent text-primary-300 border border-primary-500 hover:bg-primary-500/10 active:bg-primary-500/20",
|
||||
ghost:
|
||||
"bg-transparent text-neutral-400 hover:text-neutral-50 hover:bg-neutral-700/50",
|
||||
danger:
|
||||
"bg-error text-neutral-50 hover:bg-error/80 active:bg-error-dark",
|
||||
};
|
||||
|
||||
const sizeStyles: Record<ButtonSize, string> = {
|
||||
sm: "px-3 py-1.5 text-sm rounded-[var(--radius-sm)]",
|
||||
md: "px-5 py-2.5 text-base rounded-[var(--radius-md)]",
|
||||
lg: "px-7 py-3 text-lg rounded-[var(--radius-md)]",
|
||||
};
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = "primary", size = "md", children, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center font-medium transition-colors cursor-pointer",
|
||||
variantStyles[variant],
|
||||
sizeStyles[size],
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = "Button";
|
||||
export { Button, type ButtonProps };
|
||||
27
src/components/ui/card.tsx
Normal file
27
src/components/ui/card.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type HTMLAttributes, forwardRef } from "react";
|
||||
|
||||
interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
||||
hoverable?: boolean;
|
||||
}
|
||||
|
||||
const Card = forwardRef<HTMLDivElement, CardProps>(
|
||||
({ className, hoverable = false, children, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-surface-card border border-surface-border rounded-[var(--radius-lg)] p-6",
|
||||
hoverable && "transition-all hover:bg-surface-card-hover hover:shadow-[var(--shadow-glow)]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Card.displayName = "Card";
|
||||
export { Card, type CardProps };
|
||||
4
src/lib/tokens.ts
Normal file
4
src/lib/tokens.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import tokens from "../../.automancer/context/design-system/tokens.json";
|
||||
|
||||
export type Tokens = typeof tokens;
|
||||
export default tokens;
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
34
tsconfig.json
Normal file
34
tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user