TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1"use client";2import * as React from "react";3import { useInView, useReducedMotion } from "motion/react";4import { cn } from "@/lib/utils";5import { DesktopFrame, PhoneFrame } from "./frames";6import { ArenaMiniCard, DesktopChatMock, MobileChatMock } from "./product-mock";7import { fastestIndex, useFakeStreams, type FakeStream } from "./fake-stream";8import { ARENA_REPLIES, HERO_PROMPT, HERO_REPLY, MOCK_MODELS } from "./mock-data";910const HERO_MODEL = MOCK_MODELS[0];11const CHAT_STREAMS: FakeStream[] = [{ text: HERO_REPLY, speed: 5, delay: 800 }];1213const ARENA_MODELS = [MOCK_MODELS[1], MOCK_MODELS[2], MOCK_MODELS[3]];14const ARENA_TEXTS = ARENA_MODELS.map((m) => ARENA_REPLIES[m.key]);15/** The mini Arena starts a beat after the chat answer so the eye follows one thing at a time. */16const ARENA_STREAMS: FakeStream[] = [17 { text: ARENA_TEXTS[0], speed: 3, delay: 3200 },18 { text: ARENA_TEXTS[1], speed: 5, delay: 2700 },19 { text: ARENA_TEXTS[2], speed: 2, delay: 3600 },20];21const ARENA_FASTEST = fastestIndex(ARENA_STREAMS);2223/**24 * Hero visual: the real product, animated. Desktop (≥ md) shows the app window with the sidebar,25 * model pill, a streaming answer with cursor and its metadata line, plus a mini Arena card; phones26 * get a phone-shaped frame with the real mobile layout (header, messages, composer, bottom nav).27 * Both share one stream so switching breakpoints never desyncs.28 */29export function HeroVisual({ className }: { className?: string }) {30 const ref = React.useRef<HTMLDivElement>(null);31 const inView = useInView(ref, { once: true, margin: "0px 0px -10% 0px" });32 const reduce = !!useReducedMotion();33 const chat = useFakeStreams(CHAT_STREAMS, inView, 0, reduce);34 const arena = useFakeStreams(ARENA_STREAMS, inView, 0, reduce);35 const arenaStarted = arena.progress.some((p) => p > 0);3637 const chatProps = { prompt: HERO_PROMPT, reply: HERO_REPLY, progress: chat.progress[0], elapsedMs: chat.elapsedFor(0), ttftMs: chat.ttftFor(0), model: HERO_MODEL };3839 return (40 <div ref={ref} className={cn("relative", className)}>41 {/* Desktop */}42 <div className="relative hidden md:block">43 <div aria-hidden className="absolute -inset-px rounded-[13px] bg-gradient-to-b from-border-strong/70 via-transparent to-transparent" />44 <DesktopFrame url="polyllm.io/app/chat" className="relative">45 <DesktopChatMock {...chatProps} className="min-h-[30rem]" />46 </DesktopFrame>47 <ArenaMiniCard48 models={ARENA_MODELS}49 replies={ARENA_TEXTS}50 progress={arena.progress}51 elapsedFor={arena.elapsedFor}52 fastest={ARENA_FASTEST}53 allDone={arena.allDone}54 className={cn("absolute -bottom-6 -right-3 hidden transition-[opacity,transform] duration-300 ease-[var(--ease-out-soft)] lg:block xl:-right-10", arenaStarted ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0")}55 />56 </div>57 {/* Phone */}58 <div className="md:hidden">59 <PhoneFrame label="PolyLLM on a phone: chat with the model pill, a streaming answer, the composer and the bottom navigation">60 <MobileChatMock {...chatProps} />61 </PhoneFrame>62 </div>63 </div>64 );65}66