| import { useState, useEffect } from "react"; |
| import { |
| Loader2, |
| Rocket, |
| ShieldCheck, |
| Brain, |
| ArrowUpRight, |
| } from "lucide-react"; |
| import type { LoadingStatus } from "../hooks/LLMContext"; |
| import HfIcon from "./HfIcon"; |
|
|
| interface LandingPageProps { |
| onStart: () => void; |
| status: LoadingStatus; |
| isLoading: boolean; |
| showChat: boolean; |
| } |
|
|
| const cards = [ |
| { |
| title: "Step-by-step reasoning", |
| eyebrow: "REASONING MODEL", |
| body: "LFM2.5-Thinking generates its reasoning process before producing final answers, improving accuracy on complex tasks like math, coding, and logic.", |
| Icon: Rocket, |
| }, |
| { |
| title: "Private edge inference", |
| eyebrow: "LOCAL & PRIVATE", |
| body: "WebGPU-accelerated browser inference ensures high performance. No data is sent to a server, and the demo can even run offline after the initial download.", |
| Icon: ShieldCheck, |
| }, |
| { |
| title: "Scaled reinforcement", |
| eyebrow: "TRAINING PIPELINE", |
| body: "The 1.2B parameter model benefits from extended pre-training on 28T tokens and large-scale multi-stage reinforcement learning for best-in-class performance.", |
| Icon: Brain, |
| }, |
| ] as const; |
|
|
| export function LandingPage({ onStart, status, isLoading, showChat }: LandingPageProps) { |
| const [introFade, setIntroFade] = useState(true); |
|
|
| useEffect(() => { |
| const t = setTimeout(() => setIntroFade(false), 50); |
| return () => clearTimeout(t); |
| }, []); |
|
|
| const hideMainContent = isLoading || showChat; |
| const readyToStart = status.state === "ready"; |
|
|
| return ( |
| <div className="brand-surface relative flex h-screen flex-col overflow-hidden text-black"> |
| <div className="landing-brand-glow absolute inset-0" /> |
| |
| <div |
| className={`absolute inset-0 z-50 bg-white transition-opacity duration-1000 pointer-events-none ${ |
| introFade ? "opacity-100" : "opacity-0" |
| }`} |
| /> |
| |
| <div |
| className={`relative z-10 mx-auto flex h-full w-full max-w-7xl flex-col px-6 pb-10 pt-8 sm:px-8 lg:px-14 transition-all duration-700 ${ |
| hideMainContent |
| ? "opacity-0 translate-y-4 pointer-events-none" |
| : "opacity-100" |
| }`} |
| > |
| <header className="animate-rise-in flex items-start justify-between"> |
| <img |
| src="/liquid.svg" |
| alt="Liquid AI" |
| className="h-10 w-auto sm:h-12" |
| draggable={false} |
| /> |
| <p className="font-support text-[10px] uppercase tracking-[0.22em] text-[#000000b3] sm:text-xs"> |
| LFM2.5 WebGPU Demo |
| </p> |
| </header> |
| |
| <section className="mt-14 flex flex-col items-center text-center"> |
| <div className="animate-rise-in-delayed space-y-5"> |
| <p className="font-support text-xs uppercase tracking-[0.2em] text-[#5505afb3]"> |
| Capable and efficient general-purpose AI systems at every scale |
| </p> |
| <h1 className="max-w-3xl text-4xl font-bold leading-[1.04] tracking-tight sm:text-6xl lg:text-7xl"> |
| Capable reasoning.<br />Local inference.<br />WebGPU accelerated. |
| </h1> |
| <p className="max-w-2xl mx-auto text-base leading-relaxed text-[#000000b3] sm:text-lg"> |
| Run |
| <a |
| href="https://huggingface.co/LiquidAI/LFM2.5-1.2B-Thinking-ONNX" |
| target="_blank" |
| rel="noreferrer" |
| className="mx-1 underline decoration-[#5505af4d] underline-offset-4 hover:text-[#5505af] transition-colors" |
| > |
| LFM2.5-1.2B-Thinking |
| </a> |
| directly in your browser, powered by |
| <HfIcon className="size-7 inline-block ml-1 mb-[1px]" /> |
| <a |
| href="https://github.com/huggingface/transformers.js" |
| target="_blank" |
| rel="noreferrer" |
| className="ml-1 underline decoration-[#5505af4d] underline-offset-4 hover:text-[#5505af] transition-colors" |
| > |
| Transformers.js |
| </a> |
| </p> |
| </div> |
| </section> |
| |
| <section className="mt-10 flex flex-col lg:flex-row gap-4"> |
| {cards.map(({ eyebrow, title, body, Icon }, idx) => ( |
| <article |
| key={title} |
| className="animate-rise-in flex-1 flex items-start gap-4 rounded-2xl border border-[#0000001a] bg-[#ffffffcc] px-4 py-4 backdrop-blur-sm sm:gap-5 sm:px-6 sm:py-5" |
| style={{ animationDelay: `${120 + idx * 90}ms` }} |
| > |
| <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border border-[#5505af4d] bg-[linear-gradient(135deg,#5505AF_0%,#CD82F0_55%,#FF5F1E_100%)] text-white"> |
| <Icon className="h-5 w-5" /> |
| </div> |
| <div className="min-w-0 text-left"> |
| <p className="font-support text-[10px] uppercase tracking-[0.2em] text-[#00000080]"> |
| {eyebrow} |
| </p> |
| <h3 className="mt-1 text-xl font-medium leading-tight text-black"> |
| {title} |
| </h3> |
| <p className="mt-2 text-sm leading-relaxed text-[#000000b3] sm:text-[15px]"> |
| {body} |
| </p> |
| </div> |
| </article> |
| ))} |
| </section> |
| |
| <section className="mt-10 flex flex-col items-center animate-rise-in" style={{ animationDelay: "400ms" }}> |
| <button |
| onClick={onStart} |
| className="inline-flex w-full max-w-sm items-center justify-center gap-2 rounded-xl bg-black px-6 py-3.5 text-base font-semibold text-white transition-transform duration-200 hover:-translate-y-0.5 hover:bg-[#5505af] cursor-pointer" |
| > |
| {readyToStart |
| ? "Start chatting" |
| : "Load model & start chatting"} |
| <ArrowUpRight className="h-4 w-4" /> |
| </button> |
| {!readyToStart && ( |
| <p className="mt-3 text-xs text-[#00000080]"> |
| ~750 MB will be downloaded and cached locally for future sessions. |
| </p> |
| )} |
| </section> |
| </div> |
| |
| <div |
| className={`brand-surface absolute inset-0 z-20 flex flex-col items-center justify-center transition-opacity duration-700 ${ |
| isLoading ? "opacity-100" : "opacity-0 pointer-events-none" |
| }`} |
| > |
| <div className={`flex w-full max-w-md flex-col items-center px-6 transition-all duration-700 ${isLoading ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"}`}> |
| <img |
| src="/liquid.svg" |
| alt="Liquid AI" |
| className="mb-8 h-9 w-auto" |
| draggable={false} |
| /> |
| <Loader2 className="h-10 w-10 animate-spin text-[#5505af]" /> |
| <p className="mt-4 text-sm tracking-wide text-[#000000b3]"> |
| {status.state === "loading" |
| ? (status.message ?? "Loading model…") |
| : status.state === "error" |
| ? "Error" |
| : "Initializing…"} |
| </p> |
| <div className="mt-4 h-1.5 w-full rounded-full bg-[#0000001a] overflow-hidden"> |
| <div |
| className="h-full rounded-full bg-[linear-gradient(90deg,#5505AF_0%,#CD82F0_60%,#FF5F1E_100%)] transition-[width] duration-300 ease-out" |
| style={{ |
| width: `${status.state === "ready" ? 100 : status.state === "loading" && status.progress != null ? status.progress : 0}%`, |
| }} |
| /> |
| </div> |
| {status.state === "error" && ( |
| <p className="mt-3 text-sm text-red-600">{status.error}</p> |
| )} |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|