Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 129 additions & 74 deletions app/globals.css

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ export const metadata: Metadata = {
template: "%s | buffer.lol"
},
description:
"Free browser-based tools for network diagnostics, web checks, IP addresses, and developer utilities.",
"Free tools for network diagnostics, web checks, IP addresses, and private browser-side developer utilities.",
openGraph: {
title: "Free Network & Developer Tools | buffer.lol",
description:
"Fast browser-based networking, IP, web, and developer utilities with no sign-up.",
"Free networking, IP, web, and developer utilities with clear local and server data paths.",
type: "website",
url: "https://buffer.lol/",
siteName: "buffer.lol",
images: [{ url: "/opengraph-image", width: 1200, height: 630, alt: "buffer.lol network and developer tools" }]
images: [{ url: "/og.png", width: 1200, height: 630, alt: "buffer.lol network diagnostics and developer tools" }]
},
twitter: {
card: "summary_large_image",
title: "Free Network & Developer Tools | buffer.lol",
description:
"Fast browser-based networking, IP, web, and developer utilities with no sign-up.",
images: ["/opengraph-image"]
"Free networking, IP, web, and developer utilities with clear local and server data paths.",
images: ["/og.png"]
},
icons: {
icon: [
Expand Down
143 changes: 96 additions & 47 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,55 @@
import Link from "next/link";
import type { Metadata } from "next";
import { Fragment } from "react";
import { IPLensPromo } from "@/components/ip-lens/IPLensPromo";
import { HeroTerminal } from "@/components/landing/HeroTerminal";
import { SiteChrome } from "@/components/landing/SiteChrome";
import { ToolCard } from "@/components/tools/ToolCard";
import { QuickAccess } from "@/components/tools/ToolDiscovery";
import { HeroToolSearch, QuickAccess } from "@/components/tools/ToolDiscovery";
import { categoryMeta, getToolsByCategory, tools, type ToolCategory } from "@/data/tools";
import { StructuredData } from "@/components/StructuredData";
import { homeStructuredData } from "@/lib/seo";

const categories: ToolCategory[] = ["networking", "ip", "developer"];

const networkingGroups = [
{
id: "connectivity",
title: "Connectivity",
description: "Test reachability, stability, routes, uptime, and public ports.",
slugs: ["ping", "packet-loss", "traceroute", "uptime", "port-checker"]
},
{
id: "dns-and-email",
title: "DNS & email",
description: "Inspect records, compare resolvers, and validate mail configuration.",
slugs: ["dns-lookup", "dns-resolver-check", "email-dns-health", "whois-lookup"]
},
{
id: "web-and-security",
title: "Web & security",
description: "Review certificates, redirects, response metadata, and browser protections.",
slugs: ["http-headers", "ssl-checker", "redirect-checker", "robots-sitemap", "security-headers"]
},
{
id: "addressing",
title: "Addressing",
description: "Calculate IPv4 networks, ranges, masks, and host counts locally.",
slugs: ["cidr-calculator"]
}
] as const;

export const metadata: Metadata = {
title: { absolute: "Free Network & Developer Tools | buffer.lol" },
description: "Free browser-based tools for DNS, HTTP, SSL, IP addresses, network diagnostics, JSON, Base64, UUIDs, timestamps, and more.",
description: "Free tools for DNS, HTTP, SSL, IP addresses, network diagnostics, JSON, Base64, UUIDs, timestamps, and more.",
alternates: { canonical: "/" },
openGraph: {
title: "Free Network & Developer Tools | buffer.lol",
description: "Fast browser-based networking, IP, web, and developer utilities with no sign-up.",
description: "Free networking, IP, web, and developer utilities with clear local and server data paths.",
url: "/",
type: "website",
siteName: "buffer.lol"
siteName: "buffer.lol",
images: [{ url: "/og.png", width: 1200, height: 630, alt: "buffer.lol network diagnostics and developer tools" }]
}
};

Expand All @@ -31,62 +60,82 @@ export default function HomePage() {
<SiteChrome>
<main id="main-content">
<section className="home-hero" aria-labelledby="hero-title">
<div className="hero-copy">
<div className="eyebrow"><span className="live-dot" /> Browser-based network utilities</div>
<h1 id="hero-title">Network <span>&amp; developer</span> tools.</h1>
<p className="hero-subtitle">Fast, focused, and free to use.</p>
<p className="hero-description">A focused toolbox for network diagnostics, web checks, and developer utilities—built to be quick, clear, and useful without getting in your way.</p>
<div className="hero-actions">
<Link className="primary-button" href="#networking">Explore tools <span>↓</span></Link>
<Link className="secondary-button" href="/tools/json-formatter">Try JSON formatter</Link>
<div className="hero-copy">
<div className="eyebrow"><span className="live-dot" /> Browser-based network utilities</div>
<h1 id="hero-title">Network <span>&amp; developer</span> tools.</h1>
<p className="hero-subtitle">Diagnose networks and transform data—without the clutter.</p>
<p className="hero-description">Find the right network check, web diagnostic, or developer utility and get a clear result in seconds.</p>
<HeroToolSearch />
<div className="hero-browse-actions">
<Link href="#networking">Browse every tool <span aria-hidden="true">↓</span></Link>
<Link href="/tools/json-formatter">Open JSON formatter <span aria-hidden="true">→</span></Link>
</div>
<div className="hero-trust"><span>◉ No sign-up</span><span>◉ Clear data paths</span><span>◉ Zero clutter</span></div>
</div>
<div className="hero-trust"><span>◉ No sign-up</span><span>◉ Browser-first</span><span>◉ Zero clutter</span></div>
</div>

<HeroTerminal />
<HeroTerminal />
</section>

<QuickAccess />

<IPLensPromo />
<section className="tool-intro" aria-labelledby="toolbox-heading">
<div><span className="section-kicker">The toolbox</span><h2 id="toolbox-heading">Everything you need.<br />Nothing you don&apos;t.</h2></div>
<p>From quick network checks to everyday data transforms. Browser-ready tools stay local; live diagnostics run through a restricted same-origin API.</p>
</section>

<section className="tool-intro" aria-labelledby="toolbox-heading">
<div><span className="section-kicker">The toolbox</span><h2 id="toolbox-heading">Everything you need.<br />Nothing you don&apos;t.</h2></div>
<p>From quick network checks to everyday data transforms. Browser-ready tools stay local; live diagnostics run through a restricted same-origin API.</p>
</section>
<nav className="mobile-tool-nav" aria-label="Tool categories">
{categories.map((category) => {
const meta = categoryMeta[category];
const tools = getToolsByCategory(category);

<nav className="mobile-tool-nav" aria-label="Tool categories">
{categories.map((category) => {
const meta = categoryMeta[category];
const tools = getToolsByCategory(category);
return (
<Link href={`#${category}`} key={category}>
<span>{meta.title.replace(" tools", "")}</span>
<strong>{tools.length}</strong>
</Link>
);
})}
</nav>

{categories.map((category, index) => {
const meta = categoryMeta[category];
const categoryTools = getToolsByCategory(category);
return (
<Link href={`#${category}`} key={category}>
<span>{meta.title.replace(" tools", "")}</span>
<strong>{tools.length}</strong>
</Link>
<Fragment key={category}>
<section className="category-section" id={category}>
<header className="category-header">
<div><span className="category-number">0{index + 1}</span><h2>{meta.title}</h2></div>
<p>{meta.description}</p>
</header>
{category === "networking" ? (
<div className="tool-groups">
{networkingGroups.map((group) => (
<section className="tool-group" key={group.id} aria-labelledby={`tool-group-${group.id}`}>
<header>
<div><h3 id={`tool-group-${group.id}`}>{group.title}</h3><span>{group.slugs.length}</span></div>
<p>{group.description}</p>
</header>
<div className="tools-grid">{group.slugs.map((slug) => {
const tool = categoryTools.find((item) => item.slug === slug);
return tool ? <ToolCard tool={tool} key={tool.slug} /> : null;
})}</div>
</section>
))}
</div>
) : (
<div className="tools-grid">{categoryTools.map((tool) => <ToolCard tool={tool} key={tool.slug} />)}</div>
)}
</section>
{category === "networking" && <IPLensPromo />}
</Fragment>
);
})}
</nav>

{categories.map((category, index) => {
const meta = categoryMeta[category];
return (
<section className="category-section" id={category} key={category}>
<header className="category-header">
<div><span className="category-number">0{index + 1}</span><h2>{meta.title}</h2></div>
<p>{meta.description}</p>
</header>
<div className="tools-grid">{getToolsByCategory(category).map((tool) => <ToolCard tool={tool} key={tool.slug} />)}</div>
</section>
);
})}

<section className="launch-strip">
<div><span className="live-dot" /><strong>Built for the open web</strong></div>
<p>Small tools. Clear results. No accounts, dashboards, or mystery.</p>
<Link href="/tools/uuid-generator">Generate a UUID <span>→</span></Link>
</section>
<section className="launch-strip">
<div><span className="live-dot" /><strong>Built for the open web</strong></div>
<p>Small tools. Clear results. No accounts, dashboards, or mystery.</p>
<Link href="/tools/uuid-generator">Generate a UUID <span>→</span></Link>
</section>
</main>
</SiteChrome>
</>
Expand Down
69 changes: 69 additions & 0 deletions components/landing/MobileNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"use client";

import Link from "next/link";
import { useEffect, useId, useRef, useState } from "react";
import { categoryMeta, getToolsByCategory, type ToolCategory } from "@/data/tools";

const categories: ToolCategory[] = ["networking", "ip", "developer"];

export function MobileNavigation({ homePrefix = "/" }: { homePrefix?: string }) {
const [open, setOpen] = useState(false);
const menuId = useId();
const rootRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);

function close(restoreFocus = false) {
setOpen(false);
if (restoreFocus) window.setTimeout(() => triggerRef.current?.focus(), 0);
}

useEffect(() => {
if (!open) return;

function onKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") {
event.preventDefault();
close(true);
}
}

function onPointerDown(event: PointerEvent) {
if (!rootRef.current?.contains(event.target as Node)) close();
}

window.addEventListener("keydown", onKeyDown);
window.addEventListener("pointerdown", onPointerDown);
return () => {
window.removeEventListener("keydown", onKeyDown);
window.removeEventListener("pointerdown", onPointerDown);
};
}, [open]);

return (
<div className="mobile-navigation" ref={rootRef}>
<button
ref={triggerRef}
className="mobile-nav-trigger"
type="button"
aria-expanded={open}
aria-controls={menuId}
onClick={() => setOpen((value) => !value)}
>
<span className="mobile-nav-icon" aria-hidden="true"><i /><i /><i /></span>
<span className="sr-only">{open ? "Close navigation" : "Open navigation"}</span>
</button>
{open && (
<nav className="mobile-nav-panel" id={menuId} aria-label="Mobile navigation">
<p>Browse buffer.lol</p>
{categories.map((category) => (
<Link href={`${homePrefix}#${category}`} onClick={() => close()} key={category}>
<span>{categoryMeta[category].title}</span>
<strong>{getToolsByCategory(category).length} tools</strong>
</Link>
))}
<Link href="/ip-lens" onClick={() => close()}><span>IP Lens</span><strong>iPhone app</strong></Link>
</nav>
)}
</div>
);
}
6 changes: 5 additions & 1 deletion components/landing/SiteChrome.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Link from "next/link";
import { MobileNavigation } from "@/components/landing/MobileNavigation";
import { ToolLauncher } from "@/components/tools/ToolDiscovery";
import { ipLensConfig } from "@/data/ip-lens";

Expand All @@ -24,7 +25,10 @@ export function SiteHeader({ navHomePrefix = "" }: Omit<SiteChromeProps, "childr
<Link href="/ip-lens">IP Lens</Link>
{docsUrl && <a href={docsUrl}>Docs</a>}
</nav>
<ToolLauncher />
<div className="header-actions">
<ToolLauncher />
<MobileNavigation homePrefix={prefix} />
</div>
</header>
);
}
Expand Down
2 changes: 1 addition & 1 deletion components/tools/DiagnosticTools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function StatusChip({ status, label }: { status: DiagnosticStatus | Resol

function WorkflowLinks({ target, links }: { target: string; links: string[] }) {
const names: Record<string, string> = { "dns-lookup": "DNS Lookup", "email-dns-health": "Email DNS Health", "dns-resolver-check": "DNS Resolver Comparison", "ssl-checker": "SSL Certificate Checker", "http-headers": "HTTP Header Inspector", "redirect-checker": "Redirect Checker" };
return <div className="result-workflows">{links.map((slug) => <Link key={slug} href={`/tools/${slug}?target=${encodeURIComponent(target)}`}>{names[slug]} <span aria-hidden="true"></span></Link>)}</div>;
return <div className="result-workflows">{links.map((slug) => <Link key={slug} href={`/tools/${slug}?target=${encodeURIComponent(target)}`}>{names[slug]} <span aria-hidden="true"></span></Link>)}</div>;
}

function CopyJsonButton({ value }: { value: unknown }) {
Expand Down
13 changes: 10 additions & 3 deletions components/tools/ToolCard.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import Link from "next/link";
import type { Tool } from "@/data/tools";

const runtimeLabels: Record<Tool["runtime"], string> = {
local: "Runs locally",
browser: "Browser check",
server: "Server diagnostic",
worker: "Worker diagnostic"
};

export function ToolCard({ tool }: { tool: Tool }) {
return (
<Link className="tool-card" href={`/tools/${tool.slug}`}>
<div className="tool-card-topline">
<span className="command-icon" aria-hidden="true">{tool.command}</span>
<span className={`availability ${tool.status === "available" ? "is-live" : ""}`}>
{tool.status === "available" ? "Ready" : "Worker required"}
<span className={`availability runtime-${tool.runtime}`}>
{runtimeLabels[tool.runtime]}
</span>
</div>
<div>
<h3>{tool.name}</h3>
<p>{tool.description}</p>
</div>
<span className="card-link">
Open tool <span aria-hidden="true"></span>
Open tool <span aria-hidden="true"></span>
</span>
</Link>
);
Expand Down
Loading
Loading