format(www)

This commit is contained in:
Musa 2025-12-12 11:36:54 -08:00
parent a825ce9add
commit 64d45a7a00
27 changed files with 257 additions and 314 deletions

View file

@ -1,101 +0,0 @@
# ArchGW Monorepo
This is a Turborepo monorepo containing the Next.js applications and shared packages.
## Structure
```
.
├── apps/
│ ├── www/ # Marketing website
│ └── docs/ # Documentation site
├── packages/
│ ├── ui/ # Shared UI components (Navbar, Footer, Logo, etc.)
│ ├── shared-styles/ # Shared CSS and Tailwind configuration
│ ├── tailwind-config/ # Tailwind configuration
│ └── tsconfig/ # Shared TypeScript configurations
└── turbo.json # Turborepo configuration
```
## Getting Started
### Install Dependencies
```bash
npm install
```
### Development
Run all apps in development mode:
```bash
npm run dev
```
Or run specific apps:
```bash
# Marketing website (port 3000)
cd apps/www && npm run dev
# Documentation (port 3001)
cd apps/docs && npm run dev
```
### Build
Build all apps:
```bash
npm run build
```
### Lint & Type Check
```bash
npm run lint
npm run typecheck
```
## Shared Packages
### @archgw/ui
Shared React components including:
- `Navbar` - Navigation bar component
- `Footer` - Footer component
- `Logo` - Logo component
- UI components (Button, Dialog, etc.)
### @archgw/shared-styles
Shared CSS styles including:
- Tailwind CSS configuration
- Font definitions (IBM Plex Sans, JetBrains Mono)
- CSS variables for theming
### @archgw/tailwind-config
Shared Tailwind CSS configuration.
### @archgw/tsconfig
Shared TypeScript configurations:
- `base.json` - Base TypeScript config
- `nextjs.json` - Next.js specific config
## Design System
Both apps share the same design system:
- Same fonts (IBM Plex Sans, JetBrains Mono)
- Same color palette
- Same components (Navbar, Footer)
- Same Tailwind configuration
## Notes
- Fonts are stored in each app's `public/fonts/` directory
- Both apps use the same shared components and styles
- The monorepo uses npm workspaces and Turborepo for build orchestration

View file

