-
-
60-second onboarding
-
+
+
+
+
60-second onboarding
+
One line of middleware. No agents, no sidecars, no config files to maintain.
-
-
$ npm install liveboard-sdk
+
+
- {/* Distributed tracing — small with mock flame graph */}
+ {/* Endpoints / health */}
-
-
-
-
-
-
Distributed tracing
-
- Trace IDs propagate across every service automatically. See exactly where time went.
+
+
+
+
Per-endpoint health
+
+ p50 / p95 / p99, error rate and a rolling health score for every route you serve.
-
- {FLAME_SPANS.map((span, i) => (
-
-
+
+ {[
+ { r: "/checkout", p: 92, c: "#22C55E" },
+ { r: "/orders", p: 68, c: "#F59E0B" },
+ { r: "/search", p: 34, c: "#EF4444" },
+ ].map((e) => (
+
))}
@@ -75,57 +83,61 @@ export function FeatureGrid() {
- {/* AI incident summaries — featured, spans 2 rows on md+ */}
+ {/* AI incident summaries — featured, tall */}
-
-
-
-
AI incident summaries
-
- When error rate or p99 latency breaks 3 standard deviations from baseline,
- an LLM reads the surrounding traffic and writes the postmortem before you do.
+
+
+
+
+
+ AI
+
+
+
AI incident summaries
+
+ When error rate or p99 latency breaks 3σ from baseline, an LLM reads the surrounding
+ traffic and writes the postmortem before you do.
-
+
- Critical
- 2m ago
+
+ Critical
+
+ 2m ago
-
- p99 latency spike on /api/checkout
+
+ p99 latency spike on /api/checkout
-
- Latency jumped 6.4x starting 14:02 UTC, correlated with a downstream timeout
- from the payments provider. 312 requests affected. Recommend adding a circuit
- breaker around the payment client.
+
+ Latency jumped 6.4× starting 14:02 UTC, correlated with a downstream timeout from the
+ payments provider. 312 requests affected. Recommend a circuit breaker around the
+ payment client.
-
- Resolved in 4m
+
+ Resolved in 4m
- {/* User-centric — small */}
+ {/* User-centric */}
-
-
-
-
-
-
-
User-centric observability
-
+
+
+
+
User-centric observability
+
Every event carries a user ID. Know exactly who an incident hits, not just which route.
{TOP_USERS.map((u) => (
-
- {u.id}
- {u.reqs} req
+
+ {u.id}
+ {u.reqs} req
))}
@@ -133,22 +145,19 @@ export function FeatureGrid() {
- {/* Status page — small */}
+ {/* Alerts + status page combined-ish */}
-
-
-
-
-
-
-
Auto-generated status page
-
- A public, branded status page ships for free — fed by the same data, zero extra setup.
+
+
+
+
Alerts + status page
+
+ Route anomalies to Slack, PagerDuty or a webhook — and ship a public status page for free.
-
-
99.97% uptime
-
48 days
+
+ 99.97% uptime
+ 44 days
{UPTIME_DAYS.map((color, i) => (
@@ -157,7 +166,7 @@ export function FeatureGrid() {
className="h-4 flex-1 rounded-[1px]"
style={{
backgroundColor: color === "green" ? "#22C55E" : color === "yellow" ? "#F59E0B" : "#EF4444",
- opacity: color === "green" ? 0.5 : 0.9,
+ opacity: color === "green" ? 0.45 : 0.9,
}}
/>
))}
diff --git a/components/landing/footer.tsx b/components/landing/footer.tsx
index f6989e4..6c33896 100644
--- a/components/landing/footer.tsx
+++ b/components/landing/footer.tsx
@@ -36,8 +36,8 @@ const COLUMNS: { title: string; links: FooterLink[] }[] = [
export function LandingFooter() {
return (
-
-
+
+
diff --git a/components/landing/hero.tsx b/components/landing/hero.tsx
index 300a89b..f879489 100644
--- a/components/landing/hero.tsx
+++ b/components/landing/hero.tsx
@@ -1,94 +1,139 @@
+"use client";
+
+import { useRef } from "react";
import Link from "next/link";
-import { ArrowUpRight, Play } from "lucide-react";
-import { Reveal } from "@/components/landing/reveal";
-import { NetworkGraph } from "@/components/landing/network-graph";
+import { motion, useScroll, useTransform, type Variants } from "framer-motion";
+import { ArrowRight, Sparkles } from "lucide-react";
+import { BrowserFrame } from "@/components/landing/browser-frame";
+import { CountUp } from "@/components/landing/count-up";
+import { RadarField } from "@/components/landing/radar-field";
+
+const STATS = [
+ { node:
, label: "requests observed" },
+ { node:
, label: "to first live data" },
+ { node:
, label: "pipeline uptime" },
+];
+
+const container: Variants = {
+ hidden: {},
+ show: { transition: { staggerChildren: 0.09, delayChildren: 0.05 } },
+};
+
+const item: Variants = {
+ hidden: { opacity: 0, y: 20, filter: "blur(8px)" },
+ show: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: 0.7, ease: [0.21, 0.47, 0.32, 0.98] } },
+};
export function Hero() {
- return (
-
- {/* Misty ambient glow — built from the Liveboard mark's own palette */}
-
-
+ const shotRef = useRef(null);
+ const { scrollYProgress } = useScroll({ target: shotRef, offset: ["start 0.85", "end start"] });
+ // Crisp and near-flat at rest; only a whisper of depth + parallax drift.
+ const shotY = useTransform(scrollYProgress, [0, 1], [0, -70]);
+ const shotRotate = useTransform(scrollYProgress, [0, 0.5], [3, 0]);
+ const shotScale = useTransform(scrollYProgress, [0, 0.5], [0.985, 1]);
-
+ return (
+
+ {/* Radar backdrop */}
+
-
-
+
+
-
+
+
+ New
+
+ AI incident summaries are live
+
+
+
+
+
+ Know the moment
+
+
+ your API
+ breaks
+ .
+
+
+
+
+ Real-time observability for your APIs. Distributed traces, live error tracking,
+ and AI-written incident postmortems — from a single line of middleware.
+
+
-
- Real-time API observability
-
+ Start for free
+
+
+ $
+ npm i liveboard-sdk
+
+
-
- Know the moment
-
- your API breaks.
-
-
-
- One line of middleware. Real-time traces, error tracking, and AI incident summaries.
-
-
-
-
- Open dashboard
-
-
-
- See how it works
-
+
+ Open source · Self-hostable · Free to start
+
+
- {/* Falling light rays */}
-
-
-
-
-
+ {/* Real product shot — near-flat, subtle scroll parallax */}
+
+ {/* subtle monochrome ambient behind the frame */}
+
+
+
-
+
+
+
-
-
-
-
-
-
-
01 / 03 · Scroll down
-
-
-
-
Service map
-
-
-
-
-
-
+ {/* Stat band with count-up */}
+
+ {STATS.map((s, i) => (
+
+ ))}
+
);
}
diff --git a/components/landing/how-it-works.tsx b/components/landing/how-it-works.tsx
index cd1a13b..b7d67a3 100644
--- a/components/landing/how-it-works.tsx
+++ b/components/landing/how-it-works.tsx
@@ -1,50 +1,65 @@
import { Reveal } from "@/components/landing/reveal";
import { SpotlightCard } from "@/components/landing/spotlight-card";
+import { InstallSnippet, MiddlewareSnippet, DashboardUrlSnippet } from "@/components/landing/code-snippet";
const STEPS = [
{
n: "01",
+ lang: "bash",
title: "Install the SDK",
body: "Pick your language. The JS SDK supports Express and Fastify; the Python SDK supports FastAPI, Django, and Flask.",
- code: "npm install liveboard-sdk",
+ code:
,
},
{
n: "02",
+ lang: "javascript",
title: "Add one line",
body: "Wire the middleware with your project's API key. No restart-the-world config, no separate collector to deploy.",
- code: "app.use(liveboard.middleware({ apiKey }))",
+ code:
,
},
{
n: "03",
+ lang: "output",
title: "Watch it live",
body: "Requests start streaming into your dashboard within seconds. Anomalies get flagged and summarized automatically.",
- code: "→ liveboard.dev/overview",
+ code:
,
},
];
export function HowItWorks() {
return (
-
-
+
+
- Setup
-
+
+
+ Setup
+
+
From zero to live data in under a minute
-
+
{STEPS.map((step, i) => (
-
-
-
+
+
+
{step.n}
-
{step.title}
-
{step.body}
-
-
{step.code}
+
{step.title}
+
{step.body}
+
+
+
+
+
+
+
+
{step.lang}
+
+
{step.code}
diff --git a/components/landing/insights-bento.tsx b/components/landing/insights-bento.tsx
deleted file mode 100644
index d01f204..0000000
--- a/components/landing/insights-bento.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { Sparkline } from "@/components/charts/sparkline";
-import { Reveal } from "@/components/landing/reveal";
-import { SpotlightCard } from "@/components/landing/spotlight-card";
-
-const UPTIME_TREND = [99.91, 99.93, 99.95, 99.9, 99.96, 99.97, 99.94, 99.97, 99.98, 99.97];
-
-const LATENCY_BARS = [
- { route: "/checkout", ms: 86, color: "#3B82F6" },
- { route: "/orders", ms: 41, color: "#06B6D4" },
- { route: "/users/:id", ms: 18, color: "#A855F7" },
- { route: "/auth", ms: 12, color: "#F59E0B" },
- { route: "/cart/:id", ms: 24, color: "#F43F5E" },
-];
-
-const TOP_ENDPOINTS = [
- { route: "/api/checkout", pct: 92, color: "#3B82F6" },
- { route: "/api/orders", pct: 74, color: "#06B6D4" },
- { route: "/api/users/:id", pct: 58, color: "#A855F7" },
- { route: "/api/cart/:id", pct: 35, color: "#F59E0B" },
- { route: "/api/auth", pct: 22, color: "#F43F5E" },
-];
-
-function jitter(value: number, spread: number): number {
- return Math.max(0, value + (Math.random() - 0.5) * spread);
-}
-
-export function InsightsBento() {
- const [requests, setRequests] = useState(8204);
- const [errorRate, setErrorRate] = useState(0.42);
-
- useEffect(() => {
- const interval = setInterval(() => {
- setRequests((r) => Math.round(jitter(r, 600)));
- setErrorRate((e) => Math.round(jitter(e, 0.3) * 100) / 100);
- }, 1800);
- return () => clearInterval(interval);
- }, []);
-
- return (
-
-
- Insights
-
- Meet your traffic copilot
-
-
- Real numbers from a live Liveboard project — every panel below updates on its own,
- the same way your dashboard would.
-
-
-
-
- {/* Uptime — tall panel */}
-
-
-
Uptime
-
-
-
-
-
-
-
- 99.97%
- 30 days
-
-
-
-
-
Successful requests
-
-
-
-
-
-
-
- {/* Latency by endpoint — wide panel */}
-
-
-
Latency by endpoint
-
- {LATENCY_BARS.map((bar) => (
-
-
{bar.ms}ms
-
-
{bar.route}
-
- ))}
-
-
-
-
- {/* Live counters */}
-
-
-
-
Requests / min
-
- {requests.toLocaleString()}
-
-
-
-
-
Error rate
-
- {errorRate.toFixed(2)}%
- within budget
-
-
-
-
-
- {/* Top endpoints by volume */}
-
-
-
Top endpoints by volume
-
- {TOP_ENDPOINTS.map((ep) => (
-
- ))}
-
-
-
-
-
- );
-}
diff --git a/components/landing/integrations-strip.tsx b/components/landing/integrations-strip.tsx
index 6018cda..f41fcf0 100644
--- a/components/landing/integrations-strip.tsx
+++ b/components/landing/integrations-strip.tsx
@@ -1,24 +1,31 @@
import { Reveal } from "@/components/landing/reveal";
+import { TECH_LOGOS, TechMark } from "@/components/landing/tech-logos";
-const STACKS = ["Express", "Fastify", "FastAPI", "Django", "Flask", "Node.js", "Python"];
-const LOOP = [...STACKS, ...STACKS];
+const LOOP = [...TECH_LOGOS, ...TECH_LOGOS];
export function IntegrationsStrip() {
return (
-
-
- Instruments your stack
-
-
-
- {LOOP.map((name, i) => (
-
- {name}
-
- ))}
+
+
+
+ Instruments the stack
+ you already run
+
+
+
+ {LOOP.map((icon, i) => (
+
+
+ {icon.label}
+
+ ))}
+
diff --git a/components/landing/navbar.tsx b/components/landing/navbar.tsx
index 9069b34..47b6cb9 100644
--- a/components/landing/navbar.tsx
+++ b/components/landing/navbar.tsx
@@ -1,78 +1,88 @@
"use client";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import Link from "next/link";
-import { ArrowUpRight, Menu, User, X } from "lucide-react";
+import { ArrowRight, Menu, X } from "lucide-react";
import { LiveboardIcon } from "@/components/logo";
import { GithubIcon } from "@/components/landing/github-icon";
const LINKS = [
+ { label: "Tracing", href: "#tracing" },
+ { label: "Endpoints", href: "#endpoints" },
+ { label: "Alerts", href: "#alerts" },
{ label: "Features", href: "#features" },
- { label: "Insights", href: "#insights" },
- { label: "How it works", href: "#how-it-works" },
+ { label: "Setup", href: "#how-it-works" },
];
const GITHUB_URL = "https://github.com/ryzrr/liveboard";
export function LandingNavbar() {
const [open, setOpen] = useState(false);
+ const [scrolled, setScrolled] = useState(false);
+
+ useEffect(() => {
+ const onScroll = () => setScrolled(window.scrollY > 12);
+ onScroll();
+ window.addEventListener("scroll", onScroll, { passive: true });
+ return () => window.removeEventListener("scroll", onScroll);
+ }, []);
return (
-
-
-
-
-
-
-
+
+
+
+
+
Liveboard
-
-
- {LINKS.map((link) => (
-
- {link.label}
-
- ))}
-
+
+ {LINKS.map((link) => (
+
+ {link.label}
+
+ ))}
+
-
- Status
-
-
+
+
+ Status
+
+
+ Get started
+
+
-
-
-
-
- Get started
-
-
setOpen((v) => !v)}
- className="p-2 text-[#888] lg:hidden"
+ className="p-2 text-[#9a9aa4] lg:hidden"
aria-label="Toggle menu"
>
{open ? : }
@@ -80,30 +90,40 @@ export function LandingNavbar() {
{open && (
-
-
+
+
{LINKS.map((link) => (
setOpen(false)}
- className="text-sm text-[#888] hover:text-[#F5F5F5] transition-colors"
+ className="rounded-lg px-2 py-2.5 text-sm text-[#c4c4cc] transition-colors hover:bg-white/5 hover:text-white"
>
{link.label}
))}
-
setOpen(false)} className="text-sm text-[#888] hover:text-[#F5F5F5] transition-colors">
+
setOpen(false)}
+ className="rounded-lg px-2 py-2.5 text-sm text-[#c4c4cc] hover:bg-white/5 hover:text-white"
+ >
Status page
-
+
GitHub
setOpen(false)}
+ className="mt-2 flex items-center justify-center gap-1.5 rounded-full bg-white py-2.5 text-[13px] font-medium text-[#0A0A0A]"
>
Get started
-
+
diff --git a/components/landing/radar-field.tsx b/components/landing/radar-field.tsx
new file mode 100644
index 0000000..a7d36f9
--- /dev/null
+++ b/components/landing/radar-field.tsx
@@ -0,0 +1,65 @@
+/* Distinctive, on-theme hero backdrop: a monochrome radar/sonar field.
+ Static concentric rings + crosshair, a slow sweep arm, and expanding
+ "ping" rings — evoking detection of the moment your API breaks.
+ Pure black, no gradients, fades out toward the edges. */
+
+const RINGS = [80, 150, 230, 320, 420, 540];
+
+export function RadarField() {
+ return (
+
+
+ {/* Static concentric rings + crosshair */}
+
+ {RINGS.map((r, i) => (
+
+ ))}
+
+
+ {/* dashed range ring for a scope feel */}
+
+ {/* tick dots where cardinal axes cross a mid ring */}
+ {[
+ [560, 240],
+ [880, 560],
+ [560, 880],
+ [240, 560],
+ ].map(([cx, cy]) => (
+
+ ))}
+ {/* center emitter */}
+
+
+
+ {/* Slow sweep arm */}
+
+
+ {/* Expanding ping rings */}
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/landing/scroll-progress.tsx b/components/landing/scroll-progress.tsx
new file mode 100644
index 0000000..8f9384d
--- /dev/null
+++ b/components/landing/scroll-progress.tsx
@@ -0,0 +1,15 @@
+"use client";
+
+import { motion, useScroll, useSpring } from "framer-motion";
+
+export function ScrollProgress() {
+ const { scrollYProgress } = useScroll();
+ const scaleX = useSpring(scrollYProgress, { stiffness: 120, damping: 30, mass: 0.4 });
+
+ return (
+
+ );
+}
diff --git a/components/landing/showcase-image.tsx b/components/landing/showcase-image.tsx
new file mode 100644
index 0000000..ab0ceb6
--- /dev/null
+++ b/components/landing/showcase-image.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import { useRef } from "react";
+import { motion, useScroll, useTransform } from "framer-motion";
+import { BrowserFrame } from "@/components/landing/browser-frame";
+
+interface ShowcaseImageProps {
+ image: { src: string; alt: string; url: string; width: number; height: number; live?: boolean };
+ reversed?: boolean;
+ glow?: string;
+}
+
+export function ShowcaseImage({ image, reversed, glow = "rgba(255,255,255,0.05)" }: ShowcaseImageProps) {
+ const ref = useRef
(null);
+ const { scrollYProgress } = useScroll({
+ target: ref,
+ offset: ["start end", "end start"],
+ });
+
+ // Gentle parallax rise + settle of tilt as the panel scrolls through view.
+ const y = useTransform(scrollYProgress, [0, 1], [56, -56]);
+ const rotate = useTransform(scrollYProgress, [0, 0.5, 1], [reversed ? 5 : -5, 0, reversed ? -3 : 3]);
+
+ return (
+
+
+
+ {/* Moving shine */}
+
+
+
+
+ );
+}
diff --git a/components/landing/showcase.tsx b/components/landing/showcase.tsx
new file mode 100644
index 0000000..0227a53
--- /dev/null
+++ b/components/landing/showcase.tsx
@@ -0,0 +1,53 @@
+import { Check } from "lucide-react";
+import { Reveal } from "@/components/landing/reveal";
+import { ShowcaseImage } from "@/components/landing/showcase-image";
+
+interface ShowcaseProps {
+ id?: string;
+ index?: string;
+ eyebrow: string;
+ title: string;
+ body: string;
+ points: string[];
+ image: { src: string; alt: string; url: string; width: number; height: number; live?: boolean };
+ reversed?: boolean;
+ glow?: string;
+}
+
+export function Showcase({ id, index, eyebrow, title, body, points, image, reversed, glow = "rgba(255,255,255,0.05)" }: ShowcaseProps) {
+ return (
+
+ *:first-child]:order-2" : ""}`}>
+ {/* Copy */}
+
+
+ {index && (
+ {index}
+ )}
+
+
+ {eyebrow}
+
+
+
+ {title}
+
+ {body}
+
+ {points.map((p) => (
+
+
+
+
+ {p}
+
+ ))}
+
+
+
+ {/* Screenshot */}
+
+
+
+ );
+}
diff --git a/components/landing/spotlight-card.tsx b/components/landing/spotlight-card.tsx
index cabf63f..ad93383 100644
--- a/components/landing/spotlight-card.tsx
+++ b/components/landing/spotlight-card.tsx
@@ -32,7 +32,7 @@ export function SpotlightCard({ children, className }: SpotlightCardProps) {
className="pointer-events-none absolute inset-0 z-10 rounded-[inherit] opacity-0 transition-opacity duration-300 group-hover:opacity-100"
style={{
background:
- "radial-gradient(360px circle at var(--x, 50%) var(--y, 50%), rgba(55,138,221,0.12), transparent 70%)",
+ "radial-gradient(400px circle at var(--x, 50%) var(--y, 50%), rgba(255,255,255,0.06), transparent 65%)",
}}
/>
{children}
diff --git a/components/landing/tech-logos.tsx b/components/landing/tech-logos.tsx
new file mode 100644
index 0000000..04a6a11
--- /dev/null
+++ b/components/landing/tech-logos.tsx
@@ -0,0 +1,12 @@
+// AUTO-GENERATED brand marks (monochrome) from simple-icons. Rendered in currentColor.
+export interface TechLogo { key: string; label: string; viewBox: string; d: string; }
+
+export const TECH_LOGOS: TechLogo[] = [{"key":"python","label":"Python","viewBox":"0 0 24 24","d":"M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"},{"key":"nodedotjs","label":"Node.js","viewBox":"0 0 24 24","d":"M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"},{"key":"nextdotjs","label":"Next.js","viewBox":"0 0 24 24","d":"M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z"},{"key":"express","label":"Express","viewBox":"0 0 24 24","d":"M24 18.588a1.529 1.529 0 01-1.895-.72l-3.45-4.771-.5-.667-4.003 5.444a1.466 1.466 0 01-1.802.708l5.158-6.92-4.798-6.251a1.595 1.595 0 011.9.666l3.576 4.83 3.596-4.81a1.435 1.435 0 011.788-.668L21.708 7.9l-2.522 3.283a.666.666 0 000 .994l4.804 6.412zM.002 11.576l.42-2.075c1.154-4.103 5.858-5.81 9.094-3.27 1.895 1.489 2.368 3.597 2.275 5.973H1.116C.943 16.447 4.005 19.009 7.92 17.7a4.078 4.078 0 002.582-2.876c.207-.666.548-.78 1.174-.588a5.417 5.417 0 01-2.589 3.957 6.272 6.272 0 01-7.306-.933 6.575 6.575 0 01-1.64-3.858c0-.235-.08-.455-.134-.666A88.33 88.33 0 010 11.577zm1.127-.286h9.654c-.06-3.076-2.001-5.258-4.59-5.278-2.882-.04-4.944 2.094-5.071 5.264z"},{"key":"fastify","label":"Fastify","viewBox":"0 0 24 24","d":"M23.245 6.49L24 4.533l-.031-.121-7.473 1.967c.797-1.153.523-2.078.523-2.078s-2.387 1.524-4.193 1.485c-1.804-.04-2.387-.52-5.155.362-2.768.882-3.551 3.59-4.351 4.173-.804.583-3.32 2.477-3.32 2.477l.006.034 2.27-.724s-.622.585-1.945 2.37l-.062-.057.002.011s1.064 1.626 2.107 1.324a2.14 2.14 0 0 0 .353-.147c.419.234.967.463 1.572.525 0 0-.41-.475-.752-1.017l.238-.154.865.318-.096-.812c.003-.003.006-.003.008-.006l.849.311-.105-.738a5.65 5.65 0 0 1 .322-.158l.885-3.345 3.662-2.497-.291.733c-.741 1.826-2.135 2.256-2.135 2.256l-.582.22c-.433.512-.614.637-.764 2.353.348-.088.682-.107.984-.028 1.564.421 2.107 2.307 1.685 2.827-.104.13-.356.354-.673.617H7.77l-.008.514-.065.051h-.645l-.009.504-.17.127c-.607.011-1.373-.518-1.373-.518 0 .481.401 1.225.401 1.225l.07-.034-.061.045s1.625 1.083 2.646.681c.91-.356 3.263-2.213 5.296-3.093l6.15-1.62.811-2.1-4.688 1.235v-1.889l5.5-1.448.811-2.1-6.31 1.662V8.367zm-11.163 4l1.459-.384.02.074-.455 1.179-1.513.398zm.503 2.526l-1.512.398.489-1.266 1.459-.385.02.074zm1.971-.424l-1.513.398.49-1.266 1.459-.385.02.073Z"},{"key":"fastapi","label":"FastAPI","viewBox":"0 0 24 24","d":"M12 .0387C5.3729.0384.0003 5.3931 0 11.9988c-.001 6.6066 5.372 11.9628 12 11.9625 6.628.0003 12.001-5.3559 12-11.9625-.0003-6.6057-5.3729-11.9604-12-11.96m-.829 5.4153h7.55l-7.5805 5.3284h5.1828L5.279 18.5436q2.9466-6.5444 5.892-13.0896"},{"key":"django","label":"Django","viewBox":"0 0 24 24","d":"M11.146 0h3.924v18.166c-2.013.382-3.491.535-5.096.535-4.791 0-7.288-2.166-7.288-6.32 0-4.002 2.65-6.6 6.753-6.6.637 0 1.121.05 1.707.203zm0 9.143a3.894 3.894 0 00-1.325-.204c-1.988 0-3.134 1.223-3.134 3.365 0 2.09 1.096 3.236 3.109 3.236.433 0 .79-.025 1.35-.102V9.142zM21.314 6.06v9.098c0 3.134-.229 4.638-.917 5.937-.637 1.249-1.478 2.039-3.211 2.905l-3.644-1.733c1.733-.815 2.574-1.53 3.109-2.625.561-1.121.739-2.421.739-5.835V6.059h3.924zM17.39.021h3.924v4.026H17.39z"},{"key":"flask","label":"Flask","viewBox":"0 0 24 24","d":"M7.172 20.36c-.914-.72-1.89-1.41-2.556-2.38-1.402-1.712-2.482-3.694-3.22-5.777-.446-1.355-.6-2.808-1.174-4.11-.602-.944.103-1.978 1.138-2.28.46-.087 1.272-.522.293-.211-.878.644-.963-.585-.063-.662.615-.082.84-.585.63-1.037-.66-.43 1.6-.903.463-1.544C1.5 1.08 4.34.835 3.64 2.285 3.473 3.4 5.624 2.08 5.125 3.368c.507.619 1.9.14 1.865 1.009.74.05.993.672 1.687.72.72.325 2.022.58 2.266 1.39-.713.566-2.364-1.165-2.443.398.215 2.31.16 4.689 1.004 6.888.4 1.332 1.37 2.38 2.244 3.418.837 1.016 1.971 1.73 3.127 2.333 1.014.478 2.107.795 3.213.994.448-.343 1.24-1.617 1.938-1.08.033.604-1.388 1.263-.067 1.196.776-.234 1.314.6 1.953-.152.588.697 2.446-.446 2.027.98-.566.364-1.392.144-1.959.646-.935-.467-1.68.418-2.715.306a19.86 19.86 0 01-3.484.29c-1.912-.15-3.865-.214-5.684-.88-1.024-.297-2.023-.881-2.924-1.464zm1.615.7c1 .432 1.978.888 3.074 1.026 1.74.24 3.537.614 5.283.274-.79-.357-1.608.14-2.395-.255-.944.203-1.957-.052-2.917-.177-1.092-.486-2.27-.82-3.291-1.452-1.277-.466.66.598 1.005.685.798.453-.877-.233-1.114-.421-.668-.375-.754-.297-.066.084.139.08.276.166.42.235zm-1.904-1.346c.97.359-.004-.682-.45-.622-.196-.341-.751-.557-.36-.74-.704.244-.737-.93-1.07-.763-.744-.235-.29-1.07-1.176-1.58-.081-.54-.882-1.008-1.138-1.822-.113-.416-.905-1.613-.418-.5.414 1.072 1.143 1.99 1.75 2.907.47.873 1.027 1.786 1.885 2.33.29.278.568.703.977.79zM4.09 16.647c.033-.146.177.317 0 0zm3.954 3.497c.215-.096-.31-.12 0 0zm.526.192c-.054-.265-.24.148 0 0zm.66.275c.312-.3-.484-.188 0 0zm1.127.63c.191-.282-.61-.107 0 0zM8.19 19.728c.487-.315-.63-.004 0 0zm.494.246c-.014-.166-.176.075 0 0zm2.47 1.542c.397.25 2.32.55 1.115.103-.2.042-2.23-.574-1.116-.103zm-3.921-3.054c-.04-.167-.616-.185 0 0zm1.15.67c.3-.21-.621-.16 0 0zm.966.593c.43-.162-.696-.163 0 0zm-2.584-1.773c.466.358 1.88.046.714-.213-.53-.283-1.727-.476-.912.17zm3.24 1.978c.193-.33-.815-.19 0 0zm-.984-.783c1.14.323-.958-.72-.281-.118l.15.068.13.05zm1.973 1.14c1.08.01-.975-.147 0 0zm-4.644-2.96c-.042-.2-.266.018 0 0zm6.47 3.985c.028-.363-.353.27 0 0zm-4.63-2.856c-.064-.191-.336-.008 0 0zm-1.738-1.254c.62-.037-.848-.273 0 0zm-2.06-1.332c-.077-.297-.674-.534 0 0zm5.407 3.435c-.114-.13-.054.028 0 0zm3.366 2.065c-.01-.197-.183.075 0 0zm-3.664-2.373c.06-.255-.528-.077 0 0zm-2.506-1.592c.46-.05-.74-.311 0 0zm4.241 2.637c.718-.285-.7-.14 0 0zM9.03 18.545c.827.106-.985-.563-.181-.06zm2.876 1.768c.773-.462.518 1.082 1.311.13.782-.57-.675.707.29.103.696-.467 1.726.22 2.376.445.468-.023.923.405 1.403.145.923-.25-1.806-.37-1.09-.81-.845.245-1.47-.294-1.885-.835-.948-.22-2.044-.703-2.517-1.542-.192-.315.28.044-.166-.47-.57-.508-.856-1.085-1.24-1.702-.457-.244-.51-.963-.557-.024.004-.593-.553-.992-.688-.826-.002-.571.595-.285.176-.707-.09-.592-.386-1.21-.475-1.877-.138-.322-.02-1.011-.473-.282-.165.77-.055-.947.202-.38.337-.58-.12-.51-.14-.43.22-.488.14-1.18-.057-.916.117-.517.185-1.902-.175-1.656.218-.54.414-2.473-.534-1.736-.384.005-1.048.14-1.363.296.986.543-.1.196-.5.11-.052.502-.45.285-.946.29.793.098-.386.81-.841.534-.59.282.51.987.012 1.205.06.328-.905-.12-.83.64-.573-.241-.078.9.209.514.975.264.686.866.71 1.437-.158.333-.784-.783-.14-.731-.507-.827-.561-.3-.984.085-.1.028 1.079.547.34.803.65.1.668.67.8 1.03.39.407.31-.45.779.04-.296-.436-1.567-1.228-.544-.974-.005-.44-.185-.793.129-.784.31-.562-.325 1.387.375.672.193-.085.24-.563.59.045.505.498.182.858-.531.403.127.433.954.587.799 1.265.165.595.395.376.596.342.158.578.247.153.255-.123.72.155.552.58.778.88.497.224-.712-1.522.142-.526.898.81.337 1.15-.47 1.02.51-.041.675.69 1.313.664.582.277.975 1.34-.027.897-.348-.313-1.58-.7-.573-.104.929.43 1.665.688 2.561 1.227.64.458.918.982 1.16 1.086-.538.257-1.623-.206-.817-.348-.503-.091-1.068-.345-.587.28.41.343 1.45.306 1.637.345-.159.348-.43.376.006.403-.486.26.156.3.201.448zm-.994-2.808c-.296-.31-.373-.89-.053-.385.164.066.525.947.053.385zm3.238 2.057c.185-.011.006.14 0 0zm-3.706-2.816c-.01-.468.107.36 0 0zm-.322-.433c-.372-.72.47.204 0 0zm-3.9-2.692c.219-.06.108.374 0 0zm3.104 1.682c.134-.504.158.424 0 0zm-2.192-1.525c-.155-.278.323.26 0 0zm1.882.604c-.352-.79.25-.432.078.13zM5.77 12.217c-.158-.26-.418-1.02-.334-1.252.076.378.804 1.627.357.518-.494-.93.59.302.702.534.05.23-.305-.063-.064.478-.44-.617-.26.34-.661-.278zm-1.003-.691c.04-.603.23.413 0 0zm.45.155c.216-.455.366.634 0 0zm-1.084-.84c-.374-.37-.644-.713.017-.23.255.01-.566-.778.06-.25.66.12.327 1.082-.077.48zm.57-.015c.217-.215.115.212 0 0zm.35.113c-.328-.617.4.258 0 0zm-.697-.667c-1.086-.966 1.365.506.177.18zm3.11 1.808c-.47-.282-.123-1.984.037-.82.457-.148-.025.6.315.594-.053.473-.206.643-.35.226zm1.15.68c.048-.513.099.35 0 0zm-.2-.198c.054-.22.007.258 0 0zM4.57 9.955c-.697-.963 2.027.973.447.244-.165-.043-.364-.06-.447-.244zm2.216 1.175c-.066-.81.147.134 0 0zm1.682 1.079c.13-.462.01.305 0 0zM4.676 9.587c.415-.088 1.718.729.52.234-.132-.148-.416-.08-.52-.234zm3.56 1.775c.044-.83.248-.495.002.118zM4.985 9.299c.169-.248-.45-1.12.089-.313.232.185.672.31.283.387.61.539-.15.146-.372-.074zm3.075 1.804c.117-.944.103.553 0 0zM4.632 8.427c.129-.055.068.172 0 0zm.802.478c.206-.434.38.483 0 0zm2.263 1.259c-.002-.167.043.242 0 0zm-.131-.29c-.314-.776.292.41 0 0zm-.193-.51c-.053-.32.18.404 0 0zm.314-.51c-.216-.38.272-1.673.326-.87-.227.625-.065.975.093.136.293-.66-.063 1.303-.42.735zm.322-1.923c.094-.115.02.139 0 0zM7.47 17.544c-.128-.111.016.07 0 0zm1.11.56c.615.16.612-.095.055-.17-.3-.28-1.246-.575-.4-.035.057.142.235.139.344.206zM6.389 16.65c.34.253 1.28.719.484.096.269-.312-.514-.478-.254-.686-.66-.404-.52-.368-.058-.356-.794-.354.114-.328.07-.51-.305-.06-1.52-.54-.804.04-.726-.37-.173.138-.392.084-.743-.202.66.565-.118.375.425.337 1.146.864.18.357-.128.183.69.46.892.6zm1.16.667c1.41.454-.691-.556 0 0zm5.94 3.598c.02-.28-.193.24 0 0zm.611.257c.325-.315.013.503.54-.077.005-.415-.017-.66-.606-.156-.162.09-.234.473.066.233zm-9.692-6.087c-.1-.393-.7-.39 0 0zm.652.428c-.242-.402-.864-.364 0 0zm3.71 2.237c.362.32 1.662.236.44.04-.182-.27-1.151-.204-.44-.04zm5.097 3.149c.558-.468-.54.208 0 0zm1.16.796c.003-.15-.24.066 0 0zm.001-.21c.617-.654-.598.039 0 0zM2.805 13.743c-.526-.75-.327-1.088-.835-1.7-.096-.47-.87-1.533-.4-.406.43.659.558 1.679 1.235 2.106zm12.03 7.534c1.135-.734-.466-.32 0 0zm.866.34c.57-.488-.36-.102 0 0zM4.215 14.255c.163-.242-.42-.031 0 0zm11.305 7.129c.551-.355-.126-.3-.1.032zm-7.47-4.71c-.02-.24-.291.02 0 0zm.46.267c-.145-.297-.224.047 0 0zm7.894 4.684c.705-.51-.428-.098-.148.096zm-.27-.13c.574-.482-.607.213 0 0zm1.38.918c.386-.258-.469-.083 0 0zM4.57 14.08c.517.116 2.066 1.274 1.152.08-.468-.138-.187-1.283-.665-1.08.32.535.264.763-.41.426-.845-.413-.474.204-.31.374-.224.052.299.196.233.2zm-2.356-1.86c.092-.383-.853-2.107-.446-.864.146.26.13.754.446.864zm4.324 2.666c-.266-.223-.013-.032 0 0zm.656.152c0-.405-.725-.164 0 0zm5.681 3.583c-.108-.278-.428-.006 0 0zm.273.199c-.04-.155-.157.03 0 0zM15.4 20.24c.216-.16-.27-.02 0 0zM3.39 12.52c.62-.24-.664-.17 0 0zm8.984 5.662c-.007-.401-.395.1 0 0zm-9.23-6.231c.399-.135-.367-.09 0 0zm1.156.56c-.007-.133-.122.05 0 0zm14.09 8.64c.512-.104 1.678.26 1.866-.136-.62-.015-2.15-.438-2.222.1l.136.023.22.013zM4.667 12.603c.009-.407-.317-.015 0 0zM1.63 10.495c-.138-.775-.525-.118 0 0zm.724.182c.009-.25-.663-.224 0 0zm.414.203c-.12-.097-.094.122 0 0zm2.605 1.67c.122-.112-.29-.083 0 0zm-2.88-2.128c-.07-.585-.84-.088 0 0zm-1.486-.964c-.02-.27-.144.102 0 0zm.22-.167c-.035-.32-.19.04 0 0zm1.22.729c.518-.203-.94-.42-.104-.04zm16.334 10.089c.33-.303-.42-.094 0 0zm1.974 1.023c.132-.392-.334.05 0 0zM2.573 9.38c.055-.38-.41.075 0 0zM.837 8.218c-.093-.535-.08-1.474.812-1.156-1.191.236.824 1.48.57.498.5.024.98-.296.716.19.987-.11 1.67-.964 2.624-.845.742-.098 1.554-.172 2.354-.471.658-.048 1.29-.756.93-1.175-.896-.076-1.835.036-2.827.233-1.098.228-2.096.662-3.205.849-1.08.145.217.4-.092.456-.564.196.672.328-.073.534-.46-.088-.94-.246-.743-.73-1.035.133-1.945.563-1.127 1.616h.06zm2.494-1.27c.243-.894 1.3.735.398.118-.108-.08-.285-.146-.398-.12zm.047-.434c.35-.26.186.146 0 0zm.445.008c.032-.411 1.018.218.163.148zm.608-.245c.222-.26.064.23 0 0zm.156-.104c.37-.444 2.095-.283.832-.043-.338-.255-.598.15-.832.043zm2.25-.347c-.055-1.214 1.119.432 0 0zm.64-.004c.233-.612.906-.245.108-.123.017.065-.024.316-.108.123zM2.322 9.067c.697-.427-.741-.37 0 0zm.515.144c.245-.26-.531-.106 0 0zm-1.52-1.08c.399-.305-.471-.116 0 0zm20.602 12.89c.012-.355-.304.16 0 0zm-2.093-1.43c.06-.408-.27.037 0 0zm2.67 1.568c.557 0 1.688-.173.475-.173-.19.03-1.109.024-.476.173zM3.29 8.959c.45-.03.706-.497-.087-.47-1.23-.127 1.084.42-.158.264-.167.11.236.237.245.207zm.398.202c-.048-.29-.14.154 0 0zm.47-1.257c.197-.243-.27-.065 0 0zm-1.5-2.508c.806-.274 1.907-.581 2.287.135-.387-.466-.156-.924.21-.243.516.689.775-.313.438-.545.383.476.819.7.257.03.61-.734-1.223.097-1.64.088-.2.09-2.071.477-1.551.535zm.472-.903c.46-.347 1.588.206.864-.345-.07-.062-1.586.418-.864.345zm1.674.069c.538.013-.231-.722.409-.39-.105-.343-.746-.407-1.06-.544-.176.314.36.938.65.934zm-1.38-1.52c.186-.252-.326.128 0 0zm.684.164c.866-.115-.22-.373-.174-.01zm-1.277-1c-.61-.796 1.146.134.527-.7-.522-.415-1.023.468-.527.7zm7.824 4.215c.28-.496-1.155-.668-.188-.175.09.03.07.21.188.175z"}];
+
+export function TechMark({ icon, className }: { icon: TechLogo; className?: string }) {
+ return (
+
+
+
+ );
+}
diff --git a/components/logo.tsx b/components/logo.tsx
index 9d6c9ca..4347dfb 100644
--- a/components/logo.tsx
+++ b/components/logo.tsx
@@ -48,12 +48,12 @@ export function LiveboardWordmark({ height = 36, className }: { height?: number;
Liveboard
diff --git a/hooks/use-live-metrics.ts b/hooks/use-live-metrics.ts
index 36c75d6..0a83d48 100644
--- a/hooks/use-live-metrics.ts
+++ b/hooks/use-live-metrics.ts
@@ -6,6 +6,45 @@ import type { LogEntry } from "@/lib/types";
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
const LOG_BUFFER = 200;
+// ── Demo mode ────────────────────────────────────────────────────────────────
+// When no live backend is connected, synthesize a realistic request stream so
+// the dashboard reads as live instead of sitting empty.
+const DEMO_ROUTES: { method: LogEntry["method"]; route: string; weight: number }[] = [
+ { method: "GET", route: "/api/products", weight: 9 },
+ { method: "GET", route: "/api/users/:id", weight: 7 },
+ { method: "GET", route: "/health", weight: 6 },
+ { method: "POST", route: "/api/checkout", weight: 4 },
+ { method: "GET", route: "/api/search", weight: 5 },
+ { method: "POST", route: "/api/orders", weight: 4 },
+ { method: "POST", route: "/api/auth/login", weight: 3 },
+ { method: "POST", route: "/api/payments", weight: 3 },
+ { method: "PATCH", route: "/api/users/:id", weight: 2 },
+ { method: "DELETE", route: "/api/products/:id", weight: 1 },
+ { method: "GET", route: "/api/analytics/events", weight: 3 },
+];
+const DEMO_POOL = DEMO_ROUTES.flatMap((r) => Array(r.weight).fill(r));
+
+function mockLog(): LogEntry {
+ const pick = DEMO_POOL[Math.floor(Math.random() * DEMO_POOL.length)];
+ const roll = Math.random();
+ // Mostly 2xx, occasional 4xx, rare 5xx — skew errors toward checkout/auth.
+ const risky = pick.route.includes("checkout") || pick.route.includes("auth");
+ let status = 200;
+ if (roll > (risky ? 0.82 : 0.94)) status = 500;
+ else if (roll > (risky ? 0.7 : 0.9)) status = 401;
+ else if (pick.method === "POST") status = 201;
+ const base = pick.route.includes("checkout") || pick.route.includes("payments") ? 260 : 40;
+ return {
+ id: crypto.randomUUID(),
+ timestamp: new Date(),
+ method: pick.method,
+ route: pick.route,
+ status,
+ latency: Math.round(base + Math.random() * base * (status >= 500 ? 4 : 1.5)),
+ userId: `u_${Math.floor(1000 + Math.random() * 8999)}`,
+ };
+}
+
export function useLiveLog(maxEntries = LOG_BUFFER) {
const [logs, setLogs] = useState([]);
const [paused, setPaused] = useState(false);
@@ -15,6 +54,7 @@ export function useLiveLog(maxEntries = LOG_BUFFER) {
const retryDelay = useRef(1_000);
const esRef = useRef(null);
const timerRef = useRef | null>(null);
+ const mockRef = useRef | null>(null);
useEffect(() => {
pausedRef.current = paused;
@@ -23,8 +63,21 @@ export function useLiveLog(maxEntries = LOG_BUFFER) {
useEffect(() => {
const apiKey = process.env.NEXT_PUBLIC_LIVEBOARD_API_KEY ?? "";
+ function startMock() {
+ if (mockRef.current) return;
+ // Seed a full-looking buffer, then stream new entries in.
+ setLogs(Array.from({ length: 18 }, mockLog));
+ mockRef.current = setInterval(() => {
+ if (pausedRef.current) return;
+ setLogs((prev) => [mockLog(), ...prev].slice(0, maxEntries));
+ }, 900);
+ }
+
function connect() {
- if (!apiKey) return;
+ if (!apiKey) {
+ startMock();
+ return;
+ }
const url = `${API_URL}/v1/stream/logs?api_key=${encodeURIComponent(apiKey)}`;
const es = new EventSource(url);
@@ -53,12 +106,19 @@ export function useLiveLog(maxEntries = LOG_BUFFER) {
es.onopen = () => {
setConnected(true);
retryDelay.current = 1_000;
+ // Real stream is live — drop the demo fallback.
+ if (mockRef.current) {
+ clearInterval(mockRef.current);
+ mockRef.current = null;
+ }
};
es.onerror = () => {
setConnected(false);
es.close();
esRef.current = null;
+ // Backend unreachable — fall back to the demo stream so the UI stays live.
+ startMock();
// Exponential back-off: 1 s → 2 s → 4 s → 8 s
const delay = retryDelay.current;
retryDelay.current = Math.min(delay * 2, 8_000);
@@ -72,6 +132,7 @@ export function useLiveLog(maxEntries = LOG_BUFFER) {
esRef.current?.close();
esRef.current = null;
if (timerRef.current) clearTimeout(timerRef.current);
+ if (mockRef.current) clearInterval(mockRef.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [maxEntries]);
diff --git a/hooks/use-websocket.ts b/hooks/use-websocket.ts
index 2d470ab..d9fcb58 100644
--- a/hooks/use-websocket.ts
+++ b/hooks/use-websocket.ts
@@ -15,9 +15,21 @@ function emptyCards(): StatCard[] {
];
}
+/** Realistic metric tick for demo mode when no live backend is connected. */
+function mockMetric(): MetricUpdate {
+ return {
+ requests: Math.round(2200 + Math.random() * 520),
+ errorRate: Math.round((0.3 + Math.random() * 1.7) * 10) / 10,
+ p99: Math.round(250 + Math.random() * 95),
+ avg: Math.round(90 + Math.random() * 55),
+ bucket: new Date().toISOString(),
+ };
+}
+
/**
* Connects to the Socket.io server and returns live stat-card data.
- * Falls back to empty/placeholder cards while the connection is establishing.
+ * When no API key / live backend is available, falls back to a self-updating
+ * demo stream so the dashboard always feels alive instead of stuck "connecting…".
*/
export function useMetrics(apiKey: string | null) {
const [cards, setCards] = useState(emptyCards);
@@ -30,11 +42,7 @@ export function useMetrics(apiKey: string | null) {
}>({ requests: [], errorRate: [], p99: [], avg: [] });
useEffect(() => {
- if (!apiKey) return;
-
- const socket = getSocket(apiKey);
-
- function onMetric(update: MetricUpdate) {
+ function applyUpdate(update: MetricUpdate) {
const h = history.current;
const push = (arr: number[], val: number) =>
@@ -58,7 +66,7 @@ export function useMetrics(apiKey: string | null) {
setCards([
{
label: "Requests / min",
- value: update.requests,
+ value: update.requests.toLocaleString(),
unit: "req",
delta: delta(update.requests, prev.requests),
deltaLabel: "vs last min",
@@ -91,9 +99,43 @@ export function useMetrics(apiKey: string | null) {
]);
}
+ let mockTimer: ReturnType | null = null;
+ let gotReal = false;
+
+ function startMock() {
+ if (mockTimer) return;
+ // Seed history so sparklines render full immediately.
+ for (let i = 0; i < SPARKLINE_LENGTH; i++) applyUpdate(mockMetric());
+ mockTimer = setInterval(() => applyUpdate(mockMetric()), 2000);
+ }
+
+ if (!apiKey) {
+ startMock();
+ return () => {
+ if (mockTimer) clearInterval(mockTimer);
+ };
+ }
+
+ const socket = getSocket(apiKey);
+ function onMetric(update: MetricUpdate) {
+ gotReal = true;
+ if (mockTimer) {
+ clearInterval(mockTimer);
+ mockTimer = null;
+ }
+ applyUpdate(update);
+ }
socket.on("metric", onMetric);
+
+ // If no real metric arrives shortly, switch to demo mode.
+ const grace = setTimeout(() => {
+ if (!gotReal) startMock();
+ }, 1500);
+
return () => {
socket.off("metric", onMetric);
+ clearTimeout(grace);
+ if (mockTimer) clearInterval(mockTimer);
};
}, [apiKey]);
diff --git a/package-lock.json b/package-lock.json
index 606f050..d912212 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@radix-ui/react-tooltip": "^1.2.9",
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
+ "geist": "^1.7.2",
"lucide-react": "^1.17.0",
"next": "^16.2.9",
"next-auth": "^5.0.0-beta.31",
@@ -30,6 +31,7 @@
"eslint": "^8",
"eslint-config-next": "latest",
"postcss": "^8",
+ "puppeteer-core": "^23.11.1",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
@@ -1031,6 +1033,29 @@
"node": ">=14"
}
},
+ "node_modules/@puppeteer/browsers": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz",
+ "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "extract-zip": "^2.0.1",
+ "progress": "^2.0.3",
+ "proxy-agent": "^6.5.0",
+ "semver": "^7.6.3",
+ "tar-fs": "^3.0.6",
+ "unbzip2-stream": "^1.4.3",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "browsers": "lib/cjs/main-cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz",
@@ -1763,6 +1788,13 @@
"tslib": "^2.8.0"
}
},
+ "node_modules/@tootallnate/quickjs-emscripten": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
+ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
@@ -1880,6 +1912,17 @@
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.61.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz",
@@ -2505,6 +2548,16 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@@ -2765,6 +2818,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ast-types": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
+ "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/ast-types-flow": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
@@ -2818,6 +2884,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/b4a": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
+ "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2825,6 +2906,112 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/bare-events": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
+ "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-fs": {
+ "version": "4.7.4",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz",
+ "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.5.4",
+ "bare-path": "^3.0.0",
+ "bare-stream": "^2.6.4",
+ "bare-url": "^2.2.2",
+ "fast-fifo": "^1.3.2"
+ },
+ "engines": {
+ "bare": ">=1.16.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz",
+ "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/bare-stream": {
+ "version": "2.13.3",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz",
+ "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.8.1",
+ "streamx": "^2.25.0",
+ "teex": "^1.0.1"
+ },
+ "peerDependencies": {
+ "bare-abort-controller": "*",
+ "bare-buffer": "*",
+ "bare-events": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ },
+ "bare-buffer": {
+ "optional": true
+ },
+ "bare-events": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-url": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz",
+ "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.10.35",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.35.tgz",
@@ -2837,6 +3024,16 @@
"node": ">=6.0.0"
}
},
+ "node_modules/basic-ftp": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+ "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -2874,6 +3071,41 @@
"node": ">=8"
}
},
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
@@ -3019,12 +3251,81 @@
"node": ">= 6"
}
},
+ "node_modules/chromium-bidi": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz",
+ "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mitt": "3.0.1",
+ "zod": "3.23.8"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3234,6 +3535,16 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -3354,6 +3665,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/degenerator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
+ "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.13.4",
+ "escodegen": "^2.1.0",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -3370,6 +3696,13 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1367902",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
+ "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -3426,6 +3759,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/engine.io-client": {
"version": "6.6.5",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.5.tgz",
@@ -3635,6 +3978,16 @@
"benchmarks"
]
},
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -3648,6 +4001,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
"node_modules/eslint": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
@@ -4032,6 +4407,20 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
@@ -4084,6 +4473,37 @@
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
"license": "MIT"
},
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4091,6 +4511,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -4145,6 +4572,16 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -4333,6 +4770,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/geist": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/geist/-/geist-1.7.2.tgz",
+ "integrity": "sha512-Gu5lDFa3pLRyoBlBPf0QIFHVdWAnpco7fS1bJm41jyLPFoguBgiubseUN2oLXMgqZ7uxAxDoXcHMhCY/fOTTgg==",
+ "license": "SIL OPEN FONT LICENSE",
+ "peerDependencies": {
+ "next": ">=13.2.0"
+ }
+ },
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@@ -4340,7 +4786,17 @@
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.4"
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
@@ -4391,6 +4847,22 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
@@ -4422,6 +4894,21 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/get-uri": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
+ "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "basic-ftp": "^5.0.2",
+ "data-uri-to-buffer": "^6.0.2",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
@@ -4632,6 +5119,55 @@
"node": ">= 0.4"
}
},
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4722,6 +5258,16 @@
"node": ">=12"
}
},
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -5490,6 +6036,13 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/motion-dom": {
"version": "12.40.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz",
@@ -5564,6 +6117,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/netmask": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz",
+ "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/next": {
"version": "16.2.9",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.9.tgz",
@@ -5931,6 +6494,40 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pac-proxy-agent": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
+ "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/quickjs-emscripten": "^0.23.0",
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "get-uri": "^6.0.1",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.6",
+ "pac-resolver": "^7.0.1",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/pac-resolver": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "degenerator": "^5.0.0",
+ "netmask": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5998,6 +6595,13 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -6261,6 +6865,16 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -6273,6 +6887,54 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-agent": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
+ "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "http-proxy-agent": "^7.0.1",
+ "https-proxy-agent": "^7.0.6",
+ "lru-cache": "^7.14.1",
+ "pac-proxy-agent": "^7.1.0",
+ "proxy-from-env": "^1.1.0",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/proxy-agent/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -6283,6 +6945,24 @@
"node": ">=6"
}
},
+ "node_modules/puppeteer-core": {
+ "version": "23.11.1",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz",
+ "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.6.1",
+ "chromium-bidi": "0.11.0",
+ "debug": "^4.4.0",
+ "devtools-protocol": "0.0.1367902",
+ "typed-query-selector": "^2.12.0",
+ "ws": "^8.18.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -6535,6 +7215,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
@@ -6939,6 +7629,17 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
"node_modules/socket.io-client": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
@@ -6967,6 +7668,47 @@
"node": ">=10.0.0"
}
},
+ "node_modules/socks": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
+ "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.1.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -6997,6 +7739,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/streamx": {
+ "version": "2.28.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz",
+ "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -7373,6 +8127,54 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tar-fs": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz",
+ "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0",
+ "tar-stream": "^3.1.5"
+ },
+ "optionalDependencies": {
+ "bare-fs": "^4.0.1",
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz",
+ "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "bare-fs": "^4.5.5",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
+ "node_modules/teex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "streamx": "^2.12.5"
+ }
+ },
+ "node_modules/text-decoder": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -7403,6 +8205,13 @@
"node": ">=0.8"
}
},
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@@ -7613,6 +8422,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/typed-query-selector": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
+ "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -7646,6 +8462,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/unbzip2-stream": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
+ "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.2.1",
+ "through": "^2.3.8"
+ }
+ },
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -8034,6 +8861,78 @@
"node": ">=0.4.0"
}
},
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.3",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
+ "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -8046,6 +8945,16 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index 6078fc1..ac0d7b1 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@radix-ui/react-tooltip": "^1.2.9",
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
+ "geist": "^1.7.2",
"lucide-react": "^1.17.0",
"next": "^16.2.9",
"next-auth": "^5.0.0-beta.31",
@@ -31,6 +32,7 @@
"eslint": "^8",
"eslint-config-next": "latest",
"postcss": "^8",
+ "puppeteer-core": "^23.11.1",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
diff --git a/public/shots/alerts.png b/public/shots/alerts.png
new file mode 100644
index 0000000..bfa3456
Binary files /dev/null and b/public/shots/alerts.png differ
diff --git a/public/shots/endpoints.png b/public/shots/endpoints.png
new file mode 100644
index 0000000..484934d
Binary files /dev/null and b/public/shots/endpoints.png differ
diff --git a/public/shots/overview.png b/public/shots/overview.png
new file mode 100644
index 0000000..b0bcf79
Binary files /dev/null and b/public/shots/overview.png differ
diff --git a/public/shots/status.png b/public/shots/status.png
new file mode 100644
index 0000000..ea37b7d
Binary files /dev/null and b/public/shots/status.png differ
diff --git a/public/shots/traces.png b/public/shots/traces.png
new file mode 100644
index 0000000..ca7eb4f
Binary files /dev/null and b/public/shots/traces.png differ
diff --git a/scripts/shot-landing.mjs b/scripts/shot-landing.mjs
new file mode 100644
index 0000000..0e470a4
--- /dev/null
+++ b/scripts/shot-landing.mjs
@@ -0,0 +1,49 @@
+import puppeteer from "puppeteer-core";
+import { existsSync } from "node:fs";
+
+// Resolve a Chrome/Edge binary: CHROME_PATH env wins, else probe common locations.
+const CANDIDATES = [
+ process.env.CHROME_PATH,
+ "C:/Program Files/Google/Chrome/Application/chrome.exe",
+ "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",
+ "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
+ "/usr/bin/google-chrome",
+ "/usr/bin/chromium",
+].filter(Boolean);
+const CHROME = CANDIDATES.find((p) => existsSync(p));
+if (!CHROME) {
+ console.error("No Chrome/Edge binary found. Set CHROME_PATH to your browser executable.");
+ process.exit(1);
+}
+
+const BASE = process.env.BASE || "http://localhost:3000";
+const OUT = process.env.OUT || "scripts/.landing.png";
+const W = Number(process.env.W || 1512);
+const H = Number(process.env.H || 982);
+const FULL = process.env.FULL === "1";
+const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
+
+const browser = await puppeteer.launch({
+ executablePath: CHROME,
+ headless: "new",
+ defaultViewport: { width: W, height: H, deviceScaleFactor: 2 },
+ args: ["--hide-scrollbars", "--force-color-profile=srgb"],
+});
+const page = await browser.newPage();
+await page.goto(BASE, { waitUntil: "networkidle2" });
+await sleep(2500); // let entrance animations settle
+if (FULL) {
+ // scroll through to trigger in-view animations, then back to top
+ await page.evaluate(async () => {
+ for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight) {
+ window.scrollTo(0, y);
+ await new Promise((r) => setTimeout(r, 350));
+ }
+ window.scrollTo(0, 0);
+ });
+ await sleep(800);
+}
+await page.screenshot({ path: OUT, fullPage: FULL });
+await browser.close();
+console.log("saved", OUT);
diff --git a/tailwind.config.ts b/tailwind.config.ts
index ce11dcb..5a69d14 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -36,9 +36,9 @@ const config: Config = {
},
},
fontFamily: {
- sans: ["var(--font-inter)", "Inter", "Arial", "sans-serif"],
- mono: ["JetBrains Mono", "Fira Code", "monospace"],
- display: ["var(--font-space-grotesk)", "'Space Grotesk'", "system-ui", "sans-serif"],
+ sans: ["var(--font-geist-sans)", "system-ui", "Arial", "sans-serif"],
+ mono: ["var(--font-geist-mono)", "JetBrains Mono", "monospace"],
+ display: ["var(--font-sora)", "var(--font-geist-sans)", "system-ui", "sans-serif"],
},
borderRadius: {
DEFAULT: "8px",