Skip to content

Commit 4a7fc60

Browse files
committed
feat: blog, about page, remove Exegol refs, clean em
dashes - Add Blog section (/blog + /blog/:slug) with article listing and empty state - Add About page in docs (/docs/about): why, components, design choices, team - Remove all mentions of Exegol (HomePage FAQ, PricingPage) - Remove em dashes from all visible text across the site - Add Blog link in navbar
1 parent c91e982 commit 4a7fc60

File tree

12 files changed

+338
-24
lines changed

12 files changed

+338
-24
lines changed

src/components/DocsLayout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const sections = [
2727
title: 'More',
2828
items: [
2929
{ label: 'FAQ', to: '/docs/faq' },
30+
{ label: 'About', to: '/docs/about' },
3031
],
3132
},
3233
];

src/components/Layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FlyingPigeons } from './FlyingPigeons';
44

55
const navLinks = [
66
{ to: '/', label: 'Overview', end: true },
7+
{ to: '/blog', label: 'Blog' },
78
{ to: '/community', label: 'Community' },
89
{ to: '/pricing', label: 'Pricing' },
910
{ to: '/docs', label: 'Docs' },

src/config/blog.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export interface BlogPost {
2+
slug: string;
3+
title: string;
4+
date: string; // ISO format YYYY-MM-DD
5+
tags: string[];
6+
excerpt: string;
7+
content: string; // Markdown-like JSX string — rendered in BlogPostPage
8+
}
9+
10+
export const blogPosts: BlogPost[] = [
11+
// Add articles here. Example:
12+
// {
13+
// slug: 'why-we-built-nihil',
14+
// title: 'Why we built Nihil',
15+
// date: '2026-03-11',
16+
// tags: ['tooling', 'pentest'],
17+
// excerpt: 'A quick rundown of why we started this project and what problems it solves.',
18+
// content: '...',
19+
// },
20+
];

src/pages/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { UsagePage } from './docs/UsagePage';
1414
import { CompletionPage } from './docs/CompletionPage';
1515
import { HistoryPage } from './docs/HistoryPage';
1616
import { FaqPage } from './docs/FaqPage';
17+
import { AboutPage } from './docs/AboutPage';
18+
import { BlogPage } from './BlogPage';
19+
import { BlogPostPage } from './BlogPostPage';
1720

1821
export const App: React.FC = () => {
1922
return (
@@ -61,6 +64,14 @@ export const App: React.FC = () => {
6164
path="/docs/faq"
6265
element={<DocsLayout><FaqPage /></DocsLayout>}
6366
/>
67+
<Route
68+
path="/docs/about"
69+
element={<DocsLayout><AboutPage /></DocsLayout>}
70+
/>
71+
72+
{/* Blog */}
73+
<Route path="/blog" element={<BlogPage />} />
74+
<Route path="/blog/:slug" element={<BlogPostPage />} />
6475
</Routes>
6576
</Layout>
6677
);

src/pages/BlogPage.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { blogPosts } from '../config/blog';
4+
5+
export const BlogPage: React.FC = () => {
6+
const sorted = [...blogPosts].sort((a, b) => b.date.localeCompare(a.date));
7+
8+
return (
9+
<div className="space-y-12 max-w-3xl mx-auto">
10+
<header className="space-y-3">
11+
<h1 className="font-display text-3xl md:text-4xl font-bold tracking-tight text-white">
12+
Blog
13+
</h1>
14+
<p className="text-slate-400 text-lg">
15+
Notes, write-ups and thoughts from TheNullPigeons.
16+
</p>
17+
</header>
18+
19+
{sorted.length === 0 ? (
20+
<div className="rounded-xl border border-slate-700/80 bg-slate-900/50 p-10 text-center space-y-3">
21+
<p className="text-slate-400 text-lg">No articles yet, check back soon.</p>
22+
<p className="text-slate-600 text-sm">We're writing. Pigeons don't type fast.</p>
23+
</div>
24+
) : (
25+
<ul className="space-y-6">
26+
{sorted.map((post) => (
27+
<li key={post.slug}>
28+
<Link
29+
to={`/blog/${post.slug}`}
30+
className="group block rounded-xl border border-slate-700/80 bg-slate-900/50 p-6 hover:border-amber-500/30 hover:bg-slate-900/80 transition-all duration-200"
31+
>
32+
<div className="flex flex-wrap items-center gap-2 mb-2">
33+
<time className="text-xs text-slate-500 tabular-nums">{post.date}</time>
34+
{post.tags.map((tag) => (
35+
<span
36+
key={tag}
37+
className="text-xs px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-400 border border-amber-500/20"
38+
>
39+
{tag}
40+
</span>
41+
))}
42+
</div>
43+
<h2 className="text-lg font-semibold text-white group-hover:text-amber-400 transition-colors mb-1">
44+
{post.title}
45+
</h2>
46+
<p className="text-slate-400 text-sm leading-relaxed">{post.excerpt}</p>
47+
</Link>
48+
</li>
49+
))}
50+
</ul>
51+
)}
52+
</div>
53+
);
54+
};

src/pages/BlogPostPage.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import { Link, useParams, Navigate } from 'react-router-dom';
3+
import { blogPosts } from '../config/blog';
4+
5+
export const BlogPostPage: React.FC = () => {
6+
const { slug } = useParams<{ slug: string }>();
7+
const post = blogPosts.find((p) => p.slug === slug);
8+
9+
if (!post) return <Navigate to="/blog" replace />;
10+
11+
return (
12+
<article className="max-w-3xl mx-auto space-y-8">
13+
{/* Back */}
14+
<Link
15+
to="/blog"
16+
className="inline-flex items-center gap-1.5 text-sm text-slate-400 hover:text-amber-400 transition-colors"
17+
>
18+
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
19+
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
20+
</svg>
21+
Back to blog
22+
</Link>
23+
24+
{/* Header */}
25+
<header className="space-y-3 border-b border-slate-800 pb-8">
26+
<div className="flex flex-wrap items-center gap-2">
27+
<time className="text-xs text-slate-500 tabular-nums">{post.date}</time>
28+
{post.tags.map((tag) => (
29+
<span
30+
key={tag}
31+
className="text-xs px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-400 border border-amber-500/20"
32+
>
33+
{tag}
34+
</span>
35+
))}
36+
</div>
37+
<h1 className="font-display text-3xl md:text-4xl font-bold tracking-tight text-white">
38+
{post.title}
39+
</h1>
40+
<p className="text-slate-400 text-lg leading-relaxed">{post.excerpt}</p>
41+
</header>
42+
43+
{/* Content */}
44+
<div
45+
className="prose prose-invert prose-slate max-w-none
46+
prose-headings:font-display prose-headings:tracking-tight
47+
prose-a:text-amber-400 prose-a:no-underline hover:prose-a:underline
48+
prose-code:text-amber-300 prose-code:bg-slate-800/80 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded
49+
prose-pre:bg-slate-900 prose-pre:border prose-pre:border-slate-700/80"
50+
dangerouslySetInnerHTML={{ __html: post.content }}
51+
/>
52+
</article>
53+
);
54+
};

src/pages/CommunityPage.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const CommunityPage: React.FC = () => {
8484
<span className="text-amber-400">Community</span>
8585
</h1>
8686
<p className="text-slate-400 max-w-2xl mx-auto text-base md:text-lg">
87-
A small team of practitioners. No SaaS, no paywall just open code and feedback from the field.
87+
A small team of practitioners. No SaaS, no paywall, just open code and feedback from the field.
8888
</p>
8989
</section>
9090

@@ -93,26 +93,26 @@ export const CommunityPage: React.FC = () => {
9393
<div className="text-center max-w-2xl mx-auto">
9494
<h2 className="text-2xl md:text-3xl font-semibold text-white">Who is Nihil for?</h2>
9595
<p className="text-slate-400 text-sm mt-2">
96-
Built for people who work in offensive security at any level.
96+
Built for people who work in offensive security, at any level.
9797
</p>
9898
</div>
9999
<div className="grid gap-4 sm:grid-cols-2 max-w-4xl mx-auto">
100100
{[
101101
{
102102
title: 'Pentesters & Red Teamers',
103-
desc: 'Use Nihil as a ready-to-deploy offensive environment for real engagements AD attacks, network recon, web testing. Reproducible, auditable, and yours to customize.',
103+
desc: 'Use Nihil as a ready-to-deploy offensive environment for real engagements: AD attacks, network recon, web testing. Reproducible, auditable, and yours to customize.',
104104
},
105105
{
106106
title: 'CTF Players',
107-
desc: "Spin up a fully loaded container in seconds and get straight to the challenge. No setup overhead, no missing tools just nihil start ctf and you're in.",
107+
desc: "Spin up a fully loaded container in seconds and get straight to the challenge. No setup overhead, no missing tools, just nihil start ctf and you're in.",
108108
},
109109
{
110110
title: 'Security Students',
111-
desc: 'Learn offensive techniques in a clean, transparent environment. Every tool, every script is visible on GitHub — no magic, no black box to get lost in.',
111+
desc: 'Learn offensive techniques in a clean, transparent environment. Every tool, every script is visible on GitHub. No magic, no black box to get lost in.',
112112
},
113113
{
114114
title: 'Security Researchers',
115-
desc: 'Isolate your work in dedicated containers per project. Arch-based images with full control over what goes in — fork, tweak, and rebuild as you need.',
115+
desc: 'Isolate your work in dedicated containers per project. Arch-based images with full control over what goes in. Fork, tweak, and rebuild as you need.',
116116
},
117117
].map(({ title, desc }) => (
118118
<div
@@ -204,7 +204,7 @@ export const CommunityPage: React.FC = () => {
204204
{[
205205
{
206206
title: 'Transparency first',
207-
desc: 'Everything lives on GitHub Dockerfiles, scripts, CLI. No hidden layers, no mystery install. You can read, audit, and fork every line.',
207+
desc: 'Everything lives on GitHub: Dockerfiles, scripts, CLI. No hidden layers, no mystery install. You can read, audit, and fork every line.',
208208
},
209209
{
210210
title: 'No bloat',
@@ -253,7 +253,7 @@ export const CommunityPage: React.FC = () => {
253253
<section className="rounded-2xl border border-dashed border-slate-700 bg-slate-950/60 p-6 text-sm text-slate-400 space-y-2 max-w-3xl mx-auto">
254254
<p>
255255
Found a bug or have an idea? Open an issue or a PR. If TheNullPigeons helped on an engagement,
256-
we&apos;d love to hear it especially if something broke in a fun way.
256+
we&apos;d love to hear it, especially if something broke in a fun way.
257257
</p>
258258
</section>
259259
</div>

src/pages/HomePage.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const HomePage: React.FC = () => {
4545
</span>
4646
</h1>
4747
<p className="text-slate-400 mt-6 max-w-xl mx-auto leading-relaxed">
48-
Our tool <span className="text-amber-400/90 font-medium">Nihil</span>hand-crafted Docker images and scripts for real-world offensive workfrom Windows and
48+
Our tool <span className="text-amber-400/90 font-medium">Nihil</span>: hand-crafted Docker images and a CLI for real-world offensive work, from Windows and
4949
network infra to web. No magic, just good pigeons with better opsec.
5050
</p>
5151
<div className="flex flex-wrap gap-3 justify-center mt-8">
@@ -132,8 +132,7 @@ export const HomePage: React.FC = () => {
132132
<h3 className="font-semibold text-white">Transparent by design</h3>
133133
</div>
134134
<p className="text-sm text-slate-400 leading-relaxed">
135-
All images and scripts live on GitHub. No hidden install scripts, no mystery layers —
136-
just Bash, Python, and a lot of caffeine.
135+
All images and scripts live on GitHub. No hidden install scripts, no mystery layers. Just Bash, Python, and a lot of caffeine.
137136
</p>
138137
</div>
139138
<div className="rounded-2xl border border-slate-700/80 bg-slate-900/50 p-6 space-y-3 hover:border-amber-500/30 hover:bg-slate-900/70 transition-all duration-200">
@@ -142,7 +141,7 @@ export const HomePage: React.FC = () => {
142141
<h3 className="font-semibold text-white">Built for offensive work</h3>
143142
</div>
144143
<p className="text-sm text-slate-400 leading-relaxed">
145-
AD enumeration, network recon, web testing BloodHound, NetExec, Metasploit, and
144+
AD enumeration, network recon, web testing: BloodHound, NetExec, Metasploit, and
146145
more. Tools picked for real engagements, not to pad a readme.
147146
</p>
148147
</div>
@@ -195,7 +194,7 @@ export const HomePage: React.FC = () => {
195194
</span>
196195
</summary>
197196
<p className="text-sm text-slate-400 leading-relaxed px-5 pb-5 pt-0">
198-
Nihil is an offensive security environment built by TheNullPigeons. It ships as Arch-based Docker images paired with a Python CLI that handles everything pulling images, creating containers, managing workspaces and VPN sessions. No hidden config, no telemetry, no black box.
197+
Nihil is an offensive security environment built by TheNullPigeons. It ships as Arch-based Docker images paired with a Python CLI that handles everything: pulling images, creating containers, managing workspaces and VPN sessions. No hidden config, no telemetry, no black box.
199198
</p>
200199
</details>
201200
<details className="group rounded-xl border border-slate-700/80 bg-slate-900/50 overflow-hidden hover:border-amber-500/20 transition-colors">
@@ -217,7 +216,7 @@ export const HomePage: React.FC = () => {
217216
</span>
218217
</summary>
219218
<p className="text-sm text-slate-400 leading-relaxed px-5 pb-5 pt-0">
220-
Rolling updates, minimal footprint, and we like having full control over what goes in. We're both long-time Arch users, and it gives Nihil a slightly different flavor compared to Exegol&apos;s Debian base. Everything is in the Dockerfiles fork and tweak as you like.
219+
Rolling updates, minimal footprint, and we like having full control over what goes in. We're both long-time Arch users, and it keeps the base lean and up-to-date without waiting for distro packaging cycles. Everything is in the Dockerfiles, fork and tweak as you like.
221220
</p>
222221
</details>
223222
<details className="group rounded-xl border border-slate-700/80 bg-slate-900/50 overflow-hidden hover:border-amber-500/20 transition-colors">
@@ -228,7 +227,7 @@ export const HomePage: React.FC = () => {
228227
</span>
229228
</summary>
230229
<p className="text-sm text-slate-400 leading-relaxed px-5 pb-5 pt-0">
231-
On GitHub Container Registry (ghcr.io). You can also build them yourself from the repono account required to build, no pull limit for public images.
230+
On GitHub Container Registry (ghcr.io). You can also build them yourself from the repo, no account required, no pull limit for public images.
232231
</p>
233232
</details>
234233
<details className="group rounded-xl border border-slate-700/80 bg-slate-900/50 overflow-hidden hover:border-amber-500/20 transition-colors">
@@ -244,13 +243,13 @@ export const HomePage: React.FC = () => {
244243
</details>
245244
<details className="group rounded-xl border border-slate-700/80 bg-slate-900/50 overflow-hidden hover:border-amber-500/20 transition-colors">
246245
<summary className="faq-summary flex items-center justify-between gap-3 cursor-pointer p-5 font-medium text-white select-none">
247-
How does this compare to Exegol?
246+
How does it compare to other pentest environments?
248247
<span className="flex-shrink-0 w-5 h-5 text-amber-400 transition-transform duration-200 group-open:rotate-180" aria-hidden>
249248
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
250249
</span>
251250
</summary>
252251
<p className="text-sm text-slate-400 leading-relaxed px-5 pb-5 pt-0">
253-
We love Exegol. Nihil is our own take: same spirit of a ready-to-use offensive environment, but fully open and maintained by us. No SaaS, no paywall — just images and scripts you can audit and customize.
252+
Nihil is our own take on a ready-to-use offensive environment: Arch-based, fully open, no SaaS, no paywall. Just images and a CLI wrapper you control entirely.
254253
</p>
255254
</details>
256255
</div>

src/pages/InstallPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pipx install git+https://github.com/TheNullPigeons/nihil.git
2121
# Or from a clone
2222
git clone https://github.com/TheNullPigeons/nihil.git && cd nihil
2323
pip install -e .`,
24-
note: 'Requires Python 3.12+ and Docker. The CLI will pull images from GitHub Container Registryno manual docker build.',
24+
note: 'Requires Python 3.12+ and Docker. The CLI will pull images from GitHub Container Registry, no manual docker build needed.',
2525
},
2626
{
2727
title: 'Pull an image (optional)',
@@ -44,7 +44,7 @@ nihil start lab --image active-directory`,
4444
},
4545
{
4646
title: 'Useful commands',
47-
note: 'nihil infolist images and containers. nihil doctor check Docker and config. Inside the container, Ctrl+R in zsh to search tool names (bloodhound, netexec, etc.) in history.',
47+
note: 'nihil info: list images and containers. nihil doctor: check Docker and config. Inside the container, Ctrl+R in zsh to search tool names (bloodhound, netexec, etc.) in history.',
4848
},
4949
];
5050

@@ -80,7 +80,7 @@ export const InstallPage: React.FC = () => {
8080
<h1 className="text-4xl md:text-5xl font-bold tracking-tight">
8181
<span className="text-amber-400">Discover our tool</span>
8282
<span className="block text-white text-xl md:text-2xl mt-2 font-normal text-slate-200">
83-
Nihil our offensive environment, directly on your host.
83+
Nihil, our offensive environment, directly on your host.
8484
</span>
8585
</h1>
8686
<p className="text-slate-300 text-lg max-w-2xl mx-auto">

src/pages/PricingPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ const tiers = [
1919
{
2020
name: 'Enterprise Pigeon',
2121
subtitle: 'Contact sales*',
22-
price: '',
22+
price: 'N/A',
2323
period: '',
24-
description: '*We don’t have sales. This is a joke. We love Exegol, but we don’t sell subscriptions.',
24+
description: "*We don’t have sales. This is a joke. Everything is free, always.",
2525
features: [
2626
'Same as Free',
2727
'You maintain it yourself',

0 commit comments

Comments
 (0)