@ -19,7 +19,7 @@ const nextConfig: NextConfig = {
"node_modules", "node_modules",
"../../node_modules", "../../node_modules",
]; ];
if (!isServer) { if (!isServer) {
config.resolve.fallback = { config.resolve.fallback = {
...config.resolve.fallback, ...config.resolve.fallback,

View file

@ -16,4 +16,4 @@ export default defineConfig({
schema: { schema: {
types: schemaTypes, types: schemaTypes,
}, },
}); });

View file

@ -8,7 +8,9 @@ export const runtime = "edge";
function loadFont(fileName: string, baseUrl: string) { function loadFont(fileName: string, baseUrl: string) {
return fetch(new URL(`/fonts/${fileName}`, baseUrl)).then((res) => { return fetch(new URL(`/fonts/${fileName}`, baseUrl)).then((res) => {
if (!res.ok) { if (!res.ok) {
throw new Error(`Failed to fetch font ${fileName}: ${res.status} ${res.statusText}`); throw new Error(
`Failed to fetch font ${fileName}: ${res.status} ${res.statusText}`,
);
} }
return res.arrayBuffer(); return res.arrayBuffer();
}); });
@ -63,16 +65,20 @@ export async function GET(
// Load fonts with error handling // Load fonts with error handling
let fontData; let fontData;
try { try {
const [ibmPlexSans, jetbrainsMonoRegular, jetbrainsMonoMedium, jetbrainsMonoBold] = const [
await Promise.all([ ibmPlexSans,
loadFont("IBMPlexSans-VariableFont_wdth,wght.otf", fontBaseUrl), jetbrainsMonoRegular,
loadFont("JetBrainsMono-Regular.otf", fontBaseUrl), jetbrainsMonoMedium,
loadFont("JetBrainsMono-Medium.otf", fontBaseUrl), jetbrainsMonoBold,
loadFont("jetbrains-mono-bold.otf", fontBaseUrl), ] = await Promise.all([
]).catch((error: Error) => { loadFont("IBMPlexSans-VariableFont_wdth,wght.otf", fontBaseUrl),
console.error("Error loading fonts:", error); loadFont("JetBrainsMono-Regular.otf", fontBaseUrl),
throw new Error(`Failed to load fonts: ${error.message}`); loadFont("JetBrainsMono-Medium.otf", fontBaseUrl),
}); loadFont("jetbrains-mono-bold.otf", fontBaseUrl),
]).catch((error: Error) => {
console.error("Error loading fonts:", error);
throw new Error(`Failed to load fonts: ${error.message}`);
});
fontData = { fontData = {
ibmPlexSans, ibmPlexSans,
@ -81,7 +87,8 @@ export async function GET(
jetbrainsMonoBold, jetbrainsMonoBold,
}; };
} catch (error: unknown) { } catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : "Unknown error"; const errorMessage =
error instanceof Error ? error.message : "Unknown error";
console.error("Font loading error:", errorMessage); console.error("Font loading error:", errorMessage);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@ -97,7 +104,9 @@ export async function GET(
const post = await getBlogPost(slug); const post = await getBlogPost(slug);
if (!post) { if (!post) {
return new Response(JSON.stringify({ error: "Post not found" }), { status: 404 }); return new Response(JSON.stringify({ error: "Post not found" }), {
status: 404,
});
} }
// Get author image URL if available // Get author image URL if available
@ -114,8 +123,7 @@ export async function GET(
: request.nextUrl.origin); : request.nextUrl.origin);
const logoUrl = `${baseUrl}/Logomark.png`; const logoUrl = `${baseUrl}/Logomark.png`;
return new ImageResponse( return new ImageResponse(
(
<div <div
style={{ style={{
background: "linear-gradient(to top right, #ffffff, #dcdfff)", background: "linear-gradient(to top right, #ffffff, #dcdfff)",
@ -259,53 +267,53 @@ export async function GET(
)} )}
</div> </div>
</div> </div>
</div> </div>,
), {
{ width: 1200,
width: 1200, height: 630,
height: 630, fonts: [
fonts: [ {
{ name: "IBM Plex Sans Regular",
name: "IBM Plex Sans Regular", data: fontData.ibmPlexSans,
data: fontData.ibmPlexSans, style: "normal",
style: "normal", weight: 400,
weight: 400, },
}, {
{ name: "IBM Plex Sans Medium",
name: "IBM Plex Sans Medium", data: fontData.ibmPlexSans,
data: fontData.ibmPlexSans, style: "normal",
style: "normal", weight: 500,
weight: 500, },
}, {
{ name: "IBM Plex Sans Bold",
name: "IBM Plex Sans Bold", data: fontData.ibmPlexSans,
data: fontData.ibmPlexSans, style: "normal",
style: "normal", weight: 700,
weight: 700, },
}, {
{ name: "JetBrains Mono Regular",
name: "JetBrains Mono Regular", data: fontData.jetbrainsMonoRegular,
data: fontData.jetbrainsMonoRegular, style: "normal",
style: "normal", weight: 400,
weight: 400, },
}, {
{ name: "JetBrains Mono Medium",
name: "JetBrains Mono Medium", data: fontData.jetbrainsMonoMedium,
data: fontData.jetbrainsMonoMedium, style: "normal",
style: "normal", weight: 500,
weight: 500, },
}, {
{ name: "JetBrains Mono Bold",
name: "JetBrains Mono Bold", data: fontData.jetbrainsMonoBold,
data: fontData.jetbrainsMonoBold, style: "normal",
style: "normal", weight: 600,
weight: 600, },
}, ],
], },
}, );
);
} catch (error: unknown) { } catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : "Unknown error"; const errorMessage =
error instanceof Error ? error.message : "Unknown error";
console.error("Error generating image response:", error); console.error("Error generating image response:", error);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@ -316,4 +324,3 @@ export async function GET(
); );
} }
} }

View file

@ -49,7 +49,7 @@ export async function generateMetadata({
// Get baseUrl - use NEXT_PUBLIC_APP_URL if set, otherwise construct from VERCEL_URL // Get baseUrl - use NEXT_PUBLIC_APP_URL if set, otherwise construct from VERCEL_URL
// Restrict to allowed hosts: localhost:3000, archgw-tau.vercel.app, or plano.katanemo.com // Restrict to allowed hosts: localhost:3000, archgw-tau.vercel.app, or plano.katanemo.com
let baseUrl = "http://localhost:3000"; let baseUrl = "http://localhost:3000";
if (process.env.NEXT_PUBLIC_APP_URL) { if (process.env.NEXT_PUBLIC_APP_URL) {
const url = process.env.NEXT_PUBLIC_APP_URL; const url = process.env.NEXT_PUBLIC_APP_URL;
if ( if (
@ -118,4 +118,3 @@ interface LayoutProps {
export default async function Layout({ children, params }: LayoutProps) { export default async function Layout({ children, params }: LayoutProps) {
return <>{children}</>; return <>{children}</>;
} }

View file

@ -33,4 +33,3 @@ export default function NotFound() {
</div> </div>
); );
} }

View file

@ -204,4 +204,3 @@ export default async function BlogPostPage({
</article> </article>
); );
} }

View file

@ -117,4 +117,3 @@ export default async function BlogPage() {
</div> </div>
); );
} }

View file

@ -1,7 +1,15 @@
"use client"; "use client";
import React from "react"; import React from "react";
import { ResearchHero, ResearchGrid, ResearchTimeline, ResearchCTA, ResearchCapabilities, ResearchBenchmarks, ResearchFamily } from "@/components/research"; import {
ResearchHero,
ResearchGrid,
ResearchTimeline,
ResearchCTA,
ResearchCapabilities,
ResearchBenchmarks,
ResearchFamily,
} from "@/components/research";
import { UnlockPotentialSection } from "@/components/UnlockPotentialSection"; import { UnlockPotentialSection } from "@/components/UnlockPotentialSection";
export default function ResearchPage() { export default function ResearchPage() {

View file

@ -9,4 +9,3 @@ export default function StudioLayout({
</div> </div>
); );
} }

View file

@ -85,4 +85,3 @@ export function BlogCard({ post, index = 0 }: BlogCardProps) {
</motion.div> </motion.div>
); );
} }

View file

@ -23,4 +23,3 @@ export function BlogHeader() {
</motion.section> </motion.section>
); );
} }

View file

@ -3,10 +3,7 @@
export function BlogSectionHeader() { export function BlogSectionHeader() {
return ( return (
<h2 className="text-2xl sm:text-3xl lg:text-4xl font-normal leading-tight tracking-tighter text-black mb-12"> <h2 className="text-2xl sm:text-3xl lg:text-4xl font-normal leading-tight tracking-tighter text-black mb-12">
<span className="font-sans"> <span className="font-sans">The latest and greatest from our blog.</span>
The latest and greatest from our blog.
</span>
</h2> </h2>
); );
} }

