SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.1 KB · 66 lines tsx
Raw Blame History
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