Files
website/src/pages/Home.tsx
T
Christian ManivongandClaude Opus 5.5 011f816fc9 feat: replace UI mockups with real netOrk screenshots
The homepage showed seven hand-built JSX imitations of the netOrk UI. They
are gone; every image is now a screenshot of netOrk v0.28.0 itself, taken
from an anonymized copy of a production database (scripts/demo) with
scripts/screenshots/capture.py and published as WebP (~630 KB for all eight).

- Hero: the device inventory. Walkthrough: device detail, VLANs, the Security
  tab, the vulnerability triage queue (replacing the config-diff row), the
  dashboard and service checks (new row 6). NIS2: the audit log, filtered to
  what people did.
- Copy follows the images: row 3 describes the security assessment, row 4 the
  triage queue; row 2 no longer claims corrections are always automatic;
  18 widgets. Alt texts in both languages.
- Also fixed on the homepage: the NIS2 teaser for Art. 21 (2e) and the
  container list of a deployment (three worker pools, plus Flower, registry,
  APT cache and the Signal gateway).
- Demo tooling hardened on the real dump: secrets inside JSON (Wi-Fi keys),
  reverse DNS zones, glued identifiers, tens of thousands of CrowdSec
  addresses, a schema newer than the release (anonymize, then downgrade),
  MFA-enforcing roles, and click steps for view filters.
- DESIGN.md: real screenshots only. PAGES.md: the six rows as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 08:24:51 +02:00

272 lines
12 KiB
TypeScript