View file

@ -3,11 +3,7 @@
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { Navbar, Footer } from "@katanemo/ui"; import { Navbar, Footer } from "@katanemo/ui";
export function ConditionalLayout({ export function ConditionalLayout({ children }: { children: React.ReactNode }) {
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname(); const pathname = usePathname();
const isStudio = pathname?.startsWith("/studio"); const isStudio = pathname?.startsWith("/studio");
@ -23,4 +19,3 @@ export function ConditionalLayout({
</div> </div>
); );
} }

View file

@ -115,4 +115,3 @@ export function FeaturedBlogCard({ post }: FeaturedBlogCardProps) {
</motion.div> </motion.div>
); );
} }

View file

@ -44,9 +44,8 @@ export function Hero() {
{/* Subheading with CTA Buttons */} {/* Subheading with CTA Buttons */}
<div className="max-w-7xl relative z-10"> <div className="max-w-7xl relative z-10">
<p className="text-base sm:text-lg md:text-xl lg:text-[22px] font-sans font-[400] tracking-[-1.0px] sm:tracking-[-1.22px]! text-black max-w-76 sm:max-w-2xl mb-6"> <p className="text-base sm:text-lg md:text-xl lg:text-[22px] font-sans font-[400] tracking-[-1.0px] sm:tracking-[-1.22px]! text-black max-w-76 sm:max-w-2xl mb-6">
Build agents faster, and deliver them reliably to production - Build agents faster, and deliver them reliably to production - by
by offloading the critical plumbing work to Plano. offloading the critical plumbing work to Plano.
</p> </p>
{/* CTA Buttons */} {/* CTA Buttons */}

View file

@ -150,7 +150,9 @@ export function IdeaToAgentSection() {
</div> </div>
<Button asChild className="mt-6 sm:mt-8 w-full sm:w-auto"> <Button asChild className="mt-6 sm:mt-8 w-full sm:w-auto">
<Link href={carouselData[currentSlide].link}>Learn more</Link> <Link href={carouselData[currentSlide].link}>
Learn more
</Link>
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -20,15 +20,18 @@ export function IntroSection() {
{/* Body Text */} {/* Body Text */}
<div className="text-white text-sm sm:text-base lg:text-lg max-w-[713px]"> <div className="text-white text-sm sm:text-base lg:text-lg max-w-[713px]">
<p className="mb-0"> <p className="mb-0">
Plano is a models-native proxy and dataplane for agents that handles Plano is a models-native proxy and dataplane for agents that
critical plumbing work in AI - agent routing and orchestration, rich agentic handles critical plumbing work in AI - agent routing and
traces, guardrail hooks, and smart model routing APIs for LLMs. Use any orchestration, rich agentic traces, guardrail hooks, and smart
language, AI framework, and deliver agents to productions quickly with Plano. model routing APIs for LLMs. Use any language, AI framework, and
deliver agents to productions quickly with Plano.
</p> </p>
<p className="mb-0 mt-4"> <p className="mb-0 mt-4">
Developers can focus more on core product logic of agents. Developers can focus more on core product logic of agents.
Product teams can accelerate feedback loops for reinforcement learning. Product teams can accelerate feedback loops for reinforcement
Engineering teams can standardize policies and access controls across every agent and LLM for safer, more reliable scaling. learning. Engineering teams can standardize policies and access
controls across every agent and LLM for safer, more reliable
scaling.
</p> </p>
</div> </div>
</div> </div>

View file

@ -34,17 +34,17 @@ export function LogoCloud() {
const isTMobile = index === 1; // T-Mobile is before HP const isTMobile = index === 1; // T-Mobile is before HP
const isHP = index === 2; // HP is in center const isHP = index === 2; // HP is in center
const isSanDisk = index === 3; // SanDisk is after HP const isSanDisk = index === 3; // SanDisk is after HP
// Custom spacing for logos around HP on large screens // Custom spacing for logos around HP on large screens
let spacingClass = 'lg:mx-6 xl:mx-8'; // Default spacing let spacingClass = "lg:mx-6 xl:mx-8"; // Default spacing
if (isTMobile) { if (isTMobile) {
spacingClass = 'lg:mr-3 xl:mr-4 lg:ml-6 xl:ml-8'; // Smaller gap to HP spacingClass = "lg:mr-3 xl:mr-4 lg:ml-6 xl:ml-8"; // Smaller gap to HP
} else if (isHP) { } else if (isHP) {
spacingClass = 'lg:mx-3 xl:mx-4'; // Smaller gaps on both sides spacingClass = "lg:mx-3 xl:mx-4"; // Smaller gaps on both sides
} else if (isSanDisk) { } else if (isSanDisk) {
spacingClass = 'lg:ml-3 xl:ml-4 lg:mr-6 xl:mr-8'; // Smaller gap from HP spacingClass = "lg:ml-3 xl:ml-4 lg:mr-6 xl:mr-8"; // Smaller gap from HP
} }
return ( return (
<div <div
key={logo.name} key={logo.name}

View file

@ -13,7 +13,7 @@ const squares = [
{ x: 3, y: 15, color: "#ABB2FA", col: 0, row: 4 }, { x: 3, y: 15, color: "#ABB2FA", col: 0, row: 4 },
{ x: 3, y: 18, color: "#ABB2FA", col: 0, row: 5 }, { x: 3, y: 18, color: "#ABB2FA", col: 0, row: 5 },
{ x: 3, y: 21, color: "#969FF4", col: 0, row: 6 }, { x: 3, y: 21, color: "#969FF4", col: 0, row: 6 },
// Column 2 (x=6) // Column 2 (x=6)
{ x: 6, y: 3, color: "#B0B7FF", col: 1, row: 0 }, { x: 6, y: 3, color: "#B0B7FF", col: 1, row: 0 },
{ x: 6, y: 6, color: "#B0B7FF", col: 1, row: 1 }, { x: 6, y: 6, color: "#B0B7FF", col: 1, row: 1 },
@ -22,7 +22,7 @@ const squares = [
{ x: 6, y: 15, color: "#ABB2FA", col: 1, row: 4 }, { x: 6, y: 15, color: "#ABB2FA", col: 1, row: 4 },
{ x: 6, y: 18, color: "#969FF4", col: 1, row: 5 }, { x: 6, y: 18, color: "#969FF4", col: 1, row: 5 },
{ x: 6, y: 21, color: "#969FF4", col: 1, row: 6 }, { x: 6, y: 21, color: "#969FF4", col: 1, row: 6 },
// Column 3 (x=9) // Column 3 (x=9)
{ x: 9, y: 3, color: "#B0B7FF", col: 2, row: 0 }, { x: 9, y: 3, color: "#B0B7FF", col: 2, row: 0 },
{ x: 9, y: 6, color: "#ABB2FA", col: 2, row: 1 }, { x: 9, y: 6, color: "#ABB2FA", col: 2, row: 1 },
@ -31,7 +31,7 @@ const squares = [
{ x: 9, y: 15, color: "#969FF4", col: 2, row: 4 }, { x: 9, y: 15, color: "#969FF4", col: 2, row: 4 },
{ x: 9, y: 18, color: "#969FF4", col: 2, row: 5 }, { x: 9, y: 18, color: "#969FF4", col: 2, row: 5 },
{ x: 9, y: 21, color: "#969FF4", col: 2, row: 6 }, { x: 9, y: 21, color: "#969FF4", col: 2, row: 6 },
// Column 4 (x=12) // Column 4 (x=12)
{ x: 12, y: 3, color: "#ABB2FA", col: 3, row: 0 }, { x: 12, y: 3, color: "#ABB2FA", col: 3, row: 0 },
{ x: 12, y: 6, color: "#ABB2FA", col: 3, row: 1 }, { x: 12, y: 6, color: "#ABB2FA", col: 3, row: 1 },
@ -40,7 +40,7 @@ const squares = [
{ x: 12, y: 15, color: "#969FF4", col: 3, row: 4 }, { x: 12, y: 15, color: "#969FF4", col: 3, row: 4 },
{ x: 12, y: 18, color: "#969FF4", col: 3, row: 5 }, { x: 12, y: 18, color: "#969FF4", col: 3, row: 5 },
{ x: 12, y: 21, color: "#969FF4", col: 3, row: 6 }, { x: 12, y: 21, color: "#969FF4", col: 3, row: 6 },
// Column 5 (x=15) // Column 5 (x=15)
{ x: 15, y: 3, color: "#ABB2FA", col: 4, row: 0 }, { x: 15, y: 3, color: "#ABB2FA", col: 4, row: 0 },
{ x: 15, y: 6, color: "#ABB2FA", col: 4, row: 1 }, { x: 15, y: 6, color: "#ABB2FA", col: 4, row: 1 },
@ -49,7 +49,7 @@ const squares = [
{ x: 15, y: 15, color: "#969FF4", col: 4, row: 4 }, { x: 15, y: 15, color: "#969FF4", col: 4, row: 4 },
{ x: 15, y: 18, color: "#969FF4", col: 4, row: 5 }, { x: 15, y: 18, color: "#969FF4", col: 4, row: 5 },
{ x: 15, y: 21, color: "#969FF4", col: 4, row: 6 }, { x: 15, y: 21, color: "#969FF4", col: 4, row: 6 },
// Column 6 (x=18) // Column 6 (x=18)
{ x: 18, y: 3, color: "#ABB2FA", col: 5, row: 0 }, { x: 18, y: 3, color: "#ABB2FA", col: 5, row: 0 },
{ x: 18, y: 6, color: "#969FF4", col: 5, row: 1 }, { x: 18, y: 6, color: "#969FF4", col: 5, row: 1 },
@ -58,7 +58,7 @@ const squares = [
{ x: 18, y: 15, color: "#969FF4", col: 5, row: 4 }, { x: 18, y: 15, color: "#969FF4", col: 5, row: 4 },
{ x: 18, y: 18, color: "#969FF4", col: 5, row: 5 }, { x: 18, y: 18, color: "#969FF4", col: 5, row: 5 },
{ x: 18, y: 21, color: "#969FF4", col: 5, row: 6 }, { x: 18, y: 21, color: "#969FF4", col: 5, row: 6 },
// Column 7 (x=21) // Column 7 (x=21)
{ x: 21, y: 3, color: "#969FF4", col: 6, row: 0 }, { x: 21, y: 3, color: "#969FF4", col: 6, row: 0 },
{ x: 21, y: 6, color: "#969FF4", col: 6, row: 1 }, { x: 21, y: 6, color: "#969FF4", col: 6, row: 1 },
@ -92,7 +92,7 @@ function getDeterministicValues(index: number) {
const seed4 = index * 0.4; const seed4 = index * 0.4;
const seed5 = index * 0.5; const seed5 = index * 0.5;
const seed6 = index * 0.6; const seed6 = index * 0.6;
return { return {
duration: roundToPrecision(3 + seededRandom(seed1) * 3, 10), // 3-6 seconds duration: roundToPrecision(3 + seededRandom(seed1) * 3, 10), // 3-6 seconds
peakOpacity: roundToPrecision(0.7 + seededRandom(seed2) * 0.3, 10), peakOpacity: roundToPrecision(0.7 + seededRandom(seed2) * 0.3, 10),
@ -105,9 +105,9 @@ function getDeterministicValues(index: number) {
export function NetworkAnimation({ className }: NetworkAnimationProps) { export function NetworkAnimation({ className }: NetworkAnimationProps) {
// Generate unique IDs for gradient and mask to avoid conflicts when multiple instances exist // Generate unique IDs for gradient and mask to avoid conflicts when multiple instances exist
const gradientId = useId().replace(/:/g, '-'); const gradientId = useId().replace(/:/g, "-");
const maskId = useId().replace(/:/g, '-'); const maskId = useId().replace(/:/g, "-");
return ( return (
<div className="absolute inset-0 pointer-events-none opacity-100"> <div className="absolute inset-0 pointer-events-none opacity-100">
<motion.div <motion.div
@ -143,55 +143,61 @@ export function NetworkAnimation({ className }: NetworkAnimationProps) {
<rect width="26" height="26" fill={`url(#${gradientId})`} /> <rect width="26" height="26" fill={`url(#${gradientId})`} />
</mask> </mask>
</defs> </defs>
<g mask={`url(#${maskId})`}> <g mask={`url(#${maskId})`}>
{/* Outer border */} {/* Outer border */}
<rect width="26" height="26" fill="#7780D9" /> <rect width="26" height="26" fill="#7780D9" />
{/* Inner background */} {/* Inner background */}
<rect x="2" y="2" width="22" height="22" fill="#B9BFFF" /> <rect x="2" y="2" width="22" height="22" fill="#B9BFFF" />
{/* Animated squares with wave effect */} {/* Animated squares with wave effect */}
{squares.map((square, index) => { {squares.map((square, index) => {
// Use deterministic values based on index for SSR/client consistency // Use deterministic values based on index for SSR/client consistency
const { duration, peakOpacity, baseOpacity, midOpacity, baseBrightness, peakBrightness } = const {
getDeterministicValues(index); duration,
peakOpacity,
return ( baseOpacity,
<motion.path midOpacity,
key={`square-${index}`} baseBrightness,
d={`M${square.x} ${square.y}H${square.x + 2}V${square.y + 2}H${square.x}V${square.y}Z`} peakBrightness,
fill={square.color} } = getDeterministicValues(index);
initial={{
opacity: roundToPrecision(baseOpacity, 10), return (
filter: `brightness(${roundToPrecision(baseBrightness, 10)})`, <motion.path
}} key={`square-${index}`}
animate={{ d={`M${square.x} ${square.y}H${square.x + 2}V${square.y + 2}H${square.x}V${square.y}Z`}
opacity: [ fill={square.color}
roundToPrecision(baseOpacity, 10), initial={{
roundToPrecision(midOpacity, 10), opacity: roundToPrecision(baseOpacity, 10),
roundToPrecision(peakOpacity, 10), filter: `brightness(${roundToPrecision(baseBrightness, 10)})`,
roundToPrecision(midOpacity, 10), }}
roundToPrecision(baseOpacity, 10), animate={{
], opacity: [
filter: [ roundToPrecision(baseOpacity, 10),
`brightness(${roundToPrecision(baseBrightness, 10)})`, roundToPrecision(midOpacity, 10),
`brightness(${roundToPrecision((baseBrightness + peakBrightness) / 2, 10)})`, roundToPrecision(peakOpacity, 10),
`brightness(${roundToPrecision(peakBrightness, 10)})`, roundToPrecision(midOpacity, 10),
`brightness(${roundToPrecision((baseBrightness + peakBrightness) / 2, 10)})`, roundToPrecision(baseOpacity, 10),
`brightness(${roundToPrecision(baseBrightness, 10)})`, ],
], filter: [
}} `brightness(${roundToPrecision(baseBrightness, 10)})`,
transition={{ `brightness(${roundToPrecision((baseBrightness + peakBrightness) / 2, 10)})`,
duration: roundToPrecision(duration, 10), `brightness(${roundToPrecision(peakBrightness, 10)})`,
delay: 0, // No delay - instant start `brightness(${roundToPrecision((baseBrightness + peakBrightness) / 2, 10)})`,
repeat: Infinity, `brightness(${roundToPrecision(baseBrightness, 10)})`,
ease: "easeInOut", ],
repeatDelay: 0, // No pause between cycles }}
}} transition={{
/> duration: roundToPrecision(duration, 10),
); delay: 0, // No delay - instant start
})} repeat: Infinity,
ease: "easeInOut",
repeatDelay: 0, // No pause between cycles
}}
/>
);
})}
</g> </g>
</svg> </svg>
</motion.div> </motion.div>

View file

@ -11,22 +11,22 @@ const components = {
types: { types: {
image: ({ value }: any) => { image: ({ value }: any) => {
if (!value?.asset) return null; if (!value?.asset) return null;
const imageUrl = urlFor(value); const imageUrl = urlFor(value);
const asset = value.asset; const asset = value.asset;
// Get natural dimensions if available from metadata // Get natural dimensions if available from metadata
const dimensions = asset.metadata?.dimensions; const dimensions = asset.metadata?.dimensions;
const width = dimensions?.width || 1000; const width = dimensions?.width || 1000;
const height = dimensions?.height || 562; const height = dimensions?.height || 562;
const aspectRatio = dimensions ? height / width : 0.5625; // Default to 16:9 if no dimensions const aspectRatio = dimensions ? height / width : 0.5625; // Default to 16:9 if no dimensions
return ( return (
<div className="my-6 lg:my-8"> <div className="my-6 lg:my-8">
<div className="max-w-3xl mx-auto"> <div className="max-w-3xl mx-auto">
<div className="relative w-full overflow-hidden rounded-lg bg-black/5"> <div className="relative w-full overflow-hidden rounded-lg bg-black/5">
<div <div
className="relative w-full" className="relative w-full"
style={{ paddingBottom: `${aspectRatio * 100}%` }} style={{ paddingBottom: `${aspectRatio * 100}%` }}
> >
<Image <Image
@ -39,7 +39,9 @@ const components = {
</div> </div>
</div> </div>
{value.alt && ( {value.alt && (
<p className="mt-2 text-sm text-black/60 text-center">{value.alt}</p> <p className="mt-2 text-sm text-black/60 text-center">
{value.alt}
</p>
)} )}
</div> </div>
</div> </div>
@ -91,12 +93,8 @@ const components = {
), ),
}, },
listItem: { listItem: {
bullet: (props: any) => ( bullet: (props: any) => <li className="ml-4">{props.children}</li>,
<li className="ml-4">{props.children}</li> number: (props: any) => <li className="ml-4">{props.children}</li>,
),
number: (props: any) => (
<li className="ml-4">{props.children}</li>
),
}, },
marks: { marks: {
strong: ({ children }: { children: React.ReactNode }) => ( strong: ({ children }: { children: React.ReactNode }) => (
@ -109,7 +107,11 @@ const components = {
<a <a
href={props.value?.href || "#"} href={props.value?.href || "#"}
target={props.value?.href?.startsWith("http") ? "_blank" : undefined} target={props.value?.href?.startsWith("http") ? "_blank" : undefined}
rel={props.value?.href?.startsWith("http") ? "noopener noreferrer" : undefined} rel={
props.value?.href?.startsWith("http")
? "noopener noreferrer"
: undefined
}
className="text-[var(--secondary)] hover:underline font-medium" className="text-[var(--secondary)] hover:underline font-medium"
> >
{props.children} {props.children}
@ -121,4 +123,3 @@ const components = {
export function PortableText({ content }: PortableTextProps) { export function PortableText({ content }: PortableTextProps) {
return <SanityPortableText value={content} components={components} />; return <SanityPortableText value={content} components={components} />;
} }

View file

@ -115,7 +115,9 @@ export function UseCasesSection() {
What's possible with Plano What's possible with Plano
</h2> </h2>
<Button asChild className="hidden lg:block"> <Button asChild className="hidden lg:block">
<Link href="https://docs.plano.katanemo.com/getting-started/installation">Start building</Link> <Link href="https://docs.plano.katanemo.com/getting-started/installation">
Start building
</Link>
</Button> </Button>
</div> </div>
</div> </div>
@ -156,7 +158,9 @@ export function UseCasesSection() {
{/* Start building button - Mobile only, appears last */} {/* Start building button - Mobile only, appears last */}
<div className="lg:hidden mt-8"> <div className="lg:hidden mt-8">
<Button asChild className="w-full"> <Button asChild className="w-full">
<Link href="https://docs.plano.katanemo.com/getting-started/installation">Start building</Link> <Link href="https://docs.plano.katanemo.com/getting-started/installation">
Start building
</Link>
</Button> </Button>
</div> </div>
</div> </div>
@ -284,7 +288,10 @@ export function UseCasesSection() {
</div> </div>
<div className="flex flex-col sm:flex-row gap-3 w-full sm:w-auto order-1"> <div className="flex flex-col sm:flex-row gap-3 w-full sm:w-auto order-1">
<Button asChild className="w-full sm:w-auto"> <Button asChild className="w-full sm:w-auto">
<Link href="https://docs.plano.katanemo.com/getting-started/installation" className="flex items-center gap-2"> <Link
href="https://docs.plano.katanemo.com/getting-started/installation"
className="flex items-center gap-2"
>
Start building Start building
<ArrowRightIcon className="w-4 h-4" /> <ArrowRightIcon className="w-4 h-4" />
</Link> </Link>

View file

@ -51,7 +51,7 @@ const verticalCarouselData = [
category: "PROGRAMMABLE ARCHITECTURE", category: "PROGRAMMABLE ARCHITECTURE",
title: "", title: "",
description: [ description: [
"As agent workloads move beyond prototypes, teams end up scattering critical logic across apps: compliance checks, context \"patches,\" provider-specific quirks, etc. That glue code gets duplicated across agents, is hard to audit, and slows iteration because every policy or workflow change requires touching application code and redeploying.", 'As agent workloads move beyond prototypes, teams end up scattering critical logic across apps: compliance checks, context "patches," provider-specific quirks, etc. That glue code gets duplicated across agents, is hard to audit, and slows iteration because every policy or workflow change requires touching application code and redeploying.',
"Plano keeps that logic in one place with a programmable Agent Filter Chain—hooks that can inspect, mutate, or terminate prompt traffic early, turning common steps (policy enforcement, jailbreak checks, context engineering, tool gating, routing hints) into reusable building blocks.", "Plano keeps that logic in one place with a programmable Agent Filter Chain—hooks that can inspect, mutate, or terminate prompt traffic early, turning common steps (policy enforcement, jailbreak checks, context engineering, tool gating, routing hints) into reusable building blocks.",
], ],
diagram: "/PromptRouting.svg", diagram: "/PromptRouting.svg",
@ -180,11 +180,23 @@ export function VerticalCarouselSection() {
{/* Description */} {/* Description */}
<div className="text-white text-sm sm:text-base lg:text-lg max-w-full lg:max-w-md -mt-0.5"> <div className="text-white text-sm sm:text-base lg:text-lg max-w-full lg:max-w-md -mt-0.5">
{verticalCarouselData[activeSlide].description.map((paragraph, index) => ( {verticalCarouselData[activeSlide].description.map(
<p key={index} className={index < verticalCarouselData[activeSlide].description.length - 1 ? "mb-4" : "mb-0"}> (paragraph, index) => (
{paragraph} <p
</p> key={index}
))} className={
index <
verticalCarouselData[activeSlide].description
.length -
1
? "mb-4"
: "mb-0"
}
>
{paragraph}
</p>
),
)}
</div> </div>
</div> </div>
</div> </div>

View file

@ -16,7 +16,9 @@ export function ResearchBenchmarks() {
{/* Title */} {/* Title */}
<h2 className="text-4xl sm:text-4xl md:text-5xl lg:text-4xl font-medium leading-tight tracking-[-0.06em]! text-white"> <h2 className="text-4xl sm:text-4xl md:text-5xl lg:text-4xl font-medium leading-tight tracking-[-0.06em]! text-white">
<span className="font-sans">Production excellence, outperforming frontier LLMs</span> <span className="font-sans">
Production excellence, outperforming frontier LLMs
</span>
</h2> </h2>
</div> </div>

View file

@ -9,26 +9,34 @@ export function ResearchCTA() {
<div className="max-w-4xl"> <div className="max-w-4xl">
{/* Main Heading */} {/* Main Heading */}
<h1 className="text-4xl sm:text-4xl md:text-5xl lg:text-5xl font-medium leading-tight tracking-[-0.06em]! text-white -ml-1 mb-3 mt-4"> <h1 className="text-4xl sm:text-4xl md:text-5xl lg:text-5xl font-medium leading-tight tracking-[-0.06em]! text-white -ml-1 mb-3 mt-4">
<span className="font-sans">Meet Plano-Orchestrator. Our latest models.</span> <span className="font-sans">
Meet Plano-Orchestrator. Our latest models.
</span>
</h1> </h1>
</div> </div>
{/* Description with CTA Buttons */} {/* Description with CTA Buttons */}
<div className="max-w-5xl"> <div className="max-w-5xl">
<p className="leading-relaxed sm:text-lg md:text-lg lg:text-[18px] font-sans font-normal text-white/90 mb-6"> <p className="leading-relaxed sm:text-lg md:text-lg lg:text-[18px] font-sans font-normal text-white/90 mb-6">
Plano-Orchestrator is a family of state-of-the-art routing and orchestration models Plano-Orchestrator is a family of state-of-the-art routing and
that decides which agent(s) or LLM(s) should handle each request, and in what sequence. orchestration models that decides which agent(s) or LLM(s) should
Built for multi-agent orchestration systems, Plano-Orchestrator excels at analyzing handle each request, and in what sequence. Built for multi-agent
user intent and conversation context to make precise routing and orchestration decisions. orchestration systems, Plano-Orchestrator excels at analyzing user
intent and conversation context to make precise routing and
orchestration decisions.
</p> </p>
{/* CTA Buttons */} {/* CTA Buttons */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-start gap-3 sm:gap-4"> <div className="flex flex-col sm:flex-row items-stretch sm:items-start gap-3 sm:gap-4">
<Button asChild className="w-full sm:w-auto"> <Button asChild className="w-full sm:w-auto">
<Link href="https://huggingface.co/katanemo">Download Plano models</Link> <Link href="https://huggingface.co/katanemo">
Download Plano models
</Link>
</Button> </Button>
<Button variant="secondary" asChild className="w-full sm:w-auto"> <Button variant="secondary" asChild className="w-full sm:w-auto">
<Link href="https://docs.plano.katanemo.com">Get Started with Plano</Link> <Link href="https://docs.plano.katanemo.com">
Get Started with Plano
</Link>
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -52,14 +52,16 @@ export function ResearchCapabilities() {
{/* Title */} {/* Title */}
<h2 className="text-4xl sm:text-4xl md:text-5xl lg:text-4xl font-medium leading-tight tracking-[-0.06em]! text-white mb-4"> <h2 className="text-4xl sm:text-4xl md:text-5xl lg:text-4xl font-medium leading-tight tracking-[-0.06em]! text-white mb-4">
<span className="font-sans">Accurately route with confidence with no compromise</span> <span className="font-sans">
Accurately route with confidence with no compromise
</span>
</h2> </h2>
<p className="text-white/90 w-full sm:w-[75%] text-sm sm:text-base leading-relaxed"> <p className="text-white/90 w-full sm:w-[75%] text-sm sm:text-base leading-relaxed">
Designed for real-world deployments, it delivers strong performance across general Designed for real-world deployments, it delivers strong performance
conversations, coding tasks, and long-context multi-turn conversations, while remaining across general conversations, coding tasks, and long-context
efficient enough for low-latency production environments. multi-turn conversations, while remaining efficient enough for
low-latency production environments.
</p> </p>
</div> </div>
@ -67,15 +69,18 @@ export function ResearchCapabilities() {
<div className="lg:hidden grid grid-cols-1 gap-8"> <div className="lg:hidden grid grid-cols-1 gap-8">
{capabilitiesData.map((capability) => { {capabilitiesData.map((capability) => {
// Map each capability to its icon // Map each capability to its icon
const iconMap: Record<number, React.ComponentType<{ className?: string }>> = { const iconMap: Record<
number,
React.ComponentType<{ className?: string }>
> = {
1: MessagesSquare, // Multi-turn Understanding 1: MessagesSquare, // Multi-turn Understanding
2: GitFork, // Multi-Intent Detection 2: GitFork, // Multi-Intent Detection
3: Route, // Content-Dependency Routing 3: Route, // Content-Dependency Routing
4: RefreshCw, // Conversational-Flow Handling 4: RefreshCw, // Conversational-Flow Handling
}; };
const Icon = iconMap[capability.id]; const Icon = iconMap[capability.id];
return ( return (
<div key={capability.id} className="flex flex-col"> <div key={capability.id} className="flex flex-col">
{/* Icon Card */} {/* Icon Card */}
@ -84,9 +89,7 @@ export function ResearchCapabilities() {
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="bg-gradient-to-b from-[rgba(177,184,255,0.16)] to-[rgba(17,28,132,0.035)] border-2 border-[rgba(171,178,250,0.27)] rounded-md p-6 h-40 flex items-center justify-center mb-4" className="bg-gradient-to-b from-[rgba(177,184,255,0.16)] to-[rgba(17,28,132,0.035)] border-2 border-[rgba(171,178,250,0.27)] rounded-md p-6 h-40 flex items-center justify-center mb-4"
> >
{Icon && ( {Icon && <Icon className="w-24 h-24 text-[#9797ea]" />}
<Icon className="w-24 h-24 text-[#9797ea]" />
)}
</motion.div> </motion.div>
{/* Title */} {/* Title */}
@ -107,15 +110,18 @@ export function ResearchCapabilities() {
<div className="hidden lg:grid lg:grid-cols-4 gap-6 mb-6"> <div className="hidden lg:grid lg:grid-cols-4 gap-6 mb-6">
{capabilitiesData.map((capability) => { {capabilitiesData.map((capability) => {
// Map each capability to its icon // Map each capability to its icon
const iconMap: Record<number, React.ComponentType<{ className?: string }>> = { const iconMap: Record<
number,
React.ComponentType<{ className?: string }>
> = {
1: MessagesSquare, // Multi-turn Understanding 1: MessagesSquare, // Multi-turn Understanding
2: GitFork, // Multi-Intent Detection 2: GitFork, // Multi-Intent Detection
3: Route, // Content-Dependency Routing 3: Route, // Content-Dependency Routing
4: RefreshCw, // Conversational-Flow Handling 4: RefreshCw, // Conversational-Flow Handling
}; };
const Icon = iconMap[capability.id]; const Icon = iconMap[capability.id];
return ( return (
<motion.div <motion.div
key={capability.id} key={capability.id}
@ -123,9 +129,7 @@ export function ResearchCapabilities() {
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="bg-gradient-to-b from-[rgba(177,184,255,0.16)] to-[rgba(17,28,132,0.035)] border-2 border-[rgba(171,178,250,0.27)] rounded-md p-6 h-52 flex items-center justify-center" className="bg-gradient-to-b from-[rgba(177,184,255,0.16)] to-[rgba(17,28,132,0.035)] border-2 border-[rgba(171,178,250,0.27)] rounded-md p-6 h-52 flex items-center justify-center"
> >
{Icon && ( {Icon && <Icon className="w-24 h-24 text-[#9797ea]" />}
<Icon className="w-24 h-24 text-[#9797ea]" />
)}
</motion.div> </motion.div>
); );
})} })}

View file

@ -24,9 +24,7 @@ export function ResearchHero() {
</span> </span>
<span className="text-xs sm:text-sm font-[600] tracking-[-0.6px]! text-black leading-tight"> <span className="text-xs sm:text-sm font-[600] tracking-[-0.6px]! text-black leading-tight">
<span className=""> <span className="">Plano Orchestrator models released</span>
Plano Orchestrator models released
</span>
</span> </span>
</div> </div>
</div> </div>
@ -40,16 +38,19 @@ export function ResearchHero() {
{/* Description */} {/* Description */}
<div className="max-w-70 sm:max-w-2xl relative z-10"> <div className="max-w-70 sm:max-w-2xl relative z-10">
<p className="text-base sm:text-lg md:text-xl lg:text-[22px] font-sans font-normal tracking-[-1.0px] sm:tracking-[-1.22px]! text-black"> <p className="text-base sm:text-lg md:text-xl lg:text-[22px] font-sans font-normal tracking-[-1.0px] sm:tracking-[-1.22px]! text-black">
Our applied research focuses on how to deliver agents safely, efficiently, Our applied research focuses on how to deliver agents safely,
and with improved real-world performance critical for any AI application, efficiently, and with improved real-world performance critical for
but work that sits outside of any agent's core product logic. any AI application, but work that sits outside of any agent's core
product logic.
</p> </p>
</div> </div>
<div className="flex flex-col sm:flex-row items-stretch sm:items-start gap-3 sm:gap-4 mt-6 sm:mt-8 relative z-10"> <div className="flex flex-col sm:flex-row items-stretch sm:items-start gap-3 sm:gap-4 mt-6 sm:mt-8 relative z-10">
<Button asChild className="w-full sm:w-auto"> <Button asChild className="w-full sm:w-auto">
<Link href="https://huggingface.co/katanemo">Available on Hugging Face</Link> <Link href="https://huggingface.co/katanemo">
</Button> Available on Hugging Face
</Link>
</Button>
</div> </div>
</div> </div>
</section> </section>