landing pages

This commit is contained in:
2026-03-16 23:59:31 -05:00
parent c6931d5c5d
commit 0d7282664e
3 changed files with 340 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;700&display=swap");
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;

View File

@@ -104,6 +104,12 @@ const ShipmentFormPage = React.lazy(() =>
const GanttPage = React.lazy(() => const GanttPage = React.lazy(() =>
import("./modules/gantt/GanttPage").then((module) => ({ default: module.GanttPage })) import("./modules/gantt/GanttPage").then((module) => ({ default: module.GanttPage }))
); );
const LandingPage = React.lazy(() =>
import("./modules/landing/LandingPage").then((module) => ({ default: module.LandingPage }))
);
const DarkLandingPage = React.lazy(() =>
import("./modules/landing/LandingPage").then((module) => ({ default: module.DarkLandingPage }))
);
function RouteFallback() { function RouteFallback() {
return ( return (
@@ -119,6 +125,8 @@ function lazyElement(element: React.ReactNode) {
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: "/login", element: <LoginPage /> }, { path: "/login", element: <LoginPage /> },
{ path: "/landing", element: lazyElement(<LandingPage />) },
{ path: "/darklanding", element: lazyElement(<DarkLandingPage />) },
{ {
element: <ProtectedRoute />, element: <ProtectedRoute />,
children: [ children: [

View File

@@ -0,0 +1,331 @@
import { Link } from "react-router-dom";
type LandingVariant = "light" | "dark";
const heroMetrics = [
{ label: "On-time release rate", value: "98.2%", detail: "across 146 active jobs this week" },
{ label: "Planner response time", value: "14 min", detail: "from demand signal to released draft supply" },
{ label: "Inventory exposure", value: "$1.4M", detail: "visible by warehouse, location, and reservation" },
];
const moduleCards = [
{
eyebrow: "Commercial Control",
title: "Quotes, orders, revisions, and approvals stay connected.",
copy: "Customer commitments flow directly into execution without spreadsheet handoffs or revision blind spots.",
bullets: ["Revision comparisons", "Approval checkpoints", "Project linkage"],
},
{
eyebrow: "Production Visibility",
title: "Planning, projects, and work orders share the same reality.",
copy: "Material readiness, shortages, pegged supply, and station execution all stay visible from one operational surface.",
bullets: ["Live readiness rollups", "Reservation automation", "Capacity-ready planning"],
},
{
eyebrow: "Inventory Discipline",
title: "SKU structure, stock control, and purchasing are built for actual manufacturing.",
copy: "Family-based SKU generation, BOM-aware demand, transfer visibility, and vendor-backed replenishment keep the floor aligned.",
bullets: ["Master SKU builder", "BOM explosion", "Preferred-vendor sourcing"],
},
];
const timeline = [
{ time: "06:40", title: "Demand spike detected", detail: "Three approved sales orders added 28 assemblies to near-term demand." },
{ time: "07:05", title: "Planner drafts supply", detail: "System nets stock, open POs, and open work orders before recommending build and buy actions." },
{ time: "08:10", title: "Reservations applied", detail: "Available stock updates immediately across project, manufacturing, and purchasing views." },
{ time: "09:25", title: "Shipment package released", detail: "Packing slip PDF and label artifacts are ready from the same order thread." },
];
const proofCards = [
{ label: "Modules live", value: "10", note: "CRM, inventory, sales, purchasing, shipping, projects, manufacturing, planning, admin, branding" },
{ label: "Data domains unified", value: "1", note: "Single operational model instead of disconnected tools" },
{ label: "Container footprint", value: "1", note: "Frontend, backend, SQLite, uploads, Puppeteer PDF pipeline" },
];
const spotlightBoard = [
{ title: "Revenue committed", value: "$842K", change: "+18% vs last month", tone: "brand" },
{ title: "Shortage lines", value: "12", change: "-31% after planner actions", tone: "accent" },
{ title: "Work orders ready", value: "27", change: "6 waiting on materials", tone: "neutral" },
{ title: "Shipments due today", value: "9", change: "4 already packed", tone: "brand" },
];
function LandingExperience({ variant }: { variant: LandingVariant }) {
const isDark = variant === "dark";
const variantLabel = isDark ? "Dark landing" : "Landing";
const altRoute = isDark ? "/landing" : "/darklanding";
const altLabel = isDark ? "View light version" : "View dark version";
return (
<div className={isDark ? "dark" : undefined}>
<div id="top" className="min-h-screen bg-page text-text">
<div className="relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(var(--color-brand),0.34),transparent_28%),radial-gradient(circle_at_78%_18%,rgba(var(--color-accent),0.28),transparent_24%),linear-gradient(180deg,rgba(255,255,255,0.08),transparent_45%)] dark:bg-[radial-gradient(circle_at_top_left,rgba(var(--color-brand),0.22),transparent_30%),radial-gradient(circle_at_78%_18%,rgba(var(--color-accent),0.18),transparent_26%),linear-gradient(180deg,rgba(255,255,255,0.02),transparent_45%)]" />
<div className="absolute left-[-12%] top-24 h-72 w-72 rounded-full bg-[rgb(var(--color-brand)/0.16)] blur-3xl dark:bg-[rgb(var(--color-brand)/0.22)]" />
<div className="absolute right-[-8%] top-40 h-96 w-96 rounded-full bg-[rgb(var(--color-accent)/0.14)] blur-3xl dark:bg-[rgb(var(--color-accent)/0.18)]" />
<div className="relative mx-auto flex w-full max-w-7xl flex-col gap-12 px-6 pb-20 pt-8 lg:px-8">
<header className="flex flex-col gap-4 rounded-[28px] border border-line/60 bg-surface/70 px-5 py-4 shadow-[0_30px_80px_rgba(15,23,42,0.12)] backdrop-blur xl:flex-row xl:items-center xl:justify-between dark:shadow-[0_30px_80px_rgba(2,6,23,0.45)]">
<div className="flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[linear-gradient(135deg,rgb(var(--color-brand)),rgb(var(--color-accent)))] text-lg font-black tracking-[0.18em] text-white">
CX
</div>
<div>
<div className="text-xs font-semibold uppercase tracking-[0.34em] text-muted">CODEXIUM</div>
<div className="text-sm text-muted">Manufacturing resource planning without the ERP drag.</div>
</div>
</div>
<div className="flex flex-wrap items-center gap-3">
<span className="rounded-2xl border border-line/70 px-4 py-2 text-sm font-semibold text-text">
{variantLabel}
</span>
<Link to={altRoute} className="rounded-2xl border border-line/70 px-4 py-2 text-sm font-semibold text-text transition hover:bg-page/70">
{altLabel}
</Link>
<Link to="/login" className="rounded-2xl border border-line/70 px-4 py-2 text-sm font-semibold text-text transition hover:bg-page/70">
Open app
</Link>
<a
href="#contact"
className="rounded-2xl bg-[linear-gradient(135deg,rgb(var(--color-brand)),rgb(var(--color-accent)))] px-4 py-2 text-sm font-semibold text-white shadow-[0_18px_40px_rgba(24,90,219,0.28)]"
>
Book a walkthrough
</a>
</div>
</header>
<section className="grid gap-10 lg:grid-cols-[1.05fr_0.95fr] lg:items-center">
<div>
<div className="inline-flex items-center gap-2 rounded-full border border-line/70 bg-surface/75 px-4 py-2 text-xs font-semibold uppercase tracking-[0.24em] text-muted backdrop-blur">
Built for discrete manufacturing teams
</div>
<h1 className="mt-6 max-w-4xl font-['Space_Grotesk','Manrope',sans-serif] text-5xl font-black leading-[0.95] tracking-[-0.05em] text-text sm:text-6xl xl:text-7xl">
A sharper MRP for operators who need flow, not ERP theater.
</h1>
<p className="mt-6 max-w-2xl text-lg leading-8 text-muted">
CODEXIUM connects commercial demand, inventory truth, project execution, purchasing, shipping, and manufacturing control in one system designed to move work across the floor.
</p>
<div className="mt-8 grid gap-3 sm:grid-cols-3">
{heroMetrics.map((metric) => (
<article key={metric.label} className="rounded-[24px] border border-line/70 bg-surface/80 p-4 shadow-[0_20px_50px_rgba(15,23,42,0.1)] backdrop-blur dark:shadow-[0_20px_50px_rgba(2,6,23,0.4)]">
<div className="text-xs font-semibold uppercase tracking-[0.22em] text-muted">{metric.label}</div>
<div className="mt-3 text-3xl font-black tracking-[-0.04em] text-text">{metric.value}</div>
<div className="mt-2 text-sm text-muted">{metric.detail}</div>
</article>
))}
</div>
</div>
<div className="relative">
<div className="absolute -left-8 top-10 hidden h-20 w-20 rounded-[28px] border border-white/20 bg-white/10 blur-sm lg:block" />
<div className="rounded-[32px] border border-line/70 bg-[linear-gradient(160deg,rgba(255,255,255,0.68),rgba(255,255,255,0.24))] p-5 shadow-[0_35px_90px_rgba(15,23,42,0.18)] backdrop-blur dark:bg-[linear-gradient(160deg,rgba(15,23,42,0.82),rgba(15,23,42,0.48))] dark:shadow-[0_35px_90px_rgba(2,6,23,0.55)]">
<div className="rounded-[28px] border border-line/70 bg-page/85 p-5">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Generated executive board</div>
<div className="mt-2 text-2xl font-black tracking-[-0.04em] text-text">This week in operations</div>
</div>
<div className="rounded-full border border-emerald-400/35 bg-emerald-500/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-emerald-700 dark:text-emerald-300">
Live-ready demo data
</div>
</div>
<div className="mt-5 grid gap-3 sm:grid-cols-2">
{spotlightBoard.map((card) => (
<article
key={card.title}
className={`rounded-[24px] border p-4 ${
card.tone === "brand"
? "border-[rgb(var(--color-brand)/0.25)] bg-[rgb(var(--color-brand)/0.08)]"
: card.tone === "accent"
? "border-[rgb(var(--color-accent)/0.25)] bg-[rgb(var(--color-accent)/0.08)]"
: "border-line/70 bg-surface/70"
}`}
>
<div className="text-sm font-semibold text-muted">{card.title}</div>
<div className="mt-3 text-4xl font-black tracking-[-0.05em] text-text">{card.value}</div>
<div className="mt-2 text-sm text-muted">{card.change}</div>
</article>
))}
</div>
<div className="mt-5 grid gap-3 xl:grid-cols-[1.3fr_0.7fr]">
<div className="rounded-[24px] border border-line/70 bg-surface/80 p-4">
<div className="flex items-center justify-between gap-3">
<div className="text-sm font-semibold text-text">Project and manufacturing pulse</div>
<div className="text-xs uppercase tracking-[0.18em] text-muted">Generated snapshot</div>
</div>
<div className="mt-4 space-y-3">
{[
{ label: "Falcon enclosure program", progress: 86, status: "Ready for final assembly" },
{ label: "Orion service kit launch", progress: 61, status: "Waiting on one purchased line" },
{ label: "Atlas retrofit release", progress: 43, status: "Routing review in progress" },
].map((row) => (
<div key={row.label} className="rounded-[20px] border border-line/60 bg-page/75 p-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="font-semibold text-text">{row.label}</div>
<div className="text-xs uppercase tracking-[0.18em] text-muted">{row.progress}%</div>
</div>
<div className="mt-3 h-2 overflow-hidden rounded-full bg-line/70">
<div
className="h-full rounded-full bg-[linear-gradient(90deg,rgb(var(--color-brand)),rgb(var(--color-accent)))]"
style={{ width: `${row.progress}%` }}
/>
</div>
<div className="mt-2 text-sm text-muted">{row.status}</div>
</div>
))}
</div>
</div>
<div className="rounded-[24px] border border-line/70 bg-surface/80 p-4">
<div className="text-sm font-semibold text-text">Supply signals</div>
<div className="mt-4 space-y-3">
{[
{ sku: "AXL-4472", action: "Build", qty: "18 EA" },
{ sku: "KIT-2208", action: "Buy", qty: "240 EA" },
{ sku: "CAB-9031", action: "Transfer", qty: "64 EA" },
].map((signal) => (
<div key={signal.sku} className="rounded-[18px] border border-line/60 bg-page/75 px-3 py-3">
<div className="flex items-center justify-between gap-3">
<div>
<div className="font-semibold text-text">{signal.sku}</div>
<div className="mt-1 text-xs uppercase tracking-[0.18em] text-muted">{signal.action} recommendation</div>
</div>
<div className="text-sm font-semibold text-text">{signal.qty}</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section className="grid gap-4 rounded-[32px] border border-line/70 bg-surface/70 p-6 shadow-[0_25px_70px_rgba(15,23,42,0.14)] backdrop-blur dark:shadow-[0_25px_70px_rgba(2,6,23,0.45)] lg:grid-cols-3">
{proofCards.map((card) => (
<article key={card.label} className="rounded-[24px] border border-line/60 bg-page/70 p-4">
<div className="text-xs font-semibold uppercase tracking-[0.22em] text-muted">{card.label}</div>
<div className="mt-3 text-5xl font-black tracking-[-0.06em] text-text">{card.value}</div>
<div className="mt-2 text-sm text-muted">{card.note}</div>
</article>
))}
</section>
</div>
</div>
<main className="mx-auto flex w-full max-w-7xl flex-col gap-8 px-6 pb-24 lg:px-8">
<section className="grid gap-6 lg:grid-cols-3">
{moduleCards.map((card) => (
<article key={card.title} className="rounded-[28px] border border-line/70 bg-surface/85 p-6 shadow-[0_25px_65px_rgba(15,23,42,0.1)] dark:shadow-[0_25px_65px_rgba(2,6,23,0.38)]">
<div className="text-xs font-semibold uppercase tracking-[0.22em] text-muted">{card.eyebrow}</div>
<h2 className="mt-4 font-['Space_Grotesk','Manrope',sans-serif] text-2xl font-bold tracking-[-0.04em] text-text">{card.title}</h2>
<p className="mt-4 text-sm leading-7 text-muted">{card.copy}</p>
<div className="mt-6 flex flex-wrap gap-2">
{card.bullets.map((bullet) => (
<span key={bullet} className="rounded-full border border-line/70 bg-page/70 px-3 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-text">
{bullet}
</span>
))}
</div>
</article>
))}
</section>
<section className="grid gap-8 rounded-[32px] border border-line/70 bg-surface/80 p-6 shadow-[0_30px_75px_rgba(15,23,42,0.12)] dark:shadow-[0_30px_75px_rgba(2,6,23,0.42)] lg:grid-cols-[0.9fr_1.1fr]">
<div>
<div className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">How it moves</div>
<h2 className="mt-4 font-['Space_Grotesk','Manrope',sans-serif] text-4xl font-black tracking-[-0.05em] text-text">
One demand signal. One operating picture.
</h2>
<p className="mt-4 max-w-xl text-base leading-8 text-muted">
Most systems split quoting, planning, purchasing, inventory, shipping, and production into separate stories. CODEXIUM treats them as one chain, so each step inherits context instead of creating reconciliation work.
</p>
</div>
<div className="space-y-4">
{timeline.map((step) => (
<article key={step.time} className="grid gap-4 rounded-[24px] border border-line/70 bg-page/75 p-4 md:grid-cols-[92px_1fr] md:items-start">
<div className="rounded-[18px] bg-[linear-gradient(135deg,rgb(var(--color-brand)/0.16),rgb(var(--color-accent)/0.18))] px-4 py-3 text-center">
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Time</div>
<div className="mt-1 text-2xl font-black tracking-[-0.04em] text-text">{step.time}</div>
</div>
<div>
<div className="text-lg font-bold text-text">{step.title}</div>
<div className="mt-2 text-sm leading-7 text-muted">{step.detail}</div>
</div>
</article>
))}
</div>
</section>
<section className="grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
<article className="rounded-[32px] border border-line/70 bg-[linear-gradient(135deg,rgb(var(--color-brand)/0.1),rgb(var(--color-accent)/0.08),rgb(var(--color-surface)))] p-6 shadow-[0_30px_75px_rgba(15,23,42,0.12)] dark:shadow-[0_30px_75px_rgba(2,6,23,0.42)]">
<div className="text-xs font-semibold uppercase tracking-[0.22em] text-muted">Who this is for</div>
<h2 className="mt-4 font-['Space_Grotesk','Manrope',sans-serif] text-4xl font-black tracking-[-0.05em] text-text">
Teams graduating from spreadsheets, generic ERPs, or disconnected point tools.
</h2>
<div className="mt-6 grid gap-4 md:grid-cols-2">
{[
"Contract manufacturers that need real-time supply readiness before promising dates.",
"OEM operations teams that need projects, BOMs, purchasing, and work orders linked end to end.",
"Growing production shops that want branding, PDFs, approvals, and auditability without enterprise bloat.",
"Leaders who want a system operators will actually open all day, not just during month-end cleanup.",
].map((statement) => (
<div key={statement} className="rounded-[22px] border border-line/60 bg-page/70 p-4 text-sm leading-7 text-text">
{statement}
</div>
))}
</div>
</article>
<article id="contact" className="rounded-[32px] border border-line/70 bg-surface/85 p-6 shadow-[0_30px_75px_rgba(15,23,42,0.12)] dark:shadow-[0_30px_75px_rgba(2,6,23,0.42)]">
<div className="text-xs font-semibold uppercase tracking-[0.22em] text-muted">Next step</div>
<h2 className="mt-4 font-['Space_Grotesk','Manrope',sans-serif] text-3xl font-black tracking-[-0.05em] text-text">
Pitch the platform with a page that already looks production-grade.
</h2>
<p className="mt-4 text-sm leading-7 text-muted">
Use this route as a commercial front door, demo backdrop, or customer-facing preview while the core app remains focused on execution.
</p>
<div className="mt-6 space-y-3 rounded-[24px] border border-line/70 bg-page/75 p-4 text-sm text-muted">
<div className="flex items-center justify-between gap-3">
<span>Public route</span>
<span className="font-semibold text-text">{isDark ? "/darklanding" : "/landing"}</span>
</div>
<div className="flex items-center justify-between gap-3">
<span>Core positioning</span>
<span className="font-semibold text-text">Modern manufacturing MRP</span>
</div>
<div className="flex items-center justify-between gap-3">
<span>Demo style</span>
<span className="font-semibold text-text">Generated commercial data</span>
</div>
</div>
<div className="mt-6 flex flex-wrap gap-3">
<Link
to="/login"
className="rounded-2xl bg-[linear-gradient(135deg,rgb(var(--color-brand)),rgb(var(--color-accent)))] px-4 py-3 text-sm font-semibold text-white shadow-[0_18px_40px_rgba(24,90,219,0.28)]"
>
Enter the app
</Link>
<a href="#top" className="rounded-2xl border border-line/70 px-4 py-3 text-sm font-semibold text-text transition hover:bg-page/70">
Back to top
</a>
</div>
</article>
</section>
</main>
</div>
</div>
);
}
export function LandingPage() {
return <LandingExperience variant="light" />;
}
export function DarkLandingPage() {
return <LandingExperience variant="dark" />;
}