feat: carousel

This commit is contained in:
Nik L
2026-03-09 17:09:25 -04:00
parent df393e623a
commit a5af681b9e
7 changed files with 124 additions and 15 deletions

View File

@@ -155,6 +155,12 @@ section {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
} }
/* Carousel progress bar */
@keyframes progress {
from { width: 0; }
to { width: 100%; }
}
/* Fade in animation for sections */ /* Fade in animation for sections */
@keyframes fade-up { @keyframes fade-up {
from { from {

View File

@@ -1,8 +1,72 @@
'use client'
import { useState, useEffect, useRef } from 'react'
import { Eye } from 'lucide-react' import { Eye } from 'lucide-react'
const slides = [
{
label: 'Dashboard',
src: '/dashboard.png',
alt: 'GreyProxy dashboard overview showing connection stats and activity',
},
{
label: 'Pending requests',
src: '/pending_requests.png',
alt: 'GreyProxy pending network requests with Allow and Deny controls',
},
{
label: 'Rules',
src: '/rules.png',
alt: 'GreyProxy domain rules configuration for allow and deny policies',
},
{
label: 'Logs',
src: '/logs.png',
alt: 'GreyProxy connection logs showing all outbound network activity',
},
]
const INTERVAL = 4000
export function Observability() { export function Observability() {
const [active, setActive] = useState(0)
const [paused, setPaused] = useState(false)
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
// Key to force re-mount of the progress bar so animation restarts
const [tick, setTick] = useState(0)
function goTo(i: number) {
setActive(i)
setTick((t) => t + 1)
resetTimer()
}
function advance() {
setActive((i) => (i + 1) % slides.length)
setTick((t) => t + 1)
}
function resetTimer() {
if (timerRef.current) clearInterval(timerRef.current)
if (!paused) {
timerRef.current = setInterval(advance, INTERVAL)
}
}
useEffect(() => {
if (paused) {
if (timerRef.current) clearInterval(timerRef.current)
return
}
timerRef.current = setInterval(advance, INTERVAL)
return () => {
if (timerRef.current) clearInterval(timerRef.current)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [paused])
return ( return (
<section id="features" className="py-24 px-6 border-t border-border/30"> <section id="features" className="py-24 px-4 sm:px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl"> <div className="mx-auto max-w-5xl">
<div className="max-w-2xl mb-16"> <div className="max-w-2xl mb-16">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
@@ -21,21 +85,60 @@ export function Observability() {
</p> </p>
</div> </div>
<div className="mx-auto max-w-3xl"> <div
<div className="flex items-center gap-3 mb-4"> className="mx-auto max-w-3xl"
<div className="flex items-center justify-center w-8 h-8 rounded-md bg-primary/10 text-primary"> onMouseEnter={() => setPaused(true)}
<Eye className="h-4 w-4" /> onMouseLeave={() => setPaused(false)}
</div> >
<h3 className="font-sans font-semibold text-sm">GreyProxy dashboard</h3> {/* Screenshot with crossfade */}
<div className="relative rounded-lg border border-border/40 overflow-hidden bg-card/30">
{slides.map((slide, i) => (
<img
key={slide.label}
src={slide.src}
alt={slide.alt}
className={`w-full h-auto transition-opacity duration-700 ${
i === active ? 'opacity-100 relative' : 'opacity-0 absolute inset-0'
}`}
/>
))}
</div> </div>
<div className="rounded-lg border border-border/40 overflow-hidden bg-card/30">
<img {/* Progress indicators + labels */}
src="/greyproxy.png" <div className="flex items-center justify-center gap-4 mt-5">
alt="GreyProxy dashboard showing pending network requests with Allow and Deny controls" {slides.map((slide, i) => (
className="w-full h-auto" <button
/> key={slide.label}
onClick={() => goTo(i)}
className="flex items-center gap-2 group"
>
<div className="relative h-1.5 w-8 rounded-full bg-border/50 overflow-hidden">
{i === active ? (
<div
key={tick}
className="absolute inset-y-0 left-0 rounded-full bg-primary"
style={
paused
? { width: '100%' }
: { animation: `progress ${INTERVAL}ms linear forwards` }
}
/>
) : (
<div className="absolute inset-0 rounded-full bg-transparent group-hover:bg-muted-foreground/30 transition-colors" />
)}
</div>
<span
className={`text-xs font-sans transition-colors hidden sm:inline ${
i === active ? 'text-foreground font-medium' : 'text-muted-foreground'
}`}
>
{slide.label}
</span>
</button>
))}
</div> </div>
<p className="text-xs text-muted-foreground font-serif leading-relaxed mt-4">
<p className="text-xs text-muted-foreground font-serif leading-relaxed mt-5 text-center">
Every outbound request is visible. Allow trusted domains, block unknown ones, Every outbound request is visible. Allow trusted domains, block unknown ones,
and adjust policies live as your agent works. and adjust policies live as your agent works.
</p> </p>

View File

@@ -7,7 +7,7 @@ export function Problem() {
{/* Section 1: Stochastic risk */} {/* Section 1: Stochastic risk */}
<div className="mb-12 sm:mb-16"> <div className="mb-12 sm:mb-16">
<h2 className="font-serif text-2xl sm:text-3xl md:text-4xl font-semibold tracking-tight mb-3 max-w-3xl"> <h2 className="font-serif text-2xl sm:text-3xl md:text-4xl font-semibold tracking-tight mb-3 max-w-3xl">
Your agent runs as <em className="italic text-primary">you</em>. Your agent runs as <em className="italic text-red-400/90">you</em>.
</h2> </h2>
<p className="text-muted-foreground font-serif text-sm sm:text-base leading-relaxed max-w-full mt-4 mb-10"> <p className="text-muted-foreground font-serif text-sm sm:text-base leading-relaxed max-w-full mt-4 mb-10">
Agents inherit your full permissions and decide what to access at runtime. Here&apos;s what that looks like... Agents inherit your full permissions and decide what to access at runtime. Here&apos;s what that looks like...

BIN
public/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
public/logs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

View File

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

BIN
public/rules.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB