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
74 changes: 74 additions & 0 deletions data/brand-kit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[
{
"id": "brand_logo_primary_svg",
"name": "Primary Wordmark",
"type": "logo",
"description": "Main horizontal wordmark for headers, docs, and website hero sections.",
"url": "https://cdn.foragents.dev/brand/primary-wordmark.svg",
"format": "svg",
"updatedAt": "2026-02-10T09:10:00.000Z"
},
{
"id": "brand_logo_mark_png",
"name": "Icon Mark",
"type": "logo",
"description": "Square icon for avatars, social profiles, and app launchers.",
"url": "https://cdn.foragents.dev/brand/icon-mark.png",
"format": "png",
"updatedAt": "2026-02-10T09:11:00.000Z"
},
{
"id": "brand_color_primary",
"name": "Agent Green",
"type": "color",
"description": "Primary action color used for CTAs, highlights, and active states.",
"url": "https://cdn.foragents.dev/brand/colors/agent-green.ase",
"format": "ase",
"updatedAt": "2026-02-10T09:12:00.000Z"
},
{
"id": "brand_color_neutral",
"name": "Midnight Surface",
"type": "color",
"description": "Dark background token for panels and full-bleed surfaces.",
"url": "https://cdn.foragents.dev/brand/colors/midnight-surface.ase",
"format": "ase",
"updatedAt": "2026-02-10T09:13:00.000Z"
},
{
"id": "brand_font_heading",
"name": "Space Grotesk Heading Spec",
"type": "font",
"description": "Approved heading font stack with weight and spacing recommendations.",
"url": "https://cdn.foragents.dev/brand/fonts/space-grotesk-spec.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:14:00.000Z"
},
{
"id": "brand_font_body",
"name": "Inter Body Spec",
"type": "font",
"description": "Body typography usage guide for paragraphs, lists, and UI copy.",
"url": "https://cdn.foragents.dev/brand/fonts/inter-body-spec.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:15:00.000Z"
},
{
"id": "brand_guideline_logo_clearspace",
"name": "Logo Clearspace Rules",
"type": "guideline",
"description": "Defines safe spacing, minimum sizes, and prohibited logo manipulations.",
"url": "https://cdn.foragents.dev/brand/guidelines/logo-clearspace.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:16:00.000Z"
},
{
"id": "brand_guideline_press_usage",
"name": "Press Usage Checklist",
"type": "guideline",
"description": "Editorial and attribution requirements for media and partner coverage.",
"url": "https://cdn.foragents.dev/brand/guidelines/press-usage-checklist.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:17:00.000Z"
}
]
83 changes: 83 additions & 0 deletions src/app/api/brand-kit/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { NextRequest, NextResponse } from "next/server";
import {
brandKitTypes,
createBrandKitEntry,
isBrandKitType,
queryBrandKit,
type BrandKitType,
} from "@/lib/brandKit";

export const runtime = "nodejs";

export async function GET(request: NextRequest) {
try {
const typeParam = request.nextUrl.searchParams.get("type");
const search = request.nextUrl.searchParams.get("search")?.trim() ?? "";

if (typeParam && !isBrandKitType(typeParam)) {
return NextResponse.json(
{ error: `Invalid type. Use one of: ${brandKitTypes.join(", ")}` },
{ status: 400 }
);
}

const items = await queryBrandKit({
type: (typeParam as BrandKitType | null) ?? undefined,
search: search || undefined,
});

return NextResponse.json({
items,
filters: {
type: typeParam ?? null,
search,
availableTypes: brandKitTypes,
},
total: items.length,
});
} catch (error) {
console.error("Failed to load brand kit", error);
return NextResponse.json({ error: "Failed to load brand kit" }, { status: 500 });
}
}

export async function POST(request: NextRequest) {
try {
const body = (await request.json()) as Partial<{
id: string;
name: string;
type: BrandKitType;
description: string;
url: string;
format: string;
}>;

if (!body.name || !body.type || !body.description || !body.url || !body.format) {
return NextResponse.json(
{ error: "name, type, description, url, and format are required" },
{ status: 400 }
);
}

if (!isBrandKitType(body.type)) {
return NextResponse.json(
{ error: `Invalid type. Use one of: ${brandKitTypes.join(", ")}` },
{ status: 400 }
);
}

const created = await createBrandKitEntry({
id: body.id,
name: body.name,
type: body.type,
description: body.description,
url: body.url,
format: body.format,
});

return NextResponse.json({ ok: true, item: created }, { status: 201 });
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to create brand kit item";
return NextResponse.json({ error: message }, { status: 400 });
}
}
125 changes: 125 additions & 0 deletions src/app/brand-kit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/* eslint-disable react/no-unescaped-entities */
import type { Metadata } from "next";
import Link from "next/link";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { brandKitTypes, queryBrandKit, type BrandKitType } from "@/lib/brandKit";

export const metadata: Metadata = {
title: "Brand Kit — forAgents.dev",
description: "Persistent brand assets and guidelines for logos, colors, fonts, and press usage.",
openGraph: {
title: "Brand Kit — forAgents.dev",
description: "Persistent brand assets and guidelines for logos, colors, fonts, and press usage.",
url: "https://foragents.dev/brand-kit",
siteName: "forAgents.dev",
type: "website",
},
};

export const dynamic = "force-dynamic";

type SearchParams = {
type?: string;
search?: string;
};

function titleForType(type: BrandKitType) {
if (type === "logo") return "Logos";
if (type === "color") return "Colors";
if (type === "font") return "Fonts";
return "Guidelines";
}

export default async function BrandKitPage({
searchParams,
}: {
searchParams: Promise<SearchParams>;
}) {
const params = await searchParams;
const selectedType = brandKitTypes.includes((params.type ?? "") as BrandKitType)
? ((params.type ?? "") as BrandKitType)
: undefined;
const search = params.search?.trim() ?? "";

const items = await queryBrandKit({
type: selectedType,
search: search || undefined,
});

return (
<div className="min-h-screen bg-[#0a0a0a] text-foreground">
<section className="max-w-6xl mx-auto px-4 py-16 text-center">
<Badge variant="outline" className="mb-4 border-[#06D6A0]/30 text-[#06D6A0] bg-[#06D6A0]/10">
Persistent JSON + API
</Badge>
<h1 className="text-4xl md:text-6xl font-bold tracking-tight mb-3">forAgents.dev Brand Kit</h1>
<p className="text-muted-foreground max-w-3xl mx-auto">
Official downloadable assets for logos, colors, fonts, and brand guidelines.
</p>
</section>

<Separator className="opacity-10" />

<section className="max-w-6xl mx-auto px-4 py-10 space-y-6">
<div className="flex flex-wrap gap-2 justify-center">
<Button asChild variant={!selectedType ? "default" : "outline"} className={!selectedType ? "bg-[#06D6A0] text-black" : ""}>
<Link href="/brand-kit">All</Link>
</Button>
{brandKitTypes.map((type) => (
<Button
key={type}
asChild
variant={selectedType === type ? "default" : "outline"}
className={selectedType === type ? "bg-[#06D6A0] text-black" : ""}
>
<Link href={`/brand-kit?type=${type}${search ? `&search=${encodeURIComponent(search)}` : ""}`}>
{titleForType(type)}
</Link>
</Button>
))}
</div>

<form action="/brand-kit" method="get" className="max-w-xl mx-auto flex gap-2">
{selectedType ? <input type="hidden" name="type" value={selectedType} /> : null}
<Input name="search" defaultValue={search} placeholder="Search assets..." />
<Button type="submit" variant="outline">Search</Button>
</form>

<p className="text-sm text-muted-foreground text-center">
Showing <span className="text-foreground font-medium">{items.length}</span> result{items.length === 1 ? "" : "s"}
{selectedType ? ` in ${titleForType(selectedType)}` : ""}
{search ? ` matching "${search}"` : ""}
</p>

<div className="grid md:grid-cols-2 gap-4">
{items.map((item) => (
<Card key={item.id} className="bg-card/50 border-white/10">
<CardHeader>
<CardTitle className="text-lg flex items-center justify-between gap-3">
<span>{item.name}</span>
<Badge variant="secondary" className="uppercase text-[10px] tracking-wide">
{item.type}
</Badge>
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm text-muted-foreground">{item.description}</p>
<p className="text-xs text-muted-foreground">Format: {item.format.toUpperCase()}</p>
<p className="text-xs text-muted-foreground">Updated: {new Date(item.updatedAt).toLocaleString()}</p>
<Button asChild className="bg-[#06D6A0] text-black hover:brightness-110">
<a href={item.url} target="_blank" rel="noreferrer">
Open asset
</a>
</Button>
</CardContent>
</Card>
))}
</div>
</section>
</div>
);
}
17 changes: 2 additions & 15 deletions src/app/brand/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
/* eslint-disable react/no-unescaped-entities */
import { Metadata } from "next";
import { BrandClientPage } from "./brand-client";

export const metadata: Metadata = {
title: "Brand Guidelines — forAgents.dev",
description: "Logo usage, color palette, typography, and press kit resources for forAgents.dev",
openGraph: {
title: "Brand Guidelines — forAgents.dev",
description: "Logo usage, color palette, typography, and press kit resources for forAgents.dev",
url: "https://foragents.dev/brand",
siteName: "forAgents.dev",
type: "website",
},
};
import { redirect } from "next/navigation";

export default function BrandPage() {
return <BrandClientPage />;
redirect("/brand-kit");
}
74 changes: 74 additions & 0 deletions src/data/brand-kit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[
{
"id": "brand_logo_primary_svg",
"name": "Primary Wordmark",
"type": "logo",
"description": "Main horizontal wordmark for headers, docs, and website hero sections.",
"url": "https://cdn.foragents.dev/brand/primary-wordmark.svg",
"format": "svg",
"updatedAt": "2026-02-10T09:10:00.000Z"
},
{
"id": "brand_logo_mark_png",
"name": "Icon Mark",
"type": "logo",
"description": "Square icon for avatars, social profiles, and app launchers.",
"url": "https://cdn.foragents.dev/brand/icon-mark.png",
"format": "png",
"updatedAt": "2026-02-10T09:11:00.000Z"
},
{
"id": "brand_color_primary",
"name": "Agent Green",
"type": "color",
"description": "Primary action color used for CTAs, highlights, and active states.",
"url": "https://cdn.foragents.dev/brand/colors/agent-green.ase",
"format": "ase",
"updatedAt": "2026-02-10T09:12:00.000Z"
},
{
"id": "brand_color_neutral",
"name": "Midnight Surface",
"type": "color",
"description": "Dark background token for panels and full-bleed surfaces.",
"url": "https://cdn.foragents.dev/brand/colors/midnight-surface.ase",
"format": "ase",
"updatedAt": "2026-02-10T09:13:00.000Z"
},
{
"id": "brand_font_heading",
"name": "Space Grotesk Heading Spec",
"type": "font",
"description": "Approved heading font stack with weight and spacing recommendations.",
"url": "https://cdn.foragents.dev/brand/fonts/space-grotesk-spec.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:14:00.000Z"
},
{
"id": "brand_font_body",
"name": "Inter Body Spec",
"type": "font",
"description": "Body typography usage guide for paragraphs, lists, and UI copy.",
"url": "https://cdn.foragents.dev/brand/fonts/inter-body-spec.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:15:00.000Z"
},
{
"id": "brand_guideline_logo_clearspace",
"name": "Logo Clearspace Rules",
"type": "guideline",
"description": "Defines safe spacing, minimum sizes, and prohibited logo manipulations.",
"url": "https://cdn.foragents.dev/brand/guidelines/logo-clearspace.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:16:00.000Z"
},
{
"id": "brand_guideline_press_usage",
"name": "Press Usage Checklist",
"type": "guideline",
"description": "Editorial and attribution requirements for media and partner coverage.",
"url": "https://cdn.foragents.dev/brand/guidelines/press-usage-checklist.pdf",
"format": "pdf",
"updatedAt": "2026-02-10T09:17:00.000Z"
}
]
Loading
Loading