feat: add site-wide glossary with hover tooltips
CI / TypeScript — type-check (push) Successful in 8s
CI / Publish — build & push image (push) Successful in 8s
CI / Deploy — pull & restart on host (push) Successful in 2s

Every abbreviation and technical term used in the site's copy (RBAC,
NAPALM, config drift, ...) now links to a new /glossary page and shows
a short definition on hover, wherever it appears in body text. Product
and vendor brand names are deliberately excluded — the glossary stays
a dictionary of vocabulary, not a company directory.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Christian Manivong
2026-07-02 18:37:35 +02:00
co-authored by Claude Sonnet 5
parent 2a8a87bdd1
commit 38c7fb1db9
14 changed files with 758 additions and 31 deletions
+15 -14
View File
@@ -7,6 +7,7 @@ import {
ShieldCheckIcon,
} from '@heroicons/react/24/outline'
import { useLang } from '../context/LangContext'
import { linkify } from '../lib/glossary'
const DRIVERS = [
'Arista EOS', 'Cisco IOS', 'Cisco IOS-XE', 'Cisco NX-OS', 'Fritz!Box',
@@ -290,7 +291,7 @@ export default function Home() {
<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">
{h.hero.sub}
{linkify(h.hero.sub)}
</p>
<div className="flex flex-wrap gap-4 justify-center">
<a
@@ -319,7 +320,7 @@ export default function Home() {
{/* 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">{h.problem}</p>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.problem)}</p>
</div>
</section>
@@ -334,21 +335,21 @@ export default function Home() {
<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">{h.cap1.body}</p>
<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">{h.cap2.body}</p>
<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">{h.cap3.body}</p>
<p className="text-sm text-slate-400 leading-relaxed">{linkify(h.cap3.body)}</p>
</div>
</div>
</div>
@@ -360,7 +361,7 @@ export default function Home() {
<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">{h.driversSub}</p>
<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">
@@ -384,7 +385,7 @@ export default function Home() {
<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">{h.screenshot1.body}</p>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot1.body)}</p>
</div>
<BrowserFrame label="netork.local / devices / ap-001">
<MockDeviceDetail />
@@ -396,13 +397,13 @@ export default function Home() {
</BrowserFrame>
<div>
<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">{h.screenshot2.body}</p>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot2.body)}</p>
</div>
</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">{h.screenshot3.body}</p>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot3.body)}</p>
</div>
<BrowserFrame label="netork.local / devices / ap-001 / security">
<MockSecurity />
@@ -414,7 +415,7 @@ export default function Home() {
</BrowserFrame>
<div>
<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">{h.screenshot4.body}</p>
<p className="text-base text-slate-400 leading-relaxed">{linkify(h.screenshot4.body)}</p>
</div>
</div>
</div>
@@ -429,7 +430,7 @@ export default function Home() {
<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">{h.nis2Body}</p>
<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" />
@@ -442,7 +443,7 @@ export default function Home() {
<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">{m.detail}</p>
<p className="text-xs text-slate-500 mt-0.5">{linkify(m.detail)}</p>
</div>
</div>
))}
@@ -465,7 +466,7 @@ export default function Home() {
<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">{h.pluginsBody}</p>
<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>
@@ -481,7 +482,7 @@ export default function Home() {
<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">{h.deployBody}</p>
<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