Skip to content

Commit b1146b6

Browse files
committed
Rebuild website with component architecture
Split the monolithic 560-line index.astro into proper components: - Data layer: rules.ts, benchmarks.ts, features.ts with typed exports - UI components: TerminalDemo, BenchmarkChart, CopyButton, SeverityDot - Section components: Hero, Speed, Coverage, Rules, Install, Footer - Index page is now a clean 40-line composition Streamlined from 8 overlapping sections to 5 focused ones. Removed defensive Semgrep positioning, moved compat to a card within Coverage. Terminal demo is now the hero visual. Benchmark shows speed multiplier. none
1 parent e722326 commit b1146b6

14 files changed

Lines changed: 672 additions & 545 deletions
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
import { frameworkGroups, features, compatFeatures } from '../../data/features';
3+
---
4+
5+
<section class="py-20" id="coverage">
6+
<div class="section-divider mb-20"></div>
7+
<div class="max-w-5xl mx-auto px-6">
8+
9+
<!-- Framework cards -->
10+
<div class="text-center mb-12">
11+
<h2 class="font-heading text-2xl sm:text-3xl text-noir-50 mb-3">What it catches</h2>
12+
<p class="text-noir-500 max-w-xl mx-auto text-sm leading-relaxed">
13+
Framework-aware rules that understand Express sessions, Django CSRF, Gin proxies, JWT flows, and more.
14+
</p>
15+
</div>
16+
17+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-16">
18+
{frameworkGroups.map((group) => (
19+
<div class="bg-noir-900 border border-noir-800 rounded-xl p-5 transition-colors hover:border-noir-700">
20+
<div class="flex items-baseline justify-between mb-3">
21+
<h3 class="text-noir-100 font-semibold text-sm">{group.title}</h3>
22+
<span class="font-mono text-[10px] text-fox">{group.ruleCount} rules</span>
23+
</div>
24+
<p class="text-xs text-noir-500 leading-relaxed mb-4">{group.desc}</p>
25+
<div class="flex flex-wrap gap-1.5">
26+
{group.badges.map((badge) => (
27+
<span class="rounded-md bg-noir-950 px-2 py-0.5 font-mono text-[10px] text-noir-600">{badge}</span>
28+
))}
29+
</div>
30+
</div>
31+
))}
32+
</div>
33+
34+
<!-- Features grid -->
35+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-16">
36+
{features.map((f) => (
37+
<div class="bg-noir-900 border border-noir-800 rounded-xl p-5 transition-colors hover:border-noir-700">
38+
<div class="font-mono text-[10px] font-bold text-fox mb-2.5 tracking-wider uppercase">{f.icon}</div>
39+
<h3 class="text-noir-100 font-semibold text-sm mb-1.5">{f.title}</h3>
40+
<p class="text-xs text-noir-500 leading-relaxed">{f.desc}</p>
41+
</div>
42+
))}
43+
</div>
44+
45+
<!-- Semgrep compat (brief, not its own section) -->
46+
<div class="bg-noir-900 border border-noir-800 rounded-xl p-6">
47+
<div class="flex flex-col sm:flex-row sm:items-start gap-6">
48+
<div class="sm:flex-1">
49+
<div class="font-mono text-[10px] uppercase tracking-wider text-noir-600 mb-2">Semgrep compatibility</div>
50+
<h3 class="text-noir-100 font-semibold text-sm mb-2">Bring your existing rules</h3>
51+
<p class="text-xs text-noir-500 leading-relaxed">
52+
Load Semgrep-compatible YAML on top of built-ins with <code class="text-noir-400">--rules</code>. Supports the structural matching subset most teams actually use.
53+
</p>
54+
</div>
55+
<div class="flex flex-wrap gap-1.5 sm:max-w-[280px]">
56+
{compatFeatures.map((f) => (
57+
<span class:list={[
58+
'rounded-md px-2 py-0.5 font-mono text-[10px]',
59+
f.supported ? 'bg-fox/10 text-fox' : 'bg-noir-950 text-noir-600'
60+
]}>
61+
{f.label}
62+
</span>
63+
))}
64+
</div>
65+
</div>
66+
</div>
67+
</div>
68+
</section>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
---
3+
4+
<footer class="py-20 text-center">
5+
<div class="section-divider mb-16"></div>
6+
<div class="max-w-3xl mx-auto px-6">
7+
<h2 class="font-heading text-2xl text-noir-50 mb-3">Open source. MIT licensed.</h2>
8+
<p class="text-noir-500 text-sm mb-8">Star on GitHub and help make codebases safer.</p>
9+
<a
10+
href="https://github.com/peaktwilight/foxguard"
11+
target="_blank"
12+
rel="noopener"
13+
class="btn-primary no-underline hover:no-underline"
14+
>
15+
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
16+
Star on GitHub
17+
</a>
18+
</div>
19+
20+
<div class="section-divider mt-16 mb-8"></div>
21+
<div class="text-[11px] text-noir-600 font-mono">
22+
<span>MIT</span>
23+
<span class="mx-2 opacity-40">·</span>
24+
<a href="https://github.com/peaktwilight" class="text-noir-500 hover:text-fox transition-colors no-underline">Peak Twilight</a>
25+
<span class="mx-2 opacity-40">·</span>
26+
<span>foxguard.dev</span>
27+
</div>
28+
</footer>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
import TerminalDemo from '../ui/TerminalDemo.astro';
3+
import CopyButton from '../ui/CopyButton.astro';
4+
import { totalRules } from '../../data/rules';
5+
---
6+
7+
<section class="relative pt-10 pb-16 sm:pt-14 sm:pb-24">
8+
<div class="max-w-6xl mx-auto px-6">
9+
10+
<!-- Top bar -->
11+
<div class="fade-up flex items-center justify-between pb-10">
12+
<div class="editorial-kicker">local-first sast</div>
13+
<nav class="hidden md:flex items-center gap-6 font-mono text-[10px] uppercase tracking-[0.12em] text-noir-600">
14+
<a href="#speed" class="hover:text-noir-100 transition-colors no-underline">speed</a>
15+
<a href="#coverage" class="hover:text-noir-100 transition-colors no-underline">coverage</a>
16+
<a href="#rules" class="hover:text-noir-100 transition-colors no-underline">rules</a>
17+
<a href="#install" class="hover:text-noir-100 transition-colors no-underline">install</a>
18+
</nav>
19+
</div>
20+
21+
<!-- Main hero grid -->
22+
<div class="grid gap-10 lg:grid-cols-[1fr_1fr] lg:items-start">
23+
24+
<!-- Left: copy -->
25+
<div class="fade-up fade-up-delay-1">
26+
<h1 class="font-heading text-noir-50 leading-[0.92] tracking-[-0.03em] mb-6" style="font-size: clamp(3.2rem, 7.5vw, 6rem);">
27+
fox<span class="text-fox-light">guard</span>
28+
</h1>
29+
30+
<p class="max-w-lg text-lg text-noir-300 mb-3">
31+
Fast local security scanner written in Rust.
32+
</p>
33+
<p class="max-w-lg text-base text-noir-500 leading-relaxed mb-8">
34+
{totalRules} built-in rules for JS/TS, Python, and Go. Secrets scanning. Baselines. Semgrep-compatible YAML. Single binary. No network calls.
35+
</p>
36+
37+
<!-- CTA -->
38+
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-3 mb-10">
39+
<button
40+
id="npx-btn"
41+
class="group flex items-center gap-3 rounded-md border border-noir-800 bg-noir-950 px-5 py-3 font-mono text-sm text-fox-light transition-all duration-200 hover:border-noir-700 cursor-pointer"
42+
>
43+
<span class="text-noir-600">$</span>
44+
<span>npx foxguard .</span>
45+
<svg class="w-3.5 h-3.5 text-noir-600 group-hover:text-fox-light transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
46+
</button>
47+
48+
<a
49+
href="https://github.com/peaktwilight/foxguard"
50+
target="_blank"
51+
rel="noopener"
52+
class="btn-ghost no-underline hover:no-underline"
53+
>
54+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
55+
GitHub
56+
</a>
57+
</div>
58+
59+
<!-- Stats row -->
60+
<div class="flex flex-wrap items-center gap-6 font-mono text-xs">
61+
<div>
62+
<span class="text-noir-100 text-lg font-heading">{totalRules}</span>
63+
<span class="text-noir-600 ml-1.5">rules</span>
64+
</div>
65+
<div class="w-px h-4 bg-noir-800"></div>
66+
<div>
67+
<span class="text-noir-100 text-lg font-heading">3</span>
68+
<span class="text-noir-600 ml-1.5">languages</span>
69+
</div>
70+
<div class="w-px h-4 bg-noir-800"></div>
71+
<div>
72+
<span class="text-noir-100 text-lg font-heading">&lt;1s</span>
73+
<span class="text-noir-600 ml-1.5">typical scan</span>
74+
</div>
75+
</div>
76+
</div>
77+
78+
<!-- Right: terminal demo -->
79+
<div class="fade-up fade-up-delay-2">
80+
<TerminalDemo />
81+
</div>
82+
</div>
83+
</div>
84+
</section>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
import CopyButton from '../ui/CopyButton.astro';
3+
---
4+
5+
<section class="py-20" id="install">
6+
<div class="section-divider mb-20"></div>
7+
<div class="max-w-3xl mx-auto px-6">
8+
<div class="text-center mb-12">
9+
<h2 class="font-heading text-2xl sm:text-3xl text-noir-50 mb-3">Install</h2>
10+
</div>
11+
12+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-10">
13+
<div class="bg-noir-900 border border-noir-800 rounded-xl p-5">
14+
<div class="font-mono text-[10px] uppercase tracking-wider text-noir-600 mb-3">npm / npx</div>
15+
<div class="flex items-center justify-between bg-noir-950 border border-noir-800 rounded-md px-4 py-2.5 font-mono text-sm text-fox">
16+
<code>npx foxguard .</code>
17+
<CopyButton text="npx foxguard ." />
18+
</div>
19+
</div>
20+
<div class="bg-noir-900 border border-noir-800 rounded-xl p-5">
21+
<div class="font-mono text-[10px] uppercase tracking-wider text-noir-600 mb-3">Rust / Cargo</div>
22+
<div class="flex items-center justify-between bg-noir-950 border border-noir-800 rounded-md px-4 py-2.5 font-mono text-sm text-fox">
23+
<code>cargo install foxguard</code>
24+
<CopyButton text="cargo install foxguard" />
25+
</div>
26+
</div>
27+
</div>
28+
29+
<!-- Quick start -->
30+
<div class="bg-noir-900 border border-noir-800 rounded-xl p-5">
31+
<div class="font-mono text-[10px] uppercase tracking-wider text-noir-600 mb-4">Quick start</div>
32+
<div class="flex flex-col gap-2.5 font-mono text-[12px]">
33+
<div class="flex items-start gap-3">
34+
<span class="text-noir-600 select-none shrink-0">1.</span>
35+
<div>
36+
<code class="text-noir-300">foxguard init</code>
37+
<span class="text-noir-600 ml-2">pre-commit hook + config</span>
38+
</div>
39+
</div>
40+
<div class="flex items-start gap-3">
41+
<span class="text-noir-600 select-none shrink-0">2.</span>
42+
<div>
43+
<code class="text-noir-300">foxguard .</code>
44+
<span class="text-noir-600 ml-2">scan everything</span>
45+
</div>
46+
</div>
47+
<div class="flex items-start gap-3">
48+
<span class="text-noir-600 select-none shrink-0">3.</span>
49+
<div>
50+
<code class="text-noir-300">foxguard --changed .</code>
51+
<span class="text-noir-600 ml-2">scan only modified files</span>
52+
</div>
53+
</div>
54+
<div class="flex items-start gap-3">
55+
<span class="text-noir-600 select-none shrink-0">4.</span>
56+
<div>
57+
<code class="text-noir-300">foxguard secrets --changed .</code>
58+
<span class="text-noir-600 ml-2">check for leaked credentials</span>
59+
</div>
60+
</div>
61+
</div>
62+
</div>
63+
</div>
64+
</section>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
import SeverityDot from '../ui/SeverityDot.astro';
3+
import CopyButton from '../ui/CopyButton.astro';
4+
import { ruleGroups, totalRules } from '../../data/rules';
5+
---
6+
7+
<section class="py-20" id="rules">
8+
<div class="section-divider mb-20"></div>
9+
<div class="max-w-5xl mx-auto px-6">
10+
<div class="text-center mb-12">
11+
<h2 class="font-heading text-2xl sm:text-3xl text-noir-50 mb-3">Rules</h2>
12+
<p class="text-noir-500 text-sm">{totalRules} built-in rules, each mapped to a CWE identifier.</p>
13+
</div>
14+
15+
<div class="flex flex-col gap-3">
16+
{ruleGroups.map((group) => (
17+
<details class="group bg-noir-900 border border-noir-800 rounded-xl overflow-hidden">
18+
<summary class="flex items-center justify-between px-5 py-3.5 cursor-pointer select-none list-none hover:bg-noir-800/40 transition-colors">
19+
<span class="flex items-center gap-3">
20+
<svg class="w-2.5 h-2.5 text-noir-600 transition-transform duration-200 group-open:rotate-90" viewBox="0 0 12 12" fill="currentColor"><path d="M4 1l6 5-6 5V1z"/></svg>
21+
<span class="text-noir-100 text-sm font-medium">{group.name}</span>
22+
</span>
23+
<span class="font-mono text-[10px] text-noir-600">{group.rules.length}</span>
24+
</summary>
25+
<div class="border-t border-noir-800">
26+
{group.rules.map((rule) => (
27+
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto_auto_2fr] gap-x-3 gap-y-0.5 items-center px-5 py-2 text-[12px] border-b border-noir-800/40 last:border-b-0">
28+
<code class="font-mono text-fox text-[12px]">{rule.id}</code>
29+
<SeverityDot severity={rule.severity} />
30+
<span class="font-mono text-[10px] text-noir-600">{rule.cwe}</span>
31+
<span class="text-noir-500 text-[12px] hidden sm:block">{rule.desc}</span>
32+
</div>
33+
))}
34+
</div>
35+
</details>
36+
))}
37+
</div>
38+
</div>
39+
</section>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
import BenchmarkChart from '../ui/BenchmarkChart.astro';
3+
---
4+
5+
<section class="py-20" id="speed">
6+
<div class="section-divider mb-20"></div>
7+
<div class="max-w-4xl mx-auto px-6">
8+
<div class="text-center mb-12">
9+
<h2 class="font-heading text-2xl sm:text-3xl text-noir-50 mb-3">Fast by default</h2>
10+
<p class="text-noir-500 max-w-xl mx-auto text-sm leading-relaxed">
11+
Rust-native engine with tree-sitter parsing. No JVM startup, no Python interpreter, no network round-trips.
12+
</p>
13+
</div>
14+
15+
<BenchmarkChart />
16+
17+
<!-- Workflow -->
18+
<div class="mt-16 max-w-lg mx-auto">
19+
<div class="flex flex-col sm:flex-row items-center justify-center gap-2 sm:gap-0 font-mono text-xs">
20+
<div class="bg-noir-900 border border-noir-800 rounded-md px-3.5 py-2.5 text-noir-500">edit</div>
21+
<svg class="w-5 h-5 text-noir-700 shrink-0 rotate-90 sm:rotate-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14m-7-7 7 7-7 7"/></svg>
22+
<div class="bg-noir-900 border border-fox/20 rounded-md px-3.5 py-2.5 text-fox font-medium">foxguard</div>
23+
<svg class="w-5 h-5 text-noir-700 shrink-0 rotate-90 sm:rotate-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14m-7-7 7 7-7 7"/></svg>
24+
<div class="bg-noir-900 border border-noir-800 rounded-md px-3.5 py-2.5 text-noir-500">fix</div>
25+
<svg class="w-5 h-5 text-noir-700 shrink-0 rotate-90 sm:rotate-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14m-7-7 7 7-7 7"/></svg>
26+
<div class="bg-noir-900 border border-noir-800 rounded-md px-3.5 py-2.5 text-noir-500">commit</div>
27+
</div>
28+
</div>
29+
</div>
30+
</section>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
import { benchmarkRows, timeWidth, speedMultiplier } from '../../data/benchmarks';
3+
---
4+
5+
<div class="bg-noir-900 border border-noir-800 rounded-xl p-5 sm:p-6">
6+
<div class="hidden md:grid grid-cols-[100px_1fr_1fr] gap-3 text-[10px] font-mono uppercase tracking-widest text-noir-600 mb-5 px-1">
7+
<div>Repo</div>
8+
<div>foxguard</div>
9+
<div>Semgrep</div>
10+
</div>
11+
12+
<div class="flex flex-col gap-5">
13+
{benchmarkRows.map((row) => (
14+
<div class="grid grid-cols-1 md:grid-cols-[100px_1fr_1fr] gap-3 items-center">
15+
<div class="md:pr-3">
16+
<div class="font-mono text-noir-100 text-sm">{row.repo}</div>
17+
<div class="font-mono text-[10px] text-noir-600">{row.files} files</div>
18+
</div>
19+
20+
<div>
21+
<div class="md:hidden font-mono text-[10px] uppercase tracking-widest text-noir-600 mb-1.5">foxguard</div>
22+
<div class="bg-noir-950 rounded-md border border-noir-800 overflow-hidden h-9 flex items-center">
23+
<div
24+
class="h-full flex items-center justify-end px-3 text-[11px] font-mono font-semibold min-w-[72px] bg-fox text-noir-950"
25+
style={`width: ${timeWidth(row.foxguard)}%`}
26+
>
27+
{row.foxguard.toFixed(3)}s
28+
</div>
29+
</div>
30+
</div>
31+
32+
<div>
33+
<div class="md:hidden font-mono text-[10px] uppercase tracking-widest text-noir-600 mb-1.5">Semgrep</div>
34+
<div class="bg-noir-950 rounded-md border border-noir-800 overflow-hidden h-9 flex items-center">
35+
<div
36+
class="h-full flex items-center justify-end px-3 text-[11px] font-mono font-semibold min-w-[72px] bg-noir-600 text-noir-100"
37+
style={`width: ${timeWidth(row.semgrep)}%`}
38+
>
39+
{row.semgrep.toFixed(3)}s
40+
</div>
41+
</div>
42+
<div class="text-right mt-1">
43+
<span class="font-mono text-[10px] text-fox">{speedMultiplier(row)} faster</span>
44+
</div>
45+
</div>
46+
</div>
47+
))}
48+
</div>
49+
</div>
50+
51+
<p class="text-[11px] text-noir-600 mt-4">
52+
foxguard built-in rules vs Semgrep <code class="text-noir-500">auto</code>. Run <code class="text-noir-500">./benchmarks/run.sh</code> locally to reproduce.
53+
</p>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
interface Props {
3+
text: string;
4+
class?: string;
5+
}
6+
7+
const { text, class: className = '' } = Astro.props;
8+
---
9+
10+
<button
11+
class:list={['copy-btn text-noir-600 hover:text-noir-100 transition-colors cursor-pointer', className]}
12+
data-copy={text}
13+
aria-label="Copy to clipboard"
14+
>
15+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
16+
<rect x="9" y="9" width="13" height="13" rx="2" />
17+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
18+
</svg>
19+
</button>

0 commit comments

Comments
 (0)