feat(www): improvements to mobile design, layout of diagrams
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 317 KiB |
98
www/public/BuiltOnEnvoy.svg
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
69
www/public/PromptRouting.svg
Normal file
|
After Width: | Height: | Size: 24 KiB |
69
www/public/PurposeBuilt.svg
Normal file
|
After Width: | Height: | Size: 48 KiB |
|
|
@ -159,16 +159,7 @@
|
|||
--radius-xl: calc(var(--radius) + 4px);
|
||||
}
|
||||
|
||||
/* Global letter-spacing for fonts */
|
||||
.font-sans,
|
||||
[class*="font-sans"] {
|
||||
letter-spacing: -4.56px;
|
||||
}
|
||||
|
||||
.font-mono,
|
||||
[class*="font-mono"] {
|
||||
letter-spacing: -0.96px;
|
||||
}
|
||||
/* Global letter-spacing removed - use Tailwind tracking utilities (tracking-tight, tracking-[-1.92px], etc.) instead */
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@ const footerLinks = {
|
|||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="relative overflow-hidden py-24 px-6 lg:px-[102px] pb-48" style={{ background: 'linear-gradient(to top right, #ffffff, #dcdfff)' }}>
|
||||
<footer className="relative overflow-hidden pt-20 px-6 lg:px-[102px] pb-48" style={{ background: 'linear-gradient(to top right, #ffffff, #dcdfff)' }}>
|
||||
<div className="max-w-[81rem] mx-auto relative z-10">
|
||||
{/* Main Grid Layout */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-20">
|
||||
{/* Left Column - Tagline and Copyright */}
|
||||
<div className="flex flex-col">
|
||||
<p className="font-sans text-2xl text-black tracking-[-1.7px]! leading-8 mb-8">
|
||||
<p className="font-sans text-lg sm:text-xl lg:text-2xl text-black tracking-[-1.2px] sm:tracking-[-1.5px] lg:tracking-[-1.7px]! leading-7 sm:leading-8 mb-6 sm:mb-8">
|
||||
Plano is the powerful, intelligent platform that empowers teams to seamlessly build, automate, and scale agentic systems with ease.
|
||||
</p>
|
||||
|
||||
{/* Copyright */}
|
||||
<div className="mt-auto">
|
||||
<p className="font-sans text-base text-black/63 tracking-[-0.8px]!">
|
||||
<p className="font-sans text-sm sm:text-base text-black/63 tracking-[-0.6px] sm:tracking-[-0.8px]!">
|
||||
© Katanemo Labs, Inc. 2025 / Plano by Katanemo Labs, Inc.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -38,15 +38,15 @@ export function Footer() {
|
|||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-8">
|
||||
{/* Company Links */}
|
||||
<div>
|
||||
<h3 className="font-sans font-medium text-3xl text-black tracking-[-1.6px]! mb-6">
|
||||
<h3 className="font-sans font-medium text-2xl sm:text-2xl lg:text-3xl text-black tracking-[-1.2px] sm:tracking-[-1.4px] lg:tracking-[-1.6px]! mb-4 sm:mb-6">
|
||||
Company
|
||||
</h3>
|
||||
<nav className="space-y-4">
|
||||
<nav className="space-y-3 sm:space-y-4">
|
||||
{footerLinks.company.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="block font-sans text-xl text-black tracking-[-1px]! hover:text-[var(--primary)] transition-colors"
|
||||
className="block font-sans text-base sm:text-lg lg:text-xl text-black tracking-[-0.8px] sm:tracking-[-0.9px] lg:tracking-[-1px]! hover:text-[var(--primary)] transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
|
|
@ -56,15 +56,15 @@ export function Footer() {
|
|||
|
||||
{/* Developer Resources */}
|
||||
<div>
|
||||
<h3 className="font-sans font-medium text-3xl text-black tracking-[-1.6px]! mb-6">
|
||||
<h3 className="font-sans font-medium text-2xl sm:text-2xl lg:text-3xl text-black tracking-[-1.2px] sm:tracking-[-1.4px] lg:tracking-[-1.6px]! mb-4 sm:mb-6">
|
||||
Developer Resources
|
||||
</h3>
|
||||
<nav className="space-y-4">
|
||||
<nav className="space-y-3 sm:space-y-4">
|
||||
{footerLinks.developerResources.map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="block font-sans text-xl text-black tracking-[-1px]! hover:text-[var(--primary)] transition-colors"
|
||||
className="block font-sans text-base sm:text-lg lg:text-xl text-black tracking-[-0.8px] sm:tracking-[-0.9px] lg:tracking-[-1px]! hover:text-[var(--primary)] transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -5,40 +5,43 @@ import { NetworkAnimation } from "./NetworkAnimation";
|
|||
|
||||
export function Hero() {
|
||||
return (
|
||||
<section className="relative pt-15 pb-6 px-6 lg:px-8">
|
||||
<section className="relative pt-8 sm:pt-12 lg:pt-15 pb-6 px-4 sm:px-6 lg:px-8">
|
||||
<div className="hidden lg:block">
|
||||
<NetworkAnimation />
|
||||
</div>
|
||||
<div className="max-w-[81rem] mx-auto relative z-10">
|
||||
<div className="max-w-3xl mb-4">
|
||||
<div className="max-w-3xl mb-6 sm:mb-4">
|
||||
{/* Version Badge */}
|
||||
<div className="mb-6">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1 rounded-full bg-[rgba(185,191,255,0.4)] border border-[var(--secondary)] shadow backdrop-blur">
|
||||
<span className="text-sm font-medium text-black/65">v0.4</span>
|
||||
<span className="text-sm font-medium text-black">—</span>
|
||||
<span className="text-sm font-[600] tracking-[-0.6px]! text-black">Unified /v1/responses API with state management</span>
|
||||
<div className="mb-4 sm:mb-6">
|
||||
<div className="inline-flex flex-wrap items-center gap-1.5 sm:gap-2 px-3 sm:px-4 py-1 rounded-full bg-[rgba(185,191,255,0.4)] border border-[var(--secondary)] shadow backdrop-blur">
|
||||
<span className="text-xs sm:text-sm font-medium text-black/65">v0.4</span>
|
||||
<span className="text-xs sm:text-sm font-medium text-black hidden sm:inline">—</span>
|
||||
<span className="text-xs sm:text-sm font-[600] tracking-[-0.6px]! text-black leading-tight">
|
||||
<span className="hidden sm:inline">Unified /v1/responses API with state management</span>
|
||||
<span className="sm:hidden">Unified /v1/responses API</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Heading */}
|
||||
<h1 className="text-5xl lg:text-7xl font-normal leading-tight tracking-tight text-black mb-4 flex flex-col -space-y-3">
|
||||
<h1 className="text-4xl sm:text-4xl md:text-5xl lg:text-7xl font-normal leading-tight tracking-tighter text-black mb-4 sm:mb-6 flex flex-col gap-0 sm:-space-y-2 lg:-space-y-3">
|
||||
<span className="font-sans">Models-native </span>
|
||||
<span className="font-sans font-medium text-[var(--secondary)]">dataplane for agents</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Subheading with CTA Buttons on the right */}
|
||||
{/* Subheading with CTA Buttons */}
|
||||
<div className="max-w-7xl flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
|
||||
<p className="text-xl lg:text-2xl font-sans font-[500] tracking-[-1.92px]! text-black max-w-4xl">
|
||||
<p className="text-base sm:text-lg md:text-xl lg:text-2xl font-sans font-[500] tracking-[-1.2px] sm:tracking-[-1.92px]! text-black max-w-4xl">
|
||||
Build agents faster, and scale them reliably by offloading the plumbing work in AI agents.
|
||||
</p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className="justify-self-end flex justify-end items-center gap-4">
|
||||
<Button asChild>
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 sm:gap-4 w-full sm:w-auto lg:justify-end">
|
||||
<Button asChild className="w-full sm:w-auto">
|
||||
<Link href="/get-started">Get started</Link>
|
||||
</Button>
|
||||
<Button variant="secondary" asChild>
|
||||
<Button variant="secondary" asChild className="w-full sm:w-auto">
|
||||
<Link href="/docs">Documentation</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,28 +5,49 @@ import Image from "next/image";
|
|||
|
||||
export function HowItWorksSection() {
|
||||
return (
|
||||
<section className="bg-[#1a1a1a] text-white pb-28 px-6 lg:px-[102px]">
|
||||
<div className="max-w-324 mx-auto">
|
||||
<div className="flex flex-col gap-16">
|
||||
<section className="bg-[#1a1a1a] text-white pb-16 sm:pb-20 lg:pb-28 sm:pt-3 pt-20">
|
||||
<div className="max-w-324 mx-auto sm:pl-0">
|
||||
<div className="flex flex-col gap-8 sm:gap-12 lg:gap-16">
|
||||
{/* Header and Description */}
|
||||
<div className="max-w-4xl">
|
||||
<h2 className="font-sans font-normal text-3xl lg:text-4xl tracking-[-2.4px]! text-white leading-[1.03] mb-8">
|
||||
<div className="max-w-4xl lg:-ml-[102px] lg:pl-[102px] sm:pl-0 pl-4">
|
||||
<h2 className="font-sans font-normal text-2xl sm:text-3xl lg:text-4xl tracking-[-2px] sm:tracking-[-2.4px]! text-white leading-[1.03] mb-6 sm:mb-8">
|
||||
A high-level overview of how Plano works
|
||||
</h2>
|
||||
<div className="font-mono text-white text-xl lg:text-lg leading-10 tracking-[-1.2px]!">
|
||||
<div className="font-mono text-white w-100 sm:w-full text-sm sm:text-lg lg:text-lg leading-7 sm:leading-9 lg:leading-10 tracking-[-0.8px] sm:tracking-[-1.2px]!">
|
||||
<p className="mb-0">
|
||||
Plano offers a delightful developer experience with a simple configuration file that describes the types of prompts your agentic app supports, a set of APIs that need to be plugged in for agentic scenarios (including retrieval queries) and your choice of LLMs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Large Diagram */}
|
||||
<div className="w-full">
|
||||
<Image
|
||||
{/* Large Diagram - Scrollable on mobile, normal on desktop */}
|
||||
{/* Mobile: Full-width scrollable container that extends to viewport edges */}
|
||||
<div className="mt-5 lg:hidden relative left-1/2 right-1/2 -ml-[50vw] -mr-[50vw] w-screen overflow-x-auto overflow-y-visible" style={{ scrollbarWidth: "none", msOverflowStyle: "none", WebkitOverflowScrolling: "touch" }}>
|
||||
<style jsx>{`
|
||||
.diagram-scroll-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`}</style>
|
||||
<div className="diagram-scroll-container inline-block">
|
||||
<Image
|
||||
src="/HowItWorks.svg"
|
||||
alt="How Plano Works Diagram"
|
||||
width={1200}
|
||||
height={600}
|
||||
className="h-auto"
|
||||
style={{ width: "1200px", maxWidth: "none", display: "block" }}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop: Extends to container edges */}
|
||||
<div className="hidden lg:block -w-[calc(10%+20px)] -mx-[10px]">
|
||||
<Image
|
||||
src="/HowItWorks.svg"
|
||||
alt="How Plano Works Diagram"
|
||||
width={1200}
|
||||
height={600}
|
||||
width={10}
|
||||
height={10}
|
||||
className="w-full h-auto"
|
||||
priority
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -65,21 +65,24 @@ export function IdeaToAgentSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<section className="relative py-24 px-6 lg:px-[102px]">
|
||||
<section className="relative py-12 sm:py-16 lg:py-24 px-4 sm:px-6 lg:px-[102px]">
|
||||
<div className="max-w-[81rem] mx-auto">
|
||||
{/* Main Heading */}
|
||||
<h2 className="font-sans font-normal text-4xl tracking-[-2.96px]! text-black mb-10">
|
||||
<h2 className="font-sans font-normal text-2xl sm:text-3xl lg:text-4xl tracking-[-2px] sm:tracking-[-2.96px]! text-black mb-6 sm:mb-8 lg:mb-10">
|
||||
Idea to agent — without overhead
|
||||
</h2>
|
||||
|
||||
{/* Progress Indicators */}
|
||||
<div className="flex gap-2 mb-12">
|
||||
<div className="flex gap-1.5 sm:gap-2 mb-4 sm:mb-6 lg:mb-6 w-full">
|
||||
{carouselData.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleSlideClick(index)}
|
||||
className="relative h-2 rounded-full overflow-hidden transition-all duration-300 hover:opacity-80"
|
||||
style={{ width: index === currentSlide ? '292px' : '293px' }}
|
||||
className={`relative h-1.5 sm:h-2 rounded-full overflow-hidden transition-all duration-300 hover:opacity-80 ${
|
||||
index === currentSlide
|
||||
? 'flex-1 sm:w-16 md:w-20 lg:w-[292px]'
|
||||
: 'flex-1 sm:w-16 md:w-20 lg:w-[293px]'
|
||||
}`}
|
||||
>
|
||||
{/* Background */}
|
||||
<div className="absolute inset-0 bg-black/6 rounded-full" />
|
||||
|
|
@ -103,51 +106,74 @@ export function IdeaToAgentSection() {
|
|||
))}
|
||||
</div>
|
||||
|
||||
{/* Carousel Content */}
|
||||
<div className="relative min-h-[300px] lg:min-h-[400px]">
|
||||
{/* Carousel Content - Fixed height to prevent layout shift */}
|
||||
<div className="relative h-[500px] sm:h-[550px] md:h-[600px] lg:h-[500px]">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={currentSlide}
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
className="absolute inset-0"
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row lg:justify-between lg:items-center lg:gap-12">
|
||||
<div className="flex flex-col lg:flex-row lg:justify-between lg:items-center lg:gap-12 h-full">
|
||||
{/* Left Content */}
|
||||
<div className="flex-1">
|
||||
<div className="max-w-[692px] mt-6">
|
||||
<div className="flex-1 order-1 lg:order-1 flex flex-col justify-center">
|
||||
<div className="max-w-[692px] mt-0 lg:mt-0">
|
||||
{/* Category */}
|
||||
<p className="font-mono font-bold text-[#2a3178] text-xl tracking-[1.92px]! mb-4 leading-[1.102]">
|
||||
<p className="font-mono font-bold text-[#2a3178] text-sm sm:text-base lg:text-xl tracking-[1.44px] sm:tracking-[1.92px]! mb-3 sm:mb-4 leading-[1.102]">
|
||||
{carouselData[currentSlide].category}
|
||||
</p>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="font-sans font-medium text-[#9797ea] text-5xl tracking-[-2.96px]! mb-7">
|
||||
<h3 className="font-sans font-medium text-[#9797ea] text-2xl sm:text-3xl lg:text-5xl tracking-tight sm:tracking-[-2.96px]! mb-4 sm:mb-6 lg:mb-7">
|
||||
{carouselData[currentSlide].title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<div className="font-mono text-black text-lg leading-8 max-w-140">
|
||||
<div className="font-mono text-black text-sm sm:text-base lg:text-lg leading-6 sm:leading-7 lg:leading-8 max-w-full lg:max-w-140 tracking-[-0.8px] sm:tracking-[-1.2px]!">
|
||||
<p className="mb-0">
|
||||
{carouselData[currentSlide].description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button className="mt-8">
|
||||
<Button className="mt-6 sm:mt-8 w-full sm:w-auto">
|
||||
Learn more
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Image - Only show if current slide has an image */}
|
||||
{/* Image - Show below on mobile, right side on desktop */}
|
||||
{carouselData[currentSlide].image && (
|
||||
<div className="hidden lg:flex shrink-0 w-[400px] xl:w-[500px] justify-end items-center ">
|
||||
<div className="flex lg:hidden shrink-0 w-full justify-center items-center mb-6 sm:mb-8 order-0 lg:order-2">
|
||||
<img
|
||||
src={carouselData[currentSlide].image}
|
||||
alt={carouselData[currentSlide].category}
|
||||
className="w-full h-auto max-h-[450px] object-contain"
|
||||
className={`w-full h-auto object-contain ${
|
||||
carouselData[currentSlide].image === "/Telemetry.svg"
|
||||
? "max-w-md sm:max-w-lg max-h-[300px] sm:max-h-[350px]"
|
||||
: "max-w-sm sm:max-w-md max-h-[250px] sm:max-h-[300px]"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Right Image - Desktop only */}
|
||||
{carouselData[currentSlide].image && (
|
||||
<div className={`hidden lg:flex shrink-0 justify-end items-center order-2 ${
|
||||
carouselData[currentSlide].image === "/Telemetry.svg"
|
||||
? "w-[500px] xl:w-[600px]"
|
||||
: "w-[400px] xl:w-[500px]"
|
||||
}`}>
|
||||
<img
|
||||
src={carouselData[currentSlide].image}
|
||||
alt={carouselData[currentSlide].category}
|
||||
className={`w-full h-auto object-contain ${
|
||||
carouselData[currentSlide].image === "/Telemetry.svg"
|
||||
? "max-h-[550px]"
|
||||
: "max-h-[450px]"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ export function IntroSection() {
|
|||
Plano is a framework-friendly proxy server and dataplane for agents,
|
||||
deployed as a sidecar. Plano handles the critical plumbing work in AI
|
||||
like agent routing and orchestration, comprehensive traces for agentic
|
||||
interactions, guardrail hooks, unified APIs for LLMs — so <strong> developers </strong>
|
||||
can focus more on modeling workflows, <strong> product teams </strong> can accelerate
|
||||
interactions, guardrail hooks, unified APIs for LLMs —
|
||||
</p>
|
||||
<p className="mb-0 leading-8 mt-4">
|
||||
<strong>Developers</strong> can focus more on modeling workflows, <strong>product teams</strong> can accelerate
|
||||
feedback loops for reinforcement learning and <strong>engineering teams</strong> can
|
||||
standardize policies and access controls across every agent and LLM for
|
||||
safer, more reliable scaling.
|
||||
|
|
|
|||
|
|
@ -26,23 +26,28 @@ const customerLogos = [
|
|||
|
||||
export function LogoCloud() {
|
||||
return (
|
||||
<section className="relative py-8 px-6 lg:px-8">
|
||||
<section className="relative py-6 sm:py-8 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-[81rem] mx-auto">
|
||||
<div className="flex items-center justify-center gap-8 lg:gap-12 flex-wrap lg:flex-nowrap">
|
||||
{customerLogos.map((logo) => (
|
||||
<div
|
||||
key={logo.name}
|
||||
className="flex items-center justify-center mx-auto opacity-60 hover:opacity-80 transition-opacity duration-300 w-48 h-12"
|
||||
>
|
||||
<Image
|
||||
src={logo.src}
|
||||
alt={`${logo.name} logo`}
|
||||
width={128}
|
||||
height={40}
|
||||
className="w-full h-full object-contain filter grayscale hover:grayscale-0 transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 sm:gap-6 md:gap-8 lg:gap-12 items-center justify-items-center">
|
||||
{customerLogos.map((logo, index) => {
|
||||
const isLast = index === customerLogos.length - 1;
|
||||
return (
|
||||
<div
|
||||
key={logo.name}
|
||||
className={`flex items-center justify-center opacity-60 hover:opacity-80 transition-opacity duration-300 w-full max-w-32 sm:max-w-40 md:max-w-48 h-10 sm:h-12 md:h-16 ${
|
||||
isLast ? 'col-span-2 md:col-span-3 lg:col-span-1' : ''
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={logo.src}
|
||||
alt={`${logo.name} logo`}
|
||||
width={128}
|
||||
height={40}
|
||||
className="w-full h-full object-contain filter grayscale hover:grayscale-0 transition-all duration-300"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { Logo } from "./Logo";
|
||||
import { Button } from "./ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { X, Menu } from "lucide-react";
|
||||
|
||||
const navItems = [
|
||||
{ href: "/start", label: "start locally" },
|
||||
|
|
@ -15,6 +17,147 @@ const navItems = [
|
|||
];
|
||||
|
||||
export function Navbar() {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [isDarkBackground, setIsDarkBackground] = useState(false);
|
||||
|
||||
// Detect background color behind dropdown menu
|
||||
useEffect(() => {
|
||||
if (!isMenuOpen) {
|
||||
setIsDarkBackground(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const detectBackground = () => {
|
||||
// Small delay to ensure DOM is ready
|
||||
setTimeout(() => {
|
||||
const nav = document.querySelector("nav");
|
||||
if (!nav) return;
|
||||
|
||||
const navRect = nav.getBoundingClientRect();
|
||||
const dropdownBottom = navRect.bottom;
|
||||
const checkY = dropdownBottom + 20; // Just below the dropdown
|
||||
|
||||
// First, try to find section elements directly
|
||||
const main = document.querySelector("main");
|
||||
if (main) {
|
||||
const sections = main.querySelectorAll("section");
|
||||
let foundDarkSection = false;
|
||||
|
||||
sections.forEach((section) => {
|
||||
const rect = section.getBoundingClientRect();
|
||||
// Check if this section is visible below the navbar
|
||||
if (rect.top <= checkY && rect.bottom > checkY) {
|
||||
// Check for dark background classes
|
||||
const classList = Array.from(section.classList);
|
||||
const hasDarkBg = classList.some(cls =>
|
||||
cls.includes('bg-[#1a1a1a]') ||
|
||||
cls.includes('bg-black') ||
|
||||
cls.includes('bg-gray-900') ||
|
||||
cls.includes('bg-neutral-900') ||
|
||||
cls.includes('dark')
|
||||
);
|
||||
|
||||
if (hasDarkBg) {
|
||||
foundDarkSection = true;
|
||||
setIsDarkBackground(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Also check computed background
|
||||
const computed = window.getComputedStyle(section);
|
||||
const bg = computed.backgroundColor;
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
const rgbMatch = bg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (rgbMatch) {
|
||||
const r = parseInt(rgbMatch[1]);
|
||||
const g = parseInt(rgbMatch[2]);
|
||||
const b = parseInt(rgbMatch[3]);
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
setIsDarkBackground(luminance < 0.5);
|
||||
foundDarkSection = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundDarkSection) return;
|
||||
}
|
||||
|
||||
// Fallback: Check element at point
|
||||
const centerX = window.innerWidth / 2;
|
||||
const elementBelow = document.elementFromPoint(centerX, checkY);
|
||||
|
||||
if (elementBelow) {
|
||||
let current: HTMLElement | null = elementBelow as HTMLElement;
|
||||
let backgroundColor = "";
|
||||
|
||||
// Walk up the DOM tree
|
||||
let levels = 0;
|
||||
while (current && !backgroundColor && current !== document.body && levels < 15) {
|
||||
const computed = window.getComputedStyle(current);
|
||||
const bg = computed.backgroundColor;
|
||||
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
const rgbaMatch = bg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
||||
if (rgbaMatch) {
|
||||
const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
|
||||
if (alpha > 0.1) {
|
||||
backgroundColor = bg;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
backgroundColor = bg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current = current.parentElement;
|
||||
levels++;
|
||||
}
|
||||
|
||||
if (!backgroundColor) {
|
||||
const bodyBg = window.getComputedStyle(document.body).backgroundColor;
|
||||
backgroundColor = bodyBg;
|
||||
}
|
||||
|
||||
if (backgroundColor) {
|
||||
const rgbMatch = backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
||||
if (rgbMatch) {
|
||||
const r = parseInt(rgbMatch[1]);
|
||||
const g = parseInt(rgbMatch[2]);
|
||||
const b = parseInt(rgbMatch[3]);
|
||||
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
setIsDarkBackground(luminance < 0.5);
|
||||
} else {
|
||||
const darkColors = ['black', '#000', '#000000', 'rgb(0,0,0)', 'rgba(0,0,0', '#1a1a1a'];
|
||||
const isDark = darkColors.some(color => backgroundColor.toLowerCase().includes(color.toLowerCase()));
|
||||
setIsDarkBackground(isDark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// Detect on open and on scroll
|
||||
detectBackground();
|
||||
const scrollHandler = () => detectBackground();
|
||||
const resizeHandler = () => detectBackground();
|
||||
|
||||
window.addEventListener("scroll", scrollHandler, { passive: true });
|
||||
window.addEventListener("resize", resizeHandler);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", scrollHandler);
|
||||
window.removeEventListener("resize", resizeHandler);
|
||||
};
|
||||
}, [isMenuOpen]);
|
||||
|
||||
// Close menu when route changes
|
||||
const handleLinkClick = () => {
|
||||
setIsMenuOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-gradient-to-b from-transparent to-white/5 backdrop-blur border-b border-neutral-200/5">
|
||||
<div className="max-w-[85rem] mx-auto px-6 lg:px-8">
|
||||
|
|
@ -44,26 +187,82 @@ export function Navbar() {
|
|||
{/* Mobile Menu Button */}
|
||||
<div className="md:hidden">
|
||||
<button
|
||||
className="p-2 rounded-md text-[var(--muted)] hover:text-[var(--primary)]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsMenuOpen(!isMenuOpen);
|
||||
}}
|
||||
className="p-2 rounded-md text-[var(--muted)] hover:text-[var(--primary)] transition-colors"
|
||||
aria-label="Toggle menu"
|
||||
aria-expanded={isMenuOpen}
|
||||
>
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{isMenuOpen ? (
|
||||
<motion.div
|
||||
key="close"
|
||||
initial={{ opacity: 0, rotate: -90 }}
|
||||
animate={{ opacity: 1, rotate: 0 }}
|
||||
exit={{ opacity: 0, rotate: 90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<X className="h-6 w-6" />
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="menu"
|
||||
initial={{ opacity: 0, rotate: 90 }}
|
||||
animate={{ opacity: 1, rotate: 0 }}
|
||||
exit={{ opacity: 0, rotate: -90 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Menu className="h-6 w-6" />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Dropdown Menu - Outside constrained container for full width */}
|
||||
<AnimatePresence>
|
||||
{isMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "auto" }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="md:hidden overflow-hidden bg-[#7580DF]/70"
|
||||
>
|
||||
<div className="max-w-[85rem] mx-auto px-6 lg:px-8 py-3">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{navItems.map((item, index) => (
|
||||
<motion.div
|
||||
key={item.href}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.3,
|
||||
delay: index * 0.05,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
href={item.href}
|
||||
onClick={handleLinkClick}
|
||||
className={cn(
|
||||
"block px-0 py-1.5 border-b border-dashed transition-colors font-mono tracking-tighter",
|
||||
"text-sm font-medium",
|
||||
"text-white",
|
||||
)}>
|
||||
{item.label}
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { ArrowRightIcon } from "lucide-react";
|
||||
import { ArrowRightIcon, Network, Filter, TrendingUp, Shield, Server, XIcon } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogClose } from "./ui/dialog";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
interface UseCase {
|
||||
|
|
@ -12,6 +12,8 @@ interface UseCase {
|
|||
title: string;
|
||||
summary: string;
|
||||
fullContent: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
gradient: string;
|
||||
}
|
||||
|
||||
const useCasesData: UseCase[] = [
|
||||
|
|
@ -20,35 +22,45 @@ const useCasesData: UseCase[] = [
|
|||
category: "AGENT ORCHESTRATION",
|
||||
title: "Multi-agent systems without framework lock-in",
|
||||
summary: "Seamless routing and orchestration for complex agent interactions",
|
||||
fullContent: "Plano manages agent routing and orchestration without framework dependencies, allowing seamless multi-agent interactions. This is ideal for building complex systems like automated customer support or data processing pipelines, where agents hand off tasks efficiently to deliver end-to-end solutions faster."
|
||||
fullContent: "Plano manages agent routing and orchestration without framework dependencies, allowing seamless multi-agent interactions. This is ideal for building complex systems like automated customer support or data processing pipelines, where agents hand off tasks efficiently to deliver end-to-end solutions faster.",
|
||||
icon: Network,
|
||||
gradient: "from-[rgba(119,128,217,0.15)] via-[rgba(119,128,217,0.08)] to-[rgba(17,28,132,0.05)]"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
category: "CONTEXT ENGINEERING",
|
||||
title: "Reusable filters for smarter agents",
|
||||
summary: "Inject data, reformulate queries, and enforce policies efficiently",
|
||||
fullContent: "Plano's filter chain encourages reuse and decoupling for context engineering tasks like injecting data, reformulating queries, and enforcing policy before calls reach an agent or LLM. This means faster debugging, cleaner architecture, and more accurate, on-policy agents —without bespoke glue code."
|
||||
fullContent: "Plano's filter chain encourages reuse and decoupling for context engineering tasks like injecting data, reformulating queries, and enforcing policy before calls reach an agent or LLM. This means faster debugging, cleaner architecture, and more accurate, on-policy agents —without bespoke glue code.",
|
||||
icon: Filter,
|
||||
gradient: "from-[rgba(177,184,255,0.15)] via-[rgba(177,184,255,0.08)] to-[rgba(17,28,132,0.05)]"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
category: "REINFORCEMENT LEARNING",
|
||||
title: "Production signals for continuous improvement",
|
||||
summary: "Capture rich traces to accelerate training and refinement",
|
||||
fullContent: "Plano captures hyper-rich tracing and log samples from production traffic, feeding into reinforcement learning and fine-tuning cycles. This accelerates iteration in areas like recommendation engines, helping teams quickly identify failures, refine prompts, and boost agent effectiveness based on real-user signals."
|
||||
fullContent: "Plano captures hyper-rich tracing and log samples from production traffic, feeding into reinforcement learning and fine-tuning cycles. This accelerates iteration in areas like recommendation engines, helping teams quickly identify failures, refine prompts, and boost agent effectiveness based on real-user signals.",
|
||||
icon: TrendingUp,
|
||||
gradient: "from-[rgba(185,191,255,0.15)] via-[rgba(185,191,255,0.08)] to-[rgba(17,28,132,0.05)]"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
category: "SECURITY",
|
||||
title: "Built-in guardrails and centralized policies",
|
||||
summary: "Safe scaling with jailbreak detection and access controls",
|
||||
fullContent: "With built-in guardrails, centralized policies, and access controls, Plano ensures safe scaling across LLMs, detecting issues like jailbreak attempts. This is critical for deployments in regulated fields like finance or healthcare, and minimizing risks while standardizing reliability and security of agents."
|
||||
fullContent: "With built-in guardrails, centralized policies, and access controls, Plano ensures safe scaling across LLMs, detecting issues like jailbreak attempts. This is critical for deployments in regulated fields like finance or healthcare, and minimizing risks while standardizing reliability and security of agents.",
|
||||
icon: Shield,
|
||||
gradient: "from-[rgba(119,128,217,0.15)] via-[rgba(119,128,217,0.08)] to-[rgba(17,28,132,0.05)]"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
category: "ON-PREMISES",
|
||||
title: "Full data control in regulated environments",
|
||||
summary: "Deploy on private infrastructure without compromising features",
|
||||
fullContent: "Plano's lightweight sidecar model deploys effortlessly on your private infrastructure, empowering teams in regulated sectors to maintain full data control while benefiting from unified LLM access, custom filter chains, and production-grade tracing—without compromising on security or scalability."
|
||||
fullContent: "Plano's lightweight sidecar model deploys effortlessly on your private infrastructure, empowering teams in regulated sectors to maintain full data control while benefiting from unified LLM access, custom filter chains, and production-grade tracing—without compromising on security or scalability.",
|
||||
icon: Server,
|
||||
gradient: "from-[rgba(177,184,255,0.15)] via-[rgba(177,184,255,0.08)] to-[rgba(17,28,132,0.05)]"
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -56,23 +68,23 @@ export function UseCasesSection() {
|
|||
const [selectedUseCase, setSelectedUseCase] = useState<UseCase | null>(null);
|
||||
|
||||
return (
|
||||
<section className="relative py-24 px-6 lg:px-[102px]">
|
||||
<section className="relative py-12 sm:py-16 lg:py-10 px-4 sm:px-6 lg:px-[102px]">
|
||||
<div className="max-w-[81rem] mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="mb-14">
|
||||
<div className="mb-8 sm:mb-12 lg:mb-14">
|
||||
{/* USE CASES Badge */}
|
||||
<div className="mb-6">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1 rounded-full bg-[rgba(185,191,255,0.4)] border border-[var(--secondary)] shadow backdrop-blur">
|
||||
<span className="font-mono font-bold text-[#2a3178] text-sm tracking-[1.62px]!">USE CASES</span>
|
||||
<div className="mb-4 sm:mb-6">
|
||||
<div className="inline-flex items-center gap-2 px-3 sm:px-4 py-1 rounded-full bg-[rgba(185,191,255,0.4)] border border-[var(--secondary)] shadow backdrop-blur">
|
||||
<span className="font-mono font-bold text-[#2a3178] text-xs sm:text-sm tracking-[1.44px] sm:tracking-[1.62px]!">USE CASES</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Heading and CTA Button */}
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
|
||||
<h2 className="font-sans font-normal text-3xl lg:text-4xl tracking-[-2.88px]! text-black leading-[1.03]">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6 sm:gap-6">
|
||||
<h2 className="font-sans font-normal text-2xl sm:text-3xl lg:text-4xl tracking-[-2px] sm:tracking-[-2.88px]! text-black leading-[1.03]">
|
||||
What's possible with Plano
|
||||
</h2>
|
||||
<Button>
|
||||
<Button className="hidden lg:block">
|
||||
Start building
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -85,65 +97,143 @@ export function UseCasesSection() {
|
|||
key={useCase.id}
|
||||
whileHover={{ y: -4 }}
|
||||
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-8 h-90 flex flex-col justify-between cursor-pointer"
|
||||
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-4 sm:p-6 lg:p-8 h-auto sm:h-64 md:h-72 lg:h-90 flex flex-col justify-between cursor-pointer"
|
||||
onClick={() => setSelectedUseCase(useCase)}
|
||||
>
|
||||
{/* Category */}
|
||||
<div className="mb-6">
|
||||
<p className="font-mono font-bold text-[#2a3178] text-base tracking-[1.92px]! mb-4">
|
||||
<div className="mb-4 sm:mb-6">
|
||||
<p className="font-mono font-bold text-[#2a3178] text-sm sm:text-base tracking-[1.44px] sm:tracking-[1.92px]! mb-3 sm:mb-4">
|
||||
{useCase.category}
|
||||
</p>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="font-sans font-medium text-black text-xl lg:text-2xl tracking-[-1.2px]! leading-[1.102]">
|
||||
<h3 className="font-sans font-medium text-black text-lg sm:text-xl lg:text-2xl tracking-[-1.2px]! leading-[1.102]">
|
||||
{useCase.title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Learn More Link */}
|
||||
<div className="mt-auto">
|
||||
<button className="group flex items-center gap-2 font-mono font-bold text-[var(--primary)] text-base tracking-[1.92px]! leading-[1.45] hover:text-[var(--primary-dark)] transition-colors">
|
||||
<button className="group flex items-center gap-2 font-mono font-bold text-[var(--primary)] text-sm sm:text-base tracking-[1.44px] sm:tracking-[1.92px]! leading-[1.45] hover:text-[var(--primary-dark)] transition-colors">
|
||||
LEARN MORE
|
||||
<ArrowRightIcon className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
<ArrowRightIcon className="w-3.5 h-3.5 sm:w-4 sm:h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Start building button - Mobile only, appears last */}
|
||||
<div className="lg:hidden mt-8">
|
||||
<Button className="w-full">
|
||||
Start building
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
<Dialog open={selectedUseCase !== null} onOpenChange={(open) => !open && setSelectedUseCase(null)}>
|
||||
<AnimatePresence>
|
||||
{selectedUseCase && (
|
||||
<DialogContent className="max-w-2xl">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 10 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 10 }}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
>
|
||||
<DialogHeader>
|
||||
<div className="mb-4">
|
||||
<p className="font-mono font-bold text-[#2a3178] text-sm tracking-[1.62px]! mb-3">
|
||||
USE CASE
|
||||
</p>
|
||||
<DialogTitle className="font-sans font-medium text-3xl tracking-[-1.5px]! text-black leading-[1.1]">
|
||||
{selectedUseCase.category}
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<DialogDescription className="font-mono text-[#494949] text-base leading-relaxed pt-2">
|
||||
{selectedUseCase.fullContent}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="mt-6 flex justify-end">
|
||||
<Button onClick={() => setSelectedUseCase(null)}>
|
||||
Close
|
||||
</Button>
|
||||
{selectedUseCase && (() => {
|
||||
const IconComponent = selectedUseCase.icon;
|
||||
return (
|
||||
<DialogContent key={selectedUseCase.id} className="max-w-[90rem]! p-0 overflow-hidden" showCloseButton={false}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.98, y: 8 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.98, y: 8 }}
|
||||
transition={{ duration: 0.25, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="relative"
|
||||
>
|
||||
{/* Gradient Background */}
|
||||
<div className={`absolute inset-0 bg-gradient-to-br ${selectedUseCase.gradient} opacity-50`} />
|
||||
|
||||
{/* Decorative Border */}
|
||||
<div className="absolute inset-0 border-2 border-[rgba(171,178,250,0.3)] rounded-lg pointer-events-none" />
|
||||
|
||||
{/* Custom Close Button */}
|
||||
<DialogClose className="absolute top-4 right-4 z-50 rounded-xs opacity-70 hover:opacity-100 transition-opacity focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[rgba(171,178,250,0.5)] bg-white/80 backdrop-blur-sm p-2 hover:bg-white/90">
|
||||
<XIcon className="w-5 h-5 text-[#2a3178]" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
|
||||
{/* Content Container */}
|
||||
<div className="relative z-10 p-5 sm:p-8 md:p-10 lg:p-14">
|
||||
{/* Header Section with Icon */}
|
||||
<DialogHeader className="mb-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start gap-4 sm:gap-8 mb-8">
|
||||
{/* Icon Container - hidden on mobile */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1], delay: 0.1 }}
|
||||
className="hidden sm:flex shrink-0 w-14 h-14 sm:w-16 sm:h-16 rounded-xl bg-gradient-to-br from-[rgba(119,128,217,0.2)] to-[rgba(17,28,132,0.1)] border-2 border-[rgba(171,178,250,0.4)] items-center justify-center shadow-lg backdrop-blur-sm mx-0"
|
||||
>
|
||||
<IconComponent className="w-8 h-8 text-[#2a3178]" />
|
||||
</motion.div>
|
||||
|
||||
{/* Title Section */}
|
||||
<div className="flex-1 text-left mt-4 sm:mt-0">
|
||||
<motion.p
|
||||
initial={{ opacity: 0, x: -8 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1], delay: 0.15 }}
|
||||
className="font-mono font-bold text-[#2a3178] text-xs tracking-[1.62px]! mb-1 uppercase"
|
||||
>
|
||||
USE CASE
|
||||
</motion.p>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -8 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1], delay: 0.2 }}
|
||||
>
|
||||
<DialogTitle className="font-sans font-medium text-2xl sm:text-3xl lg:text-4xl xl:text-4xl tracking-[-1.5px]! text-black leading-[1.1] mb-4">
|
||||
{selectedUseCase.title}
|
||||
</DialogTitle>
|
||||
<div className="inline-flex items-center px-3 py-1 rounded-full bg-[rgba(185,191,255,0.3)] border border-[rgba(171,178,250,0.4)] backdrop-blur-sm">
|
||||
<span className="font-mono font-bold text-[#2a3178] text-xs tracking-[1.44px]!">
|
||||
{selectedUseCase.category}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1], delay: 0.3 }}
|
||||
className="mb-10"
|
||||
>
|
||||
<DialogDescription className="font-mono text-[#494949] text-base lg:text-base xl:text-lg leading-relaxed tracking-tight max-w-none mb-0">
|
||||
{selectedUseCase.fullContent}
|
||||
</DialogDescription>
|
||||
</motion.div>
|
||||
|
||||
{/* Footer with CTA - mobile friendly */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1], delay: 0.35 }}
|
||||
className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-4 pt-8 border-t border-[rgba(171,178,250,0.2)]"
|
||||
>
|
||||
{/* "Ready to get started?" is now first in column on mobile */}
|
||||
<div className="flex items-center gap-2 text-sm font-mono text-[#494949] justify-center sm:justify-start order-0">
|
||||
<span>Ready to get started?</span>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-3 w-full sm:w-auto order-1">
|
||||
<Button className="w-full sm:w-auto">
|
||||
Start building
|
||||
<ArrowRightIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</DialogContent>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
</AnimatePresence>
|
||||
</Dialog>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const verticalCarouselData = [
|
|||
category: "INTRODUCTION",
|
||||
title: "Simple to revolutionary",
|
||||
description: "Plano is an intelligent (edge and LLM) proxy server designed for agents - to help you focus on core business objectives. Arch handles critical but the pesky tasks related to the handling and processing of prompts, which includes detecting and rejecting jailbreak attempts, intelligent task routing for improved accuracy, mapping user requests into 'backend' functions, and managing the observability of prompts and LLM in a centralized way.",
|
||||
diagram: "/Introduction.svg"
|
||||
diagram: "/IntroDiagram.svg"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
|
@ -32,7 +32,7 @@ const verticalCarouselData = [
|
|||
category: "PURPOSE-BUILT",
|
||||
title: "Task-optimized, efficient LLMs",
|
||||
description: "Unlike generic API gateways, Plano is purpose-built for AI agent workloads. Every feature is designed with prompt processing, model routing, and agent orchestration in mind, providing optimal performance for your AI applications.",
|
||||
diagram: "/PurposeBuiltLLMs.svg"
|
||||
diagram: "/PurposeBuilt.svg"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
|
|
@ -51,19 +51,52 @@ export function VerticalCarouselSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<section className="relative bg-[#1a1a1a] text-white py-24 px-6 lg:px-[102px]">
|
||||
<section className="relative bg-[#1a1a1a] text-white py-12 sm:py-16 lg:py-24 px-4 sm:px-6 lg:px-[102px]">
|
||||
<div className="max-w-324 mx-auto">
|
||||
{/* Main Heading */}
|
||||
<h2 className="font-sans font-normal text-3xl lg:text-4xl tracking-[-2.88px]! text-white leading-[1.03] mb-16 max-w-4xl">
|
||||
<h2 className="font-sans font-normal text-2xl sm:text-3xl lg:text-4xl tracking-[-2px] sm:tracking-[-2.88px]! text-white leading-[1.03] mb-8 sm:mb-12 lg:mb-12 max-w-4xl">
|
||||
Basic scenarios to powerful agentic apps in minutes
|
||||
</h2>
|
||||
|
||||
{/* Vertical Carousel Layout */}
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
{/* Left Sidebar Navigation */}
|
||||
<div className="lg:w-72 shrink-0">
|
||||
{/* Mobile: Horizontal Scroller Navigation */}
|
||||
<div className="lg:hidden mb-8 -mx-4 sm:mx-0 px-4 sm:px-0">
|
||||
<div
|
||||
className="relative overflow-x-auto pb-2"
|
||||
style={{
|
||||
scrollbarWidth: "none",
|
||||
msOverflowStyle: "none"
|
||||
}}
|
||||
>
|
||||
<style jsx>{`
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`}</style>
|
||||
<div className="flex gap-4 min-w-max hide-scrollbar">
|
||||
{verticalCarouselData.map((item, index) => (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => handleSlideClick(index)}
|
||||
className={`relative px-4 py-2 rounded transition-all duration-300 whitespace-nowrap ${
|
||||
index === activeSlide
|
||||
? 'bg-[#6363d2]/90 text-[#f9faff]'
|
||||
: 'bg-[#6363d2]/10 text-[rgba(182,188,255,0.71)] hover:bg-[#6363d2]/15'
|
||||
}`}
|
||||
>
|
||||
<span className="font-mono font-bold text-sm tracking-[1.44px]!">
|
||||
{item.category}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop: Vertical Carousel Layout */}
|
||||
<div className="flex flex-col lg:flex-row lg:items-start">
|
||||
{/* Left Sidebar Navigation - Desktop Only */}
|
||||
<div className="hidden lg:block lg:w-72 shrink-0 lg:pt-0">
|
||||
<div className="relative space-y-6">
|
||||
{/* Sliding Rectangle Indicator */}
|
||||
<motion.div
|
||||
className="absolute left-0 top-0 w-2 h-4 bg-[#6363d2] z-10 rounded-xs"
|
||||
animate={{
|
||||
|
|
@ -96,44 +129,45 @@ export function VerticalCarouselSection() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Content Area */}
|
||||
<div className="flex-1 min-h-[600px] relative lg:-ml-8">
|
||||
{/* Right Content Area - Fixed height to prevent layout shift */}
|
||||
<div className="flex-1 h-[600px] sm:h-[650px] lg:h-[600px] relative lg:-ml-8">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activeSlide}
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -50 }}
|
||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||
className="absolute inset-0"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||
className="w-full h-full"
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-12 items-start h-full">
|
||||
<div className="flex flex-col lg:flex-row gap-6 sm:gap-8 lg:gap-12 items-start h-full">
|
||||
{/* Diagram - Above on mobile, Right Side on desktop */}
|
||||
<div className="w-full lg:flex-1 flex items-center justify-center lg:justify-start order-first lg:order-last shrink-0">
|
||||
<div className="relative w-full max-w-full sm:max-w-md lg:max-w-[600px] aspect-4/3">
|
||||
<Image
|
||||
src={verticalCarouselData[activeSlide].diagram}
|
||||
alt={verticalCarouselData[activeSlide].title}
|
||||
fill
|
||||
className="object-contain object-top"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text Content */}
|
||||
<div className="flex-1 max-w-2xl">
|
||||
<div className="flex-1 max-w-2xl order-last lg:order-first flex flex-col justify-start">
|
||||
{/* Title */}
|
||||
<h3 className="font-sans font-medium text-primary text-2xl lg:text-[34px] tracking-[-2.4px]! leading-[1.03] mb-4">
|
||||
<h3 className="font-sans font-medium text-primary text-xl sm:text-2xl lg:text-[34px] tracking-[-1px]! leading-[1.03] mb-4 sm:mb-6">
|
||||
{verticalCarouselData[activeSlide].title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<div className="font-mono text-white text-xl lg:text-lg leading-10 tracking-[-1.2px]! max-w-md">
|
||||
<div className="font-mono text-white text-sm sm:text-base lg:text-lg leading-6 sm:leading-8 lg:leading-10 tracking-[-0.8px] sm:tracking-[-1.2px]! max-w-full lg:max-w-md">
|
||||
<p className="mb-0">
|
||||
{verticalCarouselData[activeSlide].description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Diagram - Right Side */}
|
||||
<div className="flex-1 w-full flex items-start justify-center lg:justify-start pt-2">
|
||||
<Image
|
||||
src={verticalCarouselData[activeSlide].diagram}
|
||||
alt={verticalCarouselData[activeSlide].title}
|
||||
width={600}
|
||||
height={400}
|
||||
className="w-full max-w-[600px] h-auto object-contain"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
|
|
|||