import { Link } from 'react-router-dom'
import {
MagnifyingGlassIcon,
ChartBarIcon,
WrenchScrewdriverIcon,
PuzzlePieceIcon,
ShieldCheckIcon,
} from '@heroicons/react/24/outline'
import { useLang } from '../context/LangContext'
import { linkify } from '../lib/glossary'
const DRIVERS = [
'Fritz!Box', 'HPE OfficeConnect', 'HPE ProCurve / Aruba', 'Linux', 'Netgear',
'OpenMediaVault', 'OpenWrt', 'OPNsense', 'Proxmox VE', 'QNAP QTS', 'Sonos',
'TP-Link JetStream', 'Yealink', 'Zyxel VMG',
]
function Screenshot({ src, label, alt }: { src: string; label: string; alt: string }) {
return (
<div className="rounded-xl border border-slate-700 overflow-hidden shadow-2xl">
<div className="flex items-center gap-1.5 border-b border-slate-700 bg-slate-800 px-4 py-2.5">
<span className="h-2.5 w-2.5 rounded-full bg-red-500/70" />
<span className="h-2.5 w-2.5 rounded-full bg-yellow-500/70" />
<span className="h-2.5 w-2.5 rounded-full bg-green-500/70" />
<span className="ml-4 text-xs text-slate-500 font-mono">{label}</span>
</div>
<img src={src} alt={alt} loading="lazy" className="block w-full" width={1600} height={1000} />
</div>
)
}
export default function Home() {
const { t } = useLang()
const h = t.home
return (
<>
{/* 1 — Hero */}
<section className="py-24 md:py-32">
<div className="max-w-7xl mx-auto px-6 text-center">
<h1 className="text-4xl md:text-6xl font-bold text-slate-100 leading-tight mb-6">
{h.hero.line1}<br />
<span className="text-sky-400">{h.hero.line2}</span>
</h1>
<p className="text-base text-slate-400 leading-relaxed max-w-2xl mx-auto mb-10">
{linkify(h.hero.sub)}
</p>
<div className="flex flex-wrap gap-4 justify-center">
<a
href="mailto:christian@manivong.de"
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-sky-600 hover:bg-sky-500 text-white font-medium transition-colors"
>
{h.hero.cta1}
</a>
<Link
to="/features"
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg border border-slate-700 hover:border-slate-500 text-slate-300 hover:text-slate-100 transition-colors"
>
{h.hero.cta2}
</Link>
</div>
</div>
<div className="max-w-7xl mx-auto px-6 mt-16">
<Screenshot src="/screenshots/devices.webp" label="netork / devices" alt={h.shotAlt.devices} />
</div>
</section>
<div className="border-t border-slate-800" />
{/* 2 — Problem Statement */}
<section className="py-16">
<div className="max-w-3xl mx-auto px-6 text-center">
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.problem)}</p>
</div>
</section>
<div className="border-t border-slate-800" />
{/* 3 — Core Capabilities */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-6">
<div className="grid md:grid-cols-3 gap-6">
<div className="rounded-xl border border-slate-800 bg-slate-900 p-6">
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
<MagnifyingGlassIcon className="h-5 w-5 text-sky-400" />
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-100">{h.cap1.title}</h3>
<p className="text-sm text-slate-400 leading-relaxed">{linkify(h.cap1.body)}</p>
</div>
<div className="rounded-xl border border-slate-800 bg-slate-900 p-6">
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
<ChartBarIcon className="h-5 w-5 text-sky-400" />
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-100">{h.cap2.title}</h3>
<p className="text-sm text-slate-400 leading-relaxed">{linkify(h.cap2.body)}</p>
</div>
<div className="rounded-xl border border-slate-800 bg-slate-900 p-6">
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
<WrenchScrewdriverIcon className="h-5 w-5 text-sky-400" />
</div>
<h3 className="mb-2 text-lg font-semibold text-slate-100">{h.cap3.title}</h3>
<p className="text-sm text-slate-400 leading-relaxed">{linkify(h.cap3.body)}</p>
</div>
</div>
</div>
</section>
<div className="border-t border-slate-800" />
{/* 4 — Driver Grid */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-6 text-center">
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.driversHeading}</h2>
<p className="text-base text-slate-400 leading-relaxed max-w-xl mx-auto mb-10">{linkify(h.driversSub)}</p>
<div className="flex flex-wrap justify-center gap-3">
{DRIVERS.map((d) => (
<span key={d} className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full border border-slate-700 bg-slate-900 text-xs font-medium text-slate-300">
{d}
</span>
))}
</div>
<div className="mt-8">
<Link to="/drivers" className="text-sky-400 hover:text-sky-300 transition-colors text-sm font-medium">
{h.driversLink}
</Link>
</div>
</div>
</section>
<div className="border-t border-slate-800" />
{/* 5 — Screenshot Walkthrough */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-6 space-y-24">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div>
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot1.heading}</h2>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot1.body)}</p>
</div>
<Screenshot src="/screenshots/device-detail.webp" label="netork / devices / ap-floor1" alt={h.shotAlt.deviceDetail} />
</div>
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="md:order-2">
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot2.heading}</h2>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot2.body)}</p>
</div>
<Screenshot src="/screenshots/vlans.webp" label="netork / vlans" alt={h.shotAlt.vlans} />
</div>
<div className="grid md:grid-cols-2 gap-12 items-center">
<div>
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot3.heading}</h2>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot3.body)}</p>
</div>
<Screenshot src="/screenshots/device-security.webp" label="netork / devices / proxy-01 / security" alt={h.shotAlt.deviceSecurity} />
</div>
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="md:order-2">
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot4.heading}</h2>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot4.body)}</p>
</div>
<Screenshot src="/screenshots/vulnerabilities.webp" label="netork / vulnerabilities" alt={h.shotAlt.vulnerabilities} />
</div>
<div className="grid md:grid-cols-2 gap-12 items-center">
<div>
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot5.heading}</h2>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot5.body)}</p>
</div>
<Screenshot src="/screenshots/dashboard.webp" label="netork / dashboard" alt={h.shotAlt.dashboard} />
</div>
<div className="grid md:grid-cols-2 gap-12 items-center">
<div className="md:order-2">
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.screenshot6.heading}</h2>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot6.body)}</p>
</div>
<Screenshot src="/screenshots/service-checks.webp" label="netork / monitoring / checks" alt={h.shotAlt.serviceChecks} />
</div>
</div>
</section>
<div className="border-t border-slate-800" />
{/* 5b — NIS2 */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-6">
<div className="grid md:grid-cols-2 gap-12 items-center">
<div>
<p className="text-xs font-semibold tracking-widest uppercase text-sky-500 mb-3">{h.nis2Label}</p>
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.nis2Heading}</h2>
<p className="text-base text-slate-400 leading-relaxed mb-8">{linkify(h.nis2Body)}</p>
<div className="mb-8">
<Link to="/nis2" className="inline-flex items-center gap-2 text-sky-400 hover:text-sky-300 transition-colors text-sm font-medium">
<ShieldCheckIcon className="h-4 w-4" />
{h.nis2Link}
</Link>
</div>
<div className="space-y-4">
{h.nis2Items.map((m) => (
<div key={m.art} className="flex gap-4">
<span className="mt-0.5 shrink-0 w-28 text-xs font-mono text-sky-500">{m.art}</span>
<div>
<p className="text-sm font-medium text-slate-200">{m.label}</p>
<p className="text-xs text-slate-500 mt-0.5">{linkify(m.detail)}</p>
</div>
</div>
))}
</div>
</div>
<Screenshot src="/screenshots/audit-log.webp" label="netork / audit-log" alt={h.shotAlt.auditLog} />
</div>
</div>
</section>
<div className="border-t border-slate-800" />
{/* 6 — Plugin System */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-6">
<div className="rounded-xl border border-slate-800 bg-slate-900 p-8 md:p-12">
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-sky-600/10">
<PuzzlePieceIcon className="h-5 w-5 text-sky-400" />
</div>
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.pluginsHeading}</h2>
<p className="text-base text-slate-400 leading-relaxed max-w-2xl mb-6">{linkify(h.pluginsBody)}</p>
<Link to="/plugins" className="text-sky-400 hover:text-sky-300 transition-colors text-sm font-medium">
{h.pluginsLink}
</Link>
</div>
</div>
</section>
<div className="border-t border-slate-800" />
{/* 7 — Deployment */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-6">
<div className="grid md:grid-cols-2 gap-12 items-start">
<div>
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.deployHeading}</h2>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.deployBody)}</p>
</div>
<pre className="rounded-xl border border-slate-800 bg-slate-900 p-6 font-mono text-sm text-slate-300 overflow-x-auto">
<code>{`# Clone + configure
git clone https://gitea.example.com/netork/netork.git
cp .env.example .env
# edit .env (DB URL, Redis password, secret key)
# Deploy
bash scripts/deploy.sh 192.168.1.10`}</code>
</pre>
</div>
</div>
</section>
<div className="border-t border-slate-800" />
{/* 8 — CTA */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-6 text-center">
<h2 className="text-2xl md:text-3xl font-bold text-slate-100 mb-4">{h.ctaHeading}</h2>
<p className="text-base text-slate-400 leading-relaxed mb-8">{h.ctaBody}</p>
<a
href="mailto:christian@manivong.de"
className="inline-flex items-center gap-2 px-6 py-3 rounded-lg bg-sky-600 hover:bg-sky-500 text-white font-medium transition-colors"
>
{h.ctaButton}
</a>
</div>
</section>
</>
)
}