Rebuild hatch.surf with Next.js
- Recreated old website with Next.js 16 + Tailwind CSS - Hero section with terminal animation - Features section (Capture, Inspect, Mock) - Why section (Compliance, Cost, Speed) - CTA section - Header and Footer components - Dark theme with Inter + JetBrains Mono fonts - Static export for Docker deployment Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
94ea381348
commit
7bbc0ddda3
@ -1,26 +1,69 @@
|
|||||||
@import "tailwindcss";
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--surface-0: #09090b;
|
||||||
--foreground: #171717;
|
--surface-1: #111114;
|
||||||
|
--surface-2: #1a1a1f;
|
||||||
|
--surface-3: #232328;
|
||||||
|
--brand: #3b82f6;
|
||||||
|
--brand-dim: #2563eb;
|
||||||
|
--text-primary: #fafafa;
|
||||||
|
--text-secondary: #a1a1aa;
|
||||||
|
--text-tertiary: #71717a;
|
||||||
|
--text-code: #e879f9;
|
||||||
|
--code-bg: #18181b;
|
||||||
|
--code-border: #27272a;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
* {
|
||||||
--color-background: var(--background);
|
margin: 0;
|
||||||
--color-foreground: var(--foreground);
|
padding: 0;
|
||||||
--font-sans: var(--font-geist-sans);
|
box-sizing: border-box;
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
html {
|
||||||
:root {
|
font-size: 16px;
|
||||||
--background: #0a0a0a;
|
line-height: 1.6;
|
||||||
--foreground: #ededed;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
font-family: var(--font-inter), -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
color: var(--foreground);
|
color: var(--text-primary);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
background-color: var(--surface-0);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--brand);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: var(--font-mono), 'SFMono-Regular', Consolas, monospace;
|
||||||
|
font-size: 0.875em;
|
||||||
|
background-color: var(--code-bg);
|
||||||
|
border: 1px solid var(--code-border);
|
||||||
|
padding: 0.15em 0.4em;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Particle canvas */
|
||||||
|
#particle-canvas {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,45 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Inter, JetBrains_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const inter = Inter({
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
variable: "--font-inter",
|
||||||
});
|
});
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
const jetbrainsMono = JetBrains_Mono({
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mono",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Hatch — Self-hostable HTTP request inspector + mocker",
|
||||||
description: "Generated by create next app",
|
description: "Hatch is a self-hostable HTTP request inspector and mocker. One Go binary, SQLite, no SaaS, no per-request fee. Open source, MIT.",
|
||||||
|
openGraph: {
|
||||||
|
title: "Hatch — Self-hostable HTTP request inspector + mocker",
|
||||||
|
description: "Hatch is a self-hostable HTTP request inspector and mocker. One Go binary, SQLite, no SaaS, no per-request fee. Open source, MIT.",
|
||||||
|
url: "https://hatch.surf",
|
||||||
|
siteName: "Hatch",
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: "/brand/og/default.png",
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "Hatch — Self-hostable HTTP request inspector + mocker",
|
||||||
|
description: "Hatch is a self-hostable HTTP request inspector and mocker. One Go binary, SQLite, no SaaS, no per-request fee. Open source, MIT.",
|
||||||
|
images: ["/brand/og/default.png"],
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
icon: "/brand/favicon/favicon-48.png",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@ -23,11 +48,12 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html
|
<html lang="en" className={`${inter.variable} ${jetbrainsMono.variable}`}>
|
||||||
lang="en"
|
<body className="min-h-screen bg-zinc-950 text-white font-sans antialiased">
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
<Header />
|
||||||
>
|
{children}
|
||||||
<body className="min-h-full flex flex-col">{children}</body>
|
<Footer />
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
70
app/page.tsx
70
app/page.tsx
@ -1,65 +1,15 @@
|
|||||||
import Image from "next/image";
|
import Hero from '@/components/Hero'
|
||||||
|
import Features from '@/components/Features'
|
||||||
|
import Why from '@/components/Why'
|
||||||
|
import CTA from '@/components/CTA'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<main className="relative z-10">
|
||||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
<Hero />
|
||||||
<Image
|
<Features />
|
||||||
className="dark:invert"
|
<Why />
|
||||||
src="/next.svg"
|
<CTA />
|
||||||
alt="Next.js logo"
|
|
||||||
width={100}
|
|
||||||
height={20}
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
|
||||||
To get started, edit the page.tsx file.
|
|
||||||
</h1>
|
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Templates
|
|
||||||
</a>{" "}
|
|
||||||
or the{" "}
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Learning
|
|
||||||
</a>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Deploy Now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
)
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
components/CTA.tsx
Normal file
20
components/CTA.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export default function CTA() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-6">
|
||||||
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
||||||
|
Start inspecting in 30 seconds
|
||||||
|
</h2>
|
||||||
|
<p className="text-xl text-zinc-400 mb-8">
|
||||||
|
One binary. Your data stays yours.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://github.com/elfoundation/hatch"
|
||||||
|
className="inline-flex items-center justify-center gap-2 px-8 py-4 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors text-lg"
|
||||||
|
>
|
||||||
|
Get Hatch →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
61
components/Features.tsx
Normal file
61
components/Features.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const features = [
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10"></circle>
|
||||||
|
<polyline points="12 6 12 12 16 14"></polyline>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: 'Capture',
|
||||||
|
description: 'Method, path, headers, query, body. Persists across restarts because the storage is SQLite on disk, not a hosted queue.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: 'Inspect',
|
||||||
|
description: 'A live SSE feed of incoming requests. Click any captured request to see the headers, the body, the timing.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: (
|
||||||
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="16 18 22 12 16 6"></polyline>
|
||||||
|
<polyline points="8 6 2 12 8 18"></polyline>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
title: 'Mock',
|
||||||
|
description: 'Return a 200, a 500, or a custom JSON payload. For testing your own retry, backoff, and error-handling logic.',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function Features() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-6">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-center mb-16">
|
||||||
|
Three things, and nothing else
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{features.map((feature, index) => (
|
||||||
|
<div
|
||||||
|
key={feature.title}
|
||||||
|
className="p-6 rounded-xl bg-zinc-900/50 border border-zinc-800 hover:border-zinc-700 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="w-12 h-12 flex items-center justify-center rounded-lg bg-blue-500/10 text-blue-400 mb-4">
|
||||||
|
{feature.icon}
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2">{feature.title}</h3>
|
||||||
|
<p className="text-zinc-400 leading-relaxed">
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
components/Footer.tsx
Normal file
29
components/Footer.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="py-8 px-6 border-t border-zinc-800">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<span className="text-white font-bold flex items-center gap-2">
|
||||||
|
<span className="text-blue-400">⬡</span> Hatch
|
||||||
|
</span>
|
||||||
|
<p className="text-zinc-500 text-sm text-center md:text-right">
|
||||||
|
© 2026 El Foundation.{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/elfoundation/hatch"
|
||||||
|
className="text-zinc-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Hatch
|
||||||
|
</a>{' '}
|
||||||
|
is released under the{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/elfoundation/hatch/blob/main/LICENSE"
|
||||||
|
className="text-zinc-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
MIT License
|
||||||
|
</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
25
components/Header.tsx
Normal file
25
components/Header.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<header className="fixed top-0 left-0 right-0 z-50 py-4 bg-zinc-950/80 backdrop-blur-lg border-b border-zinc-800">
|
||||||
|
<div className="max-w-4xl mx-auto px-6 flex justify-between items-center">
|
||||||
|
<a href="/" className="text-xl font-bold text-white flex items-center gap-2 hover:text-white transition-colors">
|
||||||
|
<span className="text-blue-400">⬡</span> Hatch
|
||||||
|
</a>
|
||||||
|
<nav className="flex items-center gap-6">
|
||||||
|
<a
|
||||||
|
href="https://github.com/elfoundation/hatch"
|
||||||
|
className="text-zinc-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/blog/"
|
||||||
|
className="text-zinc-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
62
components/Hero.tsx
Normal file
62
components/Hero.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
export default function Hero() {
|
||||||
|
return (
|
||||||
|
<section className="pt-32 pb-20 px-6">
|
||||||
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
|
<div className="inline-block mb-6 px-4 py-2 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-400 text-sm font-medium">
|
||||||
|
Open source · MIT licensed
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6 leading-tight">
|
||||||
|
Self-hostable HTTP request<br />
|
||||||
|
inspector + mocker
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-xl text-zinc-400 mb-8 max-w-2xl mx-auto">
|
||||||
|
One Go binary. SQLite under the hood.
|
||||||
|
<code className="mx-1 px-2 py-0.5 bg-zinc-800 border border-zinc-700 rounded text-fuchsia-400 text-base">
|
||||||
|
docker compose up
|
||||||
|
</code>
|
||||||
|
, and you have an inspection endpoint and a live feed in under 30 seconds.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
||||||
|
<a
|
||||||
|
href="https://github.com/elfoundation/hatch"
|
||||||
|
className="inline-flex items-center justify-center gap-2 px-6 py-3 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
||||||
|
</svg>
|
||||||
|
View on GitHub
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/elfoundation/hatch#quick-start"
|
||||||
|
className="inline-flex items-center justify-center gap-2 px-6 py-3 bg-zinc-800 hover:bg-zinc-700 text-white font-medium rounded-lg border border-zinc-700 transition-colors"
|
||||||
|
>
|
||||||
|
Quick Start →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Terminal */}
|
||||||
|
<div className="max-w-2xl mx-auto rounded-xl overflow-hidden border border-zinc-800 bg-zinc-900/50 backdrop-blur-sm">
|
||||||
|
<div className="flex items-center gap-2 px-4 py-3 bg-zinc-800/50 border-b border-zinc-800">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-zinc-600"></div>
|
||||||
|
<div className="w-3 h-3 rounded-full bg-zinc-600"></div>
|
||||||
|
<div className="w-3 h-3 rounded-full bg-zinc-600"></div>
|
||||||
|
<span className="ml-2 text-sm text-zinc-500">terminal</span>
|
||||||
|
</div>
|
||||||
|
<pre className="p-4 text-left text-sm font-mono overflow-x-auto">
|
||||||
|
<code>
|
||||||
|
<span className="text-green-400">$</span>{' '}
|
||||||
|
<span className="text-zinc-300">docker compose up -d</span>{'\n'}
|
||||||
|
<span className="text-zinc-500">✓ hatch started on :8080</span>{'\n'}
|
||||||
|
<span className="text-green-400">$</span>{' '}
|
||||||
|
<span className="text-zinc-300">curl -X POST https://your-bin.hatch.surf/test -d {'{\'hello\':\'world\'}'}</span>{'\n'}
|
||||||
|
<span className="text-zinc-500">✓ captured — view at https://your-bin.hatch.surf/inspect</span>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
55
components/Why.tsx
Normal file
55
components/Why.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const reasons = [
|
||||||
|
{
|
||||||
|
number: '01',
|
||||||
|
title: 'Compliance and privacy',
|
||||||
|
description: 'Some teams cannot legally send webhook payloads to a hosted SaaS. Hatch keeps the data on your own network.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: '02',
|
||||||
|
title: 'Cost',
|
||||||
|
description: 'A hosted inspector charges per request, per seat, or per retention day. Hatch is one Go binary on a $5 VPS. There is no per-request fee because there is no one to charge it.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: '03',
|
||||||
|
title: 'Speed of setup',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
<code className="px-2 py-0.5 bg-zinc-800 border border-zinc-700 rounded text-fuchsia-400 text-sm">
|
||||||
|
docker compose up
|
||||||
|
</code>{' '}
|
||||||
|
is faster than signing up for a SaaS, verifying your email, configuring your first bin, and pasting the URL into your webhook config.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function Why() {
|
||||||
|
return (
|
||||||
|
<section className="py-20 px-6 bg-zinc-900/30">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-center mb-16">
|
||||||
|
Why a single binary, not a SaaS
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-8">
|
||||||
|
{reasons.map((reason) => (
|
||||||
|
<div
|
||||||
|
key={reason.number}
|
||||||
|
className="flex gap-6 items-start"
|
||||||
|
>
|
||||||
|
<div className="text-4xl font-bold text-zinc-700 font-mono shrink-0">
|
||||||
|
{reason.number}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2">{reason.title}</h3>
|
||||||
|
<p className="text-zinc-400 leading-relaxed">
|
||||||
|
{reason.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -2,6 +2,9 @@ import type { NextConfig } from "next";
|
|||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
output: "export",
|
output: "export",
|
||||||
|
images: {
|
||||||
|
unoptimized: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user