import { Component, type ComponentChild } from 'preact' import { HW, MODELS, OFFLOAD, PRESETS, RESP, SPIN, quantOf, quantsFor, canonHw, canonModel, canonQuant, type Hw, type Model, type Quant } from './data' import { calc, fmt, type CalcResult, type Fit } from './calc' import { track } from './analytics' import { pageFromPath, pageMeta, pageCfg, SeoSection } from './seo' // Design knobs — values match the mockup's exported defaults (Tokens Simulator v3). const CONFIG = { feelEffect: 'gradient' as 'retype' | 'wave' | 'shimmer' | 'breathe' | 'gradient' | 'sparks' | 'none', resetIcon: 'home' as 'home' | 'plus' | 'new-chat' | 'refresh', shareStyle: 'plain' as 'coral-solid' | 'coral-outline' | 'ink-solid' | 'plain', comparePlacement: 'config-row' as 'stats' | 'config-row' | 'composer' | 'navbar', runButtonColor: 'coral' as 'black' | 'coral' | 'graphite', fitBadgeStyle: 'hidden' as 'chip' | 'minimal' | 'solid' | 'meter' | 'hidden', showCompare: true, showShare: false, } const REPO = 'nicorobinreal/llmspeed' // search landing page this URL maps to (e.g. /llama-3.1-8b-on-rtx-4090/); 'home' for the root const PAGE = typeof location !== 'undefined' ? pageFromPath(location.pathname) : pageFromPath('/') const PAGE_META = pageMeta(PAGE) // below this the count reads as anti-social-proof, so the pills just say "Star" const STAR_COUNT_MIN = 50 const STAR_ICON = // catalog GPUs Runpod rents by the hour (checked against Runpod's GPU list, Oct 2026) const RUNPOD_REF = 'https://runpod.io?ref=y0hc0qce' const RUNPOD_HW = new Set(['rtx-3090', 'rtx-4090', 'rtx-5080', 'rtx-5090', 'rtx-pro-6000-blackwell', 'h100-80gb', 'h200-141gb']) const RUNPOD_LOGO = const fmtStars = (n: number) => n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1).replace(/\.0$/, '')}k` : `${n}` interface Cfg { hw: string; model: string; quant: string } interface Msg { role: 'user' | 'assistant'; text: string; meta?: string; hw?: string } interface LaneSt { phase: 'norun' | 'prefill' | 'stream' | 'done'; prefillT: number; text: string; liveTok: number; finishT: number; winner: boolean } interface St { cfgs: Cfg[]; preset: number; custom: string | null; phase: 'idle' | 'running' | 'done' streamText: string; liveTok: number; prefillT: number; breakdownOpen: boolean; history: Msg[] dd: string | null; tip: string | null; hwSort: 'pop' | 'ram'; modelSort: 'pop' | 'size' lanes: LaneSt[]; racePrompt: string; shared: boolean; dark: boolean feelPhase: 'type' | 'hold' | 'erase'; feelN: number; feelT: number isMobile: boolean; cfgOpen: boolean; composerH: number /** a run=1 share link was opened: render the conversation view immediately, run() fires shortly */ pendingShare: boolean /** GitHub stargazer count; null until fetched (or if the API is unavailable) */ stars: number | null /** one-time "star the repo" modal after the visitor's first finished run */ } interface RunLane { hw: Hw; m: Model; q: Quant; c: CalcResult; norun: boolean; acc: number; i: number; done: boolean; streamStarted: boolean; liveTok: number; finishT: number } type OptRow = | { hdr: true; label: string; hdrStyle: string; dotStyle: string } | { hdr: false; name: string; meta: string; sel: boolean; pick: () => void; style: string } const EMPTY_LANE: LaneSt = { phase: 'prefill', prefillT: 0, text: '', liveTok: 0, finishT: 0, winner: false } export class App extends Component<{}, St> { state: St = (() => { const base: St = { cfgs: [{ hw: 'rtx-4090', model: 'llama-3.1-8b', quant: 'q4_k_m' }], preset: 0, custom: null, phase: 'idle', streamText: '', liveTok: 0, prefillT: 0, breakdownOpen: false, history: [], dd: null, tip: null, hwSort: 'pop', modelSort: 'pop', lanes: [], racePrompt: '', shared: false, // index.html picks the theme before first paint (saved choice, else system setting) dark: typeof document !== 'undefined' && document.documentElement.classList.contains('theme-dark'), feelPhase: 'type', feelN: 0, feelT: 0, isMobile: typeof matchMedia !== 'undefined' && matchMedia('(max-width:640px)').matches, cfgOpen: false, composerH: 140, pendingShare: false, stars: null, } const pc = pageCfg(PAGE, base.cfgs[0]) if (pc) base.cfgs = [pc] // share links are applied before the first render so the hero never flashes try { const qs = new URLSearchParams(location.search) if (qs.has('hw') || qs.has('model')) { const hws = (qs.get('hw') || '').split(',').map(canonHw), models = (qs.get('model') || '').split(',').map(canonModel), quants = (qs.get('quant') || '').split(',').map(canonQuant) const n = Math.min(2, Math.max(hws.length, models.length, quants.length)) const cfgs: Cfg[] = [] for (let i = 0; i < n; i++) { const cfg = { hw: hws[i] || hws[0], model: models[i] || models[0], quant: quants[i] || quants[0] } const cm = MODELS.find(m => m.id === cfg.model) if (HW.some(h => h.id === cfg.hw) && cm && cm.quants[cfg.quant]) cfgs.push(cfg) } if (cfgs.length) base.cfgs = cfgs const p = qs.get('preset') if (p !== null && PRESETS[+p]) base.preset = +p const t = qs.get('prompt') if (t) base.custom = t.slice(0, 2000) if (qs.get('run') === '1') { base.pendingShare = true const text = base.custom !== null ? base.custom : PRESETS[base.preset].text if (base.cfgs.length === 1) base.history = [{ role: 'user', text }] else base.racePrompt = text } } } catch (e) { } return base })() timer?: ReturnType feelTimer?: ReturnType runCfg: { lanes: RunLane[]; words: string[]; tokIn: number; text: string } | null = null t0 = 0 _last = 0 _winner: RunLane | null = null _shT?: ReturnType _doc?: () => void _scrollQueued = false _sc: HTMLElement | null = null _mq?: MediaQueryList _mqL?: () => void _root: HTMLElement | null = null _rootRef = (el: HTMLElement | null) => { this._root = el } // clamps a just-opened dropdown/tooltip into the viewport so it can't cause horizontal scroll _menuRef = (el: HTMLElement | null) => { if (!el) return const pad = 8 const r = el.getBoundingClientRect() let dx = 0 if (r.right > window.innerWidth - pad) dx = window.innerWidth - pad - r.right if (r.left + dx < pad) dx = pad - r.left if (dx) el.style.marginLeft = dx + 'px' } _syncScroll = (hero: boolean) => { const had = document.documentElement.classList.contains('doc-scroll') document.documentElement.classList.toggle('doc-scroll', hero) if (had && !hero) window.scrollTo(0, 0) } heroNow() { const s = this.state const busy = s.phase === 'running' const raceActive = s.cfgs.length > 1 && (busy || s.phase === 'done') && s.lanes.length > 1 return !raceActive && s.history.length === 0 && !busy && !s.pendingShare } // a plain class (NOT data-dark: that would pull html/body into the [data-dark] transition rules // and cause chained/laggy fades) themes the document — safe areas and Safari's toolbar tint _syncDark = (d: boolean) => { document.documentElement.classList.toggle('theme-dark', d) const meta = document.querySelector('meta[name="theme-color"]') if (meta) meta.setAttribute('content', d ? '#111114' : '#ffffff') } _vv?: () => void _sysDark?: MediaQueryList _sysDarkL?: () => void _ro?: ResizeObserver // stable ref callback: measures the floating composer so the conversation gets matching bottom padding _compRef = (el: HTMLElement | null) => { if (this._ro) { this._ro.disconnect(); this._ro = undefined } if (el) { this._ro = new ResizeObserver(() => { const h = el.offsetHeight if (Math.abs(h - this.state.composerH) > 1) this.setState({ composerH: h }) }) this._ro.observe(el) } } resolve(cfg: Cfg) { return { hw: HW.find(h => h.id === cfg.hw)!, m: MODELS.find(m => m.id === cfg.model)!, q: quantOf(cfg.quant) } } promptTokens() { const s = this.state return s.custom !== null ? Math.max(8, Math.ceil(s.custom.length / 3.6)) : PRESETS[s.preset].tok } isCompare() { return this.state.cfgs.length > 1 } groupOpts(sorted: T[], fitOf: (x: T) => Fit, mapFn: (x: T) => { name: string; meta: string; sel: boolean; pick: () => void }): OptRow[] { const groups: Record = { fits: [], partial: [], no: [] } sorted.forEach(x => groups[fitOf(x)].push(x)) const meta: Record = { fits: ['Runs comfortably', 'oklch(0.55 0.15 155)'], partial: ['Partial offload · slow', 'oklch(0.68 0.13 85)'], no: ["Won't run", 'oklch(0.55 0.19 25)'] } const out: OptRow[] = [] for (const g of ['fits', 'partial', 'no'] as Fit[]) { if (!groups[g].length) continue out.push({ hdr: true, label: meta[g][0], hdrStyle: 'display:flex; align-items:center; gap:7px; padding:9px 10px 4px; font-size:10.5px; font-weight:600; letter-spacing:0.06em; text-transform:uppercase; color:var(--c-text-faint)', dotStyle: `width:7px; height:7px; border-radius:50%; background:${meta[g][1]}; display:inline-block`, }) groups[g].forEach(x => { const o = mapFn(x) out.push({ hdr: false, ...o, style: `display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; text-align:left; border:none; background:${o.sel ? 'var(--c-bg-muted)' : 'none'}; border-radius:9px; padding:8px 10px; font-size:13px; cursor:pointer; color:${g === 'no' ? 'var(--c-text-faint)' : 'var(--c-text)'}`, }) }) } return out } componentDidMount() { this._doc = () => { if (this.state.dd) this.setState({ dd: null }) } document.addEventListener('click', this._doc) this._mq = matchMedia('(max-width:640px)') this._mqL = () => this.setState({ isMobile: this._mq!.matches }) if (this._mq.addEventListener) this._mq.addEventListener('change', this._mqL) else (this._mq as MediaQueryList & { addListener(l: () => void): void }).addListener(this._mqL) // pin the app to the visual viewport while the on-screen keyboard is open (iOS Safari // resizes only the visual viewport, which would leave the composer hidden behind the keyboard) if (window.visualViewport) { this._vv = () => { const vv = window.visualViewport! if (!this._root) return if (window.innerHeight - vv.height > 60) { this._root.style.height = vv.height + 'px'; window.scrollTo(0, 0) } else this._root.style.height = '' } window.visualViewport.addEventListener('resize', this._vv) } // follow live system theme changes until the visitor picks one with the toggle this._sysDark = matchMedia('(prefers-color-scheme: dark)') this._sysDarkL = () => { try { if (localStorage.getItem('dryrun-theme')) return } catch (e) { } this.setState({ dark: this._sysDark!.matches }) } if (this._sysDark.addEventListener) this._sysDark.addEventListener('change', this._sysDarkL) this.loadStars() this._syncDark(this.state.dark) this._syncScroll(this.heroNow()) this.feelTimer = setInterval(() => { if (CONFIG.feelEffect !== 'retype') return this.setState(x => { const word = 'feel', ph = x.feelPhase ?? 'type', n = x.feelN ?? 0 if (ph === 'type') return n < word.length ? { feelN: n + 1 } : { feelPhase: 'hold' as const, feelN: n, feelT: 0 } if (ph === 'hold') return (x.feelT ?? 0) > 5 ? { feelPhase: 'erase' as const } : { feelT: (x.feelT ?? 0) + 1 } return n > 0 ? { feelN: n - 1 } : { feelPhase: 'type' as const, feelN: 0 } }) }, 260) if (this.state.pendingShare) { // question bubble animates in first, then the simulation starts setTimeout(() => { if (this.state.cfgs.length === 1) this._replayHistory = [] // run() adds the user message itself this.setState({ pendingShare: false }) this.run() }, 900) return } // legacy share links: base64 config in the #s= hash try { const hm = location.hash.match(/s=([^&]+)/) if (hm) { const d = JSON.parse(atob(decodeURIComponent(hm[1]))) const patch: Partial = {} if (Array.isArray(d.c) && d.c.length) { const cf = (d.c as Cfg[]) .map(x => ({ hw: canonHw(x.hw), model: canonModel(x.model), quant: canonQuant(x.quant) })) .filter(x => { const cm = MODELS.find(m => m.id === x.model); return HW.some(h => h.id === x.hw) && cm && cm.quants[x.quant] }) .slice(0, 2) if (cf.length) patch.cfgs = cf } if (typeof d.p === 'number' && PRESETS[d.p]) patch.preset = d.p if (typeof d.t === 'string' && d.t) patch.custom = d.t.slice(0, 2000) this.setState(patch, () => { if (d.r) setTimeout(() => this.run(), 600) }) } } catch (e) { } } componentDidUpdate(_prevProps: {}, prevState: St) { if (prevState.dark !== this.state.dark) this._syncDark(this.state.dark) this._syncScroll(this.heroNow()) // keep scrolling through the final running→done render so the appended meta line stays in view // (but not on running→idle, i.e. reset) if ((this.state.phase === 'running' || (prevState.phase === 'running' && this.state.phase === 'done')) && !this._scrollQueued) { this._scrollQueued = true requestAnimationFrame(() => { this._scrollQueued = false const d = this._sc if (!d) return const target = d.scrollHeight - d.clientHeight if (Math.abs(d.scrollTop - target) >= 1) d.scrollTop = target }) } } componentWillUnmount() { clearInterval(this.timer); clearInterval(this.feelTimer) if (this._doc) document.removeEventListener('click', this._doc) if (this._mq && this._mqL) { if (this._mq.removeEventListener) this._mq.removeEventListener('change', this._mqL) else (this._mq as MediaQueryList & { removeListener(l: () => void): void }).removeListener(this._mqL) } if (this._vv && window.visualViewport) window.visualViewport.removeEventListener('resize', this._vv) if (this._sysDark && this._sysDarkL && this._sysDark.removeEventListener) this._sysDark.removeEventListener('change', this._sysDarkL) if (this._ro) this._ro.disconnect() } feelWordEl(): ComponentChild { const v = CONFIG.feelEffect if (v === 'retype') { const n = this.state.feelN ?? 0 return {'feel'.slice(0, n)} } if (v === 'wave') return {'feel'.split('').map((ch, i) => {ch})} if (v === 'shimmer') return feel if (v === 'breathe') return feel if (v === 'gradient') return feel if (v === 'sparks') { const spark = (x: string, y: string, sz: number, delay: number, dur: number) => ( ) return feel{spark('-14px', '-8px', 11, 0, 2.4)}{spark('96%', '-12px', 14, 0.9, 3.1)}{spark('102%', '60%', 9, 1.7, 2.7)}{spark('-8px', '70%', 8, 2.3, 3.4)} } return feel } run() { const replayH = this._replayHistory this._replayHistory = null const s = this.state, ctx = this.promptTokens() track('run', { mode: s.cfgs.length > 1 ? 'race' : 'single', hw: s.cfgs.map(c => c.hw).join(','), model: s.cfgs.map(c => c.model).join(','), quant: s.cfgs.map(c => c.quant).join(','), prompt: s.custom !== null ? 'custom' : PRESETS[s.preset].label }) const lanes: RunLane[] = s.cfgs.map(cfg => { const { hw, m, q } = this.resolve(cfg) const c = calc(hw, m, q, ctx) return { hw, m, q, c, norun: c.fit === 'no', acc: 0, i: 0, done: c.fit === 'no', streamStarted: false, liveTok: c.base, finishT: 0 } }) if (lanes.every(l => l.c.fit === 'no')) return const pIdx = s.custom !== null ? 0 : s.preset const words = RESP[pIdx].split(' ') const text = s.custom !== null ? s.custom : PRESETS[s.preset].text this.runCfg = { lanes, words, tokIn: ctx, text } this.t0 = performance.now(); this._last = this.t0; this._winner = null const st: Partial = { phase: 'running', cfgOpen: false, lanes: lanes.map((l): LaneSt => ({ phase: l.norun ? 'norun' : 'prefill', prefillT: 0, text: '', liveTok: 0, finishT: 0, winner: false })), } if (!this.isCompare()) Object.assign(st, { history: [...(replayH ?? s.history), { role: 'user' as const, text }], streamText: '', prefillT: 0, liveTok: 0 }) else st.racePrompt = text this.setState(st) clearInterval(this.timer) this.timer = setInterval(() => this.tick(), 50) } tick() { const now = performance.now(), el = (now - this.t0) / 1000, dt = (now - this._last) / 1000 this._last = now const { lanes, words } = this.runCfg! let allDone = true const stLanes = lanes.map((l, idx): LaneSt => { const prev = this.state.lanes[idx] ?? EMPTY_LANE if (l.norun) return prev if (l.done) return prev if (!l.streamStarted) { if (el >= l.c.ttft) { l.streamStarted = true } else { allDone = false; return { ...prev, phase: 'prefill', prefillT: el } } } const jit = 0.72 + Math.random() * 0.56 l.acc += l.c.base * 0.72 * dt * jit const n = Math.floor(l.acc) if (n > 0) { l.acc -= n; l.i = Math.min(words.length, l.i + n); l.liveTok = l.c.base * jit } if (l.i >= words.length) { l.done = true; l.finishT = el const winner = !this._winner; if (winner) this._winner = l return { phase: 'done', text: words.join(' '), liveTok: 0, finishT: el, winner, prefillT: 0 } } allDone = false return { phase: 'stream', text: words.slice(0, l.i).join(' '), liveTok: l.liveTok, prefillT: 0, finishT: 0, winner: false } }) const st: Partial = { lanes: stLanes } if (!this.isCompare()) { const sl = stLanes[0] st.prefillT = sl.phase === 'prefill' ? sl.prefillT : 0 st.streamText = sl.phase === 'stream' ? sl.text : '' st.liveTok = sl.liveTok || 0 } if (allDone) { clearInterval(this.timer) st.phase = 'done' if (!this.isCompare()) { const l = lanes[0] const meta = `${l.hw.name} · ${l.m.name} · ${l.q.label} · ${fmt(l.c.base)} tok/s · TTFT ${l.c.ttft < 10 ? l.c.ttft.toFixed(1) : Math.round(l.c.ttft)}s` st.history = [...this.state.history, { role: 'assistant' as const, text: words.join(' '), meta, hw: l.hw.id }] st.streamText = '' } } this.setState(st) } stop() { clearInterval(this.timer) if (this.isCompare()) { this.setState(s => ({ phase: 'done' as const, lanes: s.lanes.map(l => l.phase === 'stream' || l.phase === 'prefill' ? { ...l, phase: 'done' as const } : l) })) } else { const s = this.state if (s.streamText) { const l = this.runCfg!.lanes[0] const meta = `${l.hw.name} · ${l.m.name} · ${l.q.label} · ${fmt(l.c.base)} tok/s · stopped` this.setState(x => ({ phase: 'done' as const, history: [...x.history, { role: 'assistant' as const, text: x.streamText, meta, hw: l.hw.id }], streamText: '' })) } else this.setState({ phase: 'idle' }) } } // set by replay() so run() applies the trimmed history in the same state update — a separate // setState would leave history momentarily empty and flash the hero screen _replayHistory: Msg[] | null = null replay() { if (this.state.phase === 'running' || !this.runCfg) return if (!this.isCompare()) { const h = [...this.state.history] if (h.length && h[h.length - 1].role === 'user') h.pop() else if (h.length >= 2) { h.pop(); h.pop() } this._replayHistory = h } this.run() } // star count is cached for an hour: the unauthenticated GitHub API allows 60 req/h per IP loadStars() { try { const c = JSON.parse(localStorage.getItem('llmspeed-stars') || 'null') if (c && Date.now() - c.t < 3600e3) { this.setState({ stars: c.n }); return } } catch (e) { } fetch(`https://api.github.com/repos/${REPO}`).then(r => r.ok ? r.json() : null).then(d => { if (!d || typeof d.stargazers_count !== 'number') return this.setState({ stars: d.stargazers_count }) try { localStorage.setItem('llmspeed-stars', JSON.stringify({ n: d.stargazers_count, t: Date.now() })) } catch (e) { } }).catch(() => { }) } share() { const s = this.state const q = new URLSearchParams() q.set('hw', s.cfgs.map(c => c.hw).join(',')) q.set('model', s.cfgs.map(c => c.model).join(',')) q.set('quant', s.cfgs.map(c => c.quant).join(',')) if (s.custom !== null) q.set('prompt', s.custom.slice(0, 2000)) else q.set('preset', String(s.preset)) q.set('run', '1') q.set('utm_source', 'share') track('share') const url = 'https://llmspeed.dev/?' + q.toString().replace(/%2C/g, ',') ;(navigator.clipboard ? navigator.clipboard.writeText(url) : Promise.reject()).catch(() => { }) this.setState({ shared: true }) clearTimeout(this._shT) this._shT = setTimeout(() => this.setState({ shared: false }), 1600) } cfgRow(i: number) { const s = this.state, cfg = s.cfgs[i], { hw, m, q } = this.resolve(cfg), ctx = this.promptTokens() const c = calc(hw, m, q, ctx) const compare = this.isCompare() const setCfg = (patch: Partial) => this.setState(x => ({ cfgs: x.cfgs.map((cc, j) => { if (j !== i) return cc const next = { ...cc, ...patch } // a newly picked model may lack the selected quant — fall back to its smallest const nm = MODELS.find(mm => mm.id === next.model)! if (!nm.quants[next.quant]) next.quant = quantsFor(nm)[0].id return next }), dd: null, })) const dd = (k: string) => k + i const fitColors: Record = { fits: 'oklch(0.55 0.15 155)', partial: 'oklch(0.68 0.13 85)', no: 'oklch(0.55 0.19 25)' } const fitLabels: Record = { fits: `Fits in ${c.usable.toFixed(0)} GB`, partial: 'Partial offload', no: "Won't run" } const fc = fitColors[c.fit] void fc const sortBtn = (act: boolean) => `border-radius:99px; padding:3px 11px; font-size:11.5px; cursor:pointer; ${act ? 'background:var(--c-ink); border:1px solid var(--c-ink); color:var(--c-bg)' : 'background:none; border:1px solid var(--c-border); color:var(--c-text-muted)'}` return { showPreset: i === 0, showBadge: compare, rowBadge: i ? 'B' : 'A', rowBadgeStyle: `width:20px; height:20px; border-radius:6px; background:${i ? 'var(--c-badge-b-bg)' : 'var(--c-bg-muted)'}; color:${i ? 'var(--c-badge-b)' : 'var(--c-text-sec)'}; font-size:11px; font-weight:700; display:inline-flex; align-items:center; justify-content:center; flex-shrink:0`, hwName: hw.name, modelName: m.name, quantName: q.label, hwDdOpen: s.dd === dd('hw'), modelDdOpen: s.dd === dd('model'), quantDdOpen: s.dd === dd('quant'), toggleHwDd: (e: Event) => { e.stopPropagation(); this.setState(x => ({ dd: x.dd === dd('hw') ? null : dd('hw') })) }, toggleModelDd: (e: Event) => { e.stopPropagation(); this.setState(x => ({ dd: x.dd === dd('model') ? null : dd('model') })) }, toggleQuantDd: (e: Event) => { e.stopPropagation(); this.setState(x => ({ dd: x.dd === dd('quant') ? null : dd('quant'), tip: null })) }, hwSortBtns: ([['pop', 'Popular'], ['ram', 'RAM']] as ['pop' | 'ram', string][]).map(([id, label]) => ({ label, pick: (e: Event) => { e.stopPropagation(); this.setState({ hwSort: id }) }, style: sortBtn(s.hwSort === id), })), modelSortBtns: ([['pop', 'Popular'], ['size', 'Size']] as ['pop' | 'size', string][]).map(([id, label]) => ({ label, pick: (e: Event) => { e.stopPropagation(); this.setState({ modelSort: id }) }, style: sortBtn(s.modelSort === id), })), hwOpts: this.groupOpts( [...HW].sort((a, b) => s.hwSort === 'ram' ? b.vram_gb - a.vram_gb || a.popularity_rank - b.popularity_rank : a.popularity_rank - b.popularity_rank), h => calc(h, m, q, ctx).fit, h => ({ name: h.name, meta: `${h.vram_gb} GB${h.status === 'announced' ? ' · announced' : ''}`, sel: h.id === cfg.hw, pick: () => setCfg({ hw: h.id }) })), modelOpts: this.groupOpts( [...MODELS].sort((a, b) => s.modelSort === 'size' ? a.total_params_b - b.total_params_b || a.popularity_rank - b.popularity_rank : a.popularity_rank - b.popularity_rank), mm => calc(hw, mm, quantOf(mm.quants[q.id] ? q.id : quantsFor(mm)[0].id), ctx).fit, mm => ({ name: mm.name, meta: `${mm.total_params_b} B`, sel: mm.id === cfg.model, pick: () => setCfg({ model: mm.id }) })), quantOpts: quantsFor(m).map(qq => ({ name: qq.label, meta: qq.id === 'q4_k_m' ? 'small · fast' : qq.id === 'q8_0' ? 'balanced' : qq.id === 'fp16' ? 'full quality' : `${m.quants[qq.id].file_gb.toFixed(1)} GB`, pick: () => setCfg({ quant: qq.id }), style: `display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; text-align:left; border:none; background:${qq.id === cfg.quant ? 'var(--c-bg-muted)' : 'none'}; border-radius:9px; padding:8px 10px; font-size:13px; cursor:pointer; color:var(--c-text)`, })), showQuantTip: i === 0, tipQuant: s.tip === 'quant' && s.dd !== dd('quant'), tipQuantOn: () => this.setState({ tip: 'quant' }), quantHelp: `Quantization shrinks the model to fewer bits per weight. Smaller = less memory, faster, slightly lower quality. ${q.label} → ${c.w.toFixed(1)} GB.`, fitLabel: fitLabels[c.fit], fitIsFits: c.fit === 'fits', fitIsPartial: c.fit === 'partial', fitIsNo: c.fit === 'no', showFitBadge: CONFIG.fitBadgeStyle !== 'hidden', showMeter: CONFIG.fitBadgeStyle === 'meter', meterFillStyle: `display:block; height:100%; border-radius:3px; width:${Math.min(100, c.total / c.usable * 100).toFixed(0)}%; background:${c.fit === 'no' ? 'var(--c-ink)' : 'var(--c-text-muted)'}`, fitChipStyle: (() => { const v = CONFIG.fitBadgeStyle const base = 'position:relative; display:inline-flex; align-items:center; gap:6px; font-size:12.5px; font-weight:500; white-space:nowrap; flex-shrink:0; cursor:help; margin-left:auto' if (v === 'minimal') return `${base}; color:${c.fit === 'no' ? 'var(--c-ink)' : 'var(--c-text-muted)'}; padding:7px 2px` if (v === 'solid') return `${base}; color:${c.fit === 'no' ? 'var(--c-bg)' : 'var(--c-text-strong)'}; background:${c.fit === 'no' ? 'var(--c-ink)' : 'var(--c-bg-muted)'}; border:none; border-radius:99px; padding:8px 14px` if (v === 'meter') return `${base}; color:var(--c-text-sec); background:none; border:1px solid var(--c-border); border-radius:99px; padding:7px 13px` return `${base}; color:${c.fit === 'no' ? 'var(--c-ink)' : 'var(--c-text-sec)'}; background:${c.fit === 'no' ? 'var(--c-bg-muted)' : 'var(--c-bg-subtle)'}; border:1px solid ${c.fit === 'no' ? 'var(--c-border-strong)' : 'var(--c-border)'}; border-radius:99px; padding:7px 13px` })(), tipFit: s.tip === 'fit' + i, tipFitOn: () => this.setState({ tip: 'fit' + i }), fitHelp: c.fit === 'fits' ? `Model + context (${c.total.toFixed(1)} GB) fits in the ${c.usable.toFixed(1)} GB of usable memory — full speed.` : c.fit === 'partial' ? `Needs ${c.total.toFixed(1)} GB, only ${c.usable.toFixed(1)} GB fits on the GPU. ~${Math.round((1 - c.gpuShare) * 100)}% of the weights are served from system RAM instead — that share streams at RAM speed and caps decode.` : `Needs ${c.total.toFixed(1)} GB; only ${c.usable.toFixed(1)} GB available. Doesn't fit, can't run.`, showRemove: compare && i === 1, showAddRace: !compare && CONFIG.showCompare && CONFIG.comparePlacement === 'config-row', _c: c, _hw: hw, _m: m, _q: q, } } renderVals() { const s = this.state, ctx = this.promptTokens() const compare = this.isCompare() const rows = s.cfgs.map((_, i) => this.cfgRow(i)) const c0 = rows[0]._c const busy = s.phase === 'running' const accent = 'oklch(0.62 0.17 30)' const confMap: Record = { measured: ['Measured', 'oklch(0.55 0.15 155)'], derived: ['Derived', 'oklch(0.68 0.13 85)'], projected: ['Projected', 'oklch(0.66 0.16 55)'] } const statRows = rows.map((r, i) => { const c = r._c, conf = c.conf ? confMap[c.conf] : ['—', 'var(--c-border-strong)'] return { showBadge: compare, badge: i ? 'B' : 'A', badgeStyle: `width:17px; height:17px; border-radius:5px; background:${i ? 'var(--c-badge-b-bg)' : 'var(--c-bg-muted)'}; color:${i ? 'var(--c-badge-b)' : 'var(--c-text-sec)'}; font-size:10px; font-weight:700; display:inline-flex; align-items:center; justify-content:center`, range: c.fit === 'no' ? '—' : `${fmt(c.lo)}–${fmt(c.hi)}`, ttft: c.fit === 'no' ? '—' : (c.ttft < 10 ? c.ttft.toFixed(2).replace(/0$/, '') : String(Math.round(c.ttft))) + ' s', confLabel: conf[0], confDotStyle: `width:8px; height:8px; border-radius:50%; background:${conf[1]}; display:inline-block`, showTips: i === 0 && !compare, } }) const mkRows = (r: ReturnType) => { const c = r._c, m = r._m, hw = r._hw, q = r._q return c.fit === 'no' ? [ { n: '1', f: `Weights: ${m.total_params_b} B params @ ${q.label} (file size)`, v: `${c.w.toFixed(1)} GB` }, { n: '2', f: `KV cache @ ${ctx.toLocaleString()} ctx tokens (${m.kv_gb_per_8k} GB per 8k)`, v: `${c.kv.toFixed(1)} GB` }, { n: '3', f: `Total vs usable memory (${hw.vendor === 'apple' ? '75% of unified' : '93% of VRAM'})`, v: `${c.total.toFixed(1)} / ${c.usable.toFixed(1)} GB` }, { n: '4', f: 'Result', v: "won't run" }, ] : [ { n: '1', f: `Weights: ${m.total_params_b} B params @ ${q.label} (file size)`, v: `${c.w.toFixed(1)} GB` }, { n: '2', f: `KV cache @ ${ctx.toLocaleString()} ctx tokens (${m.kv_gb_per_8k} GB per 8k)`, v: `${c.kv.toFixed(1)} GB` }, { n: '3', f: `Fit: ${c.total.toFixed(1)} GB needed vs ${c.usable.toFixed(1)} GB usable`, v: c.fit === 'fits' ? 'fits' : 'partial offload' }, { n: '4', f: c.fit === 'partial' ? `Decode: hybrid offload — ${Math.round(c.gpuShare * 100)}% of active weights read at GPU speed (${hw.bandwidth_gbs} GB/s × ${c.eff}), ${Math.round((1 - c.gpuShare) * 100)}% at system-RAM speed (~${OFFLOAD.ram_bw_gbs} GB/s)` : c.fromCombo ? 'Decode: benchmark range for this combo × context derating' : `Decode ≈ bandwidth × efficiency ÷ active weights: ${hw.bandwidth_gbs} GB/s × ${c.eff} ÷ ${c.aw.toFixed(1)} GB`, v: `${fmt(c.base)} tok/s` }, { n: '5', f: c.fit === 'partial' ? 'Spread: calibrated from measured offload runs (RAM speeds vary)' : c.fromCombo ? 'Spread: holdout-validated interval' : 'Spread ±15% (engine, thermals, runtime)', v: `${fmt(c.lo)}–${fmt(c.hi)} tok/s` }, { n: '6', f: `Prefill ≈ ${Math.round(c.pre).toLocaleString()} tok/s → ${ctx.toLocaleString()} ÷ that + overhead`, v: `TTFT ${c.ttft < 10 ? c.ttft.toFixed(1) : Math.round(c.ttft)} s` }, ] } const breakdownSecs = rows.map((r, i) => ({ showLabel: compare, label: `${i ? 'B' : 'A'} — ${r._hw.name} · ${r._m.name} · ${r._q.label}`, rows: mkRows(r) })) let suggShow = false, suggLabel = '', suggQ: string | null = null if (!compare && c0.fit !== 'fits') { const { hw, m, q } = this.resolve(s.cfgs[0]) const curGb = m.quants[q.id] ? m.quants[q.id].file_gb : Infinity for (const alt of quantsFor(m)) { if (m.quants[alt.id].file_gb >= curGb) continue const ac = calc(hw, m, alt, ctx) if (ac.fit === 'fits') { suggShow = true; suggQ = alt.id; suggLabel = `Switch to ${alt.label} — fits, ~${fmt(ac.lo)}–${fmt(ac.hi)} tok/s`; break } } } const raceActive = compare && (((busy || s.phase === 'done') && s.lanes.length > 1) || s.pendingShare) const heroMode = !raceActive && s.history.length === 0 && !busy && !s.pendingShare const chatMode = !compare && !heroMode // mobile config collapse only applies once in a conversation; the hero keeps inline chips const collapse = s.isMobile && !heroMode const wait = this.runCfg && this.runCfg.lanes[0] ? this.runCfg.lanes[0].c.ttft : 1 const prefillFrac = Math.min(1, s.prefillT / Math.max(0.01, wait)) const pStatus = (f: number) => f < 0.22 ? 'Loading weights' : f < 0.45 ? 'Allocating KV cache' : `Prefilling ${(this.runCfg ? this.runCfg.tokIn : ctx).toLocaleString()} tokens` const raceLanes = (this.runCfg && raceActive ? this.runCfg.lanes : []).map((l, i) => { const sl = s.lanes[i] || EMPTY_LANE const won = sl.winner return { badge: i ? 'B' : 'A', badgeStyle: `width:20px; height:20px; border-radius:6px; background:${i ? 'var(--c-badge-b-bg)' : 'var(--c-bg-muted)'}; color:${i ? 'var(--c-badge-b)' : 'var(--c-text-sec)'}; font-size:11px; font-weight:700; display:inline-flex; align-items:center; justify-content:center; flex-shrink:0`, title: `${l.hw.name} · ${l.m.name} · ${l.q.label}`, cardStyle: `border:1px solid ${won ? 'oklch(0.62 0.17 30 / .45)' : 'var(--c-border)'}; border-radius:16px; background:var(--c-bg); overflow:hidden; ${won ? 'box-shadow:0 4px 20px oklch(0.62 0.17 30 / .12)' : ''}`, norun: sl.phase === 'norun', norunText: `Won't run: needs ${l.c.total.toFixed(1)} GB, has ${l.c.usable.toFixed(1)} GB usable. Sits this race out.`, prefill: sl.phase === 'prefill', spinner: SPIN[Math.floor((sl.prefillT || 0) * 10) % 10], prefillStatus: pStatus(Math.min(1, (sl.prefillT || 0) / Math.max(0.01, l.c.ttft))), clock: `${(sl.prefillT || 0).toFixed(1)}s`, showText: sl.phase === 'stream' || sl.phase === 'done', rpHw: sl.phase === 'done' && RUNPOD_HW.has(l.hw.id) ? l.hw.id : '', text: sl.text || '', cursor: sl.phase === 'stream', status: sl.phase === 'norun' ? "won't run" : sl.phase === 'prefill' ? 'prefill…' : sl.phase === 'stream' ? `${fmt(sl.liveTok || 0)} tok/s` : `${won ? 'finished first · ' : ''}${(sl.finishT || 0).toFixed(1)}s`, statusColor: sl.phase === 'done' ? (won ? 'oklch(0.52 0.17 30)' : 'var(--c-text-muted)') : sl.phase === 'norun' ? 'oklch(0.55 0.19 25)' : 'oklch(0.62 0.17 30)', } }) const runnable = rows.some(r => r._c.fit !== 'no') return { feelWord: this.feelWordEl(), showReset: !heroMode, resetIconHome: CONFIG.resetIcon === 'home', resetIconPlus: CONFIG.resetIcon === 'plus', resetIconEdit: CONFIG.resetIcon === 'new-chat', resetIconRefresh: CONFIG.resetIcon === 'refresh', resetAll: () => { clearInterval(this.timer); this.runCfg = null; if (this._sc) this._sc.scrollTop = 0; this.setState({ phase: 'idle', history: [], lanes: [], racePrompt: '', streamText: '', prefillT: 0, liveTok: 0, custom: null, breakdownOpen: false, cfgOpen: false }) }, darkAttr: s.dark ? '1' : '0', isDark: s.dark, isLight: !s.dark, toggleDark: () => this.setState(x => { const d = !x.dark try { localStorage.setItem('dryrun-theme', d ? 'dark' : 'light') } catch (e) { } return { dark: d } }), maxW: compare ? '980px' : '780px', // hero: the document scrolls (native iOS pull-to-refresh); root/scroller are plain flow. // conversation: fixed-height app shell with an inner scroller (browser chrome stays put). rootStyle: heroMode ? 'display:flex; flex-direction:column; position:relative; background:var(--c-bg); color:var(--c-ink); font-family:\'Instrument Sans\',sans-serif' : 'display:flex; flex-direction:column; overflow:hidden; position:relative; background:var(--c-bg); color:var(--c-ink); font-family:\'Instrument Sans\',sans-serif', scrollerStyle: heroMode ? 'flex:1; display:flex; flex-direction:column' : 'flex:1; min-height:0; overflow-y:scroll; overflow-x:hidden', // mobile hero is slightly taller than the viewport so it scrolls a touch and sits ~20px lower wrapperStyle: heroMode ? (s.isMobile ? 'min-height:calc(100vh + 40px); min-height:calc(100dvh + 40px); display:flex; flex-direction:column' : 'min-height:100vh; min-height:100dvh; display:flex; flex-direction:column') : 'min-height:100%; display:flex; flex-direction:column', heroMode, chatMode, raceMode: raceActive, collapse, cfgOpen: s.cfgOpen, toggleCfgPanel: () => this.setState(x => ({ cfgOpen: !x.cfgOpen })), heroWrapStyle: 'margin-top:auto; text-align:center; padding-bottom:34px', // the decorative bottom spacer overflows short phone viewports and breaks hero centering showHeroSpacer: heroMode && !s.isMobile, composerWrapStyle: heroMode ? 'margin-bottom:auto; padding:8px 0 12px; position:relative' : 'position:absolute; bottom:0; left:0; right:0; z-index:10; padding-top:14px', composerRef: heroMode ? undefined : this._compRef, composerInnerClass: heroMode ? undefined : 'page-col', composerInnerStyle: heroMode ? '' : `max-width:${compare ? '980px' : '780px'}; margin:0 auto; padding-bottom:12px; position:relative`, scrollPadBottom: s.composerH + 16, // bottom sheet: a direct child of the fixed-height root (same level as the conversation // scroller, which touch-scrolls reliably on iOS) with pixel-based sizing cfgPanelStyle: `position:absolute; left:16px; right:16px; bottom:${s.composerH + 8}px; max-height:calc(100% - ${s.composerH + 40}px); overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain; background:var(--c-bg); border:1px solid var(--c-border); border-radius:16px; padding:4px 14px 14px; box-shadow:0 10px 30px rgba(0,0,0,.12); z-index:12`, cfgToggleStyle: `display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px; border-radius:99px; border:1px solid ${s.cfgOpen ? 'var(--c-border-strong)' : 'var(--c-border)'}; background:${s.cfgOpen ? 'var(--c-bg-muted)' : 'none'}; color:${s.cfgOpen ? 'var(--c-ink)' : 'var(--c-text-sec)'}; cursor:pointer; padding:0; flex-shrink:0; transition:transform .2s ease; transform:rotate(${s.cfgOpen ? '45deg' : '0deg'})`, breakdownWrapStyle: `border:1px solid var(--c-border-faint); background:var(--c-bg-subtle); border-radius:14px; padding:16px 18px; display:flex; flex-direction:column; gap:14px; ${heroMode ? 'position:absolute; top:calc(100% - 2px); left:0; right:0; z-index:25; box-shadow:0 10px 30px rgba(0,0,0,.08)' : 'margin-top:12px'}`, // dropdowns/tooltips open downward inside the scrollable "+" panel: upward overflow would be // clipped by the scroll container, downward overflow just extends its scroll area menuAnchor: collapse ? 'top:calc(100% + 6px)' : 'bottom:38px', tipAnchorQuant: collapse ? 'top:calc(100% + 6px)' : 'bottom:26px', tipAnchorStat: collapse ? 'top:calc(100% + 6px)' : 'bottom:24px', cfgRows: rows, statRows, breakdownSecs, showCompareBtn: !compare && CONFIG.showCompare && CONFIG.comparePlacement === 'stats', showCompareComposer: !compare && CONFIG.showCompare && CONFIG.comparePlacement === 'composer', showCompareNav: !compare && CONFIG.showCompare && CONFIG.comparePlacement === 'navbar', showShareBtn: CONFIG.showShare, showShareNav: !heroMode, shareNavStyle: (() => { const v = CONFIG.shareStyle const base = 'display:inline-flex; align-items:center; justify-content:center; gap:6px; border-radius:99px; padding:6px 14px; font-size:13px; font-weight:600; cursor:pointer; min-width:92px' if (s.shared) return `${base}; background:oklch(0.62 0.17 30 / .08); border:1px solid oklch(0.62 0.17 30 / .5); color:oklch(0.52 0.17 30)` if (v === 'coral-solid') return `${base}; background:oklch(0.62 0.17 30); border:1px solid oklch(0.62 0.17 30); color:#ffffff` if (v === 'coral-outline') return `${base}; background:oklch(0.62 0.17 30 / .06); border:1px solid oklch(0.62 0.17 30 / .55); color:oklch(0.52 0.17 30)` if (v === 'ink-solid') return `${base}; background:var(--c-ink); border:1px solid var(--c-ink); color:var(--c-bg)` return `${base}; background:none; border:1px solid var(--c-border); color:var(--c-text-sec); font-weight:500` })(), addCompare: () => { track('compare'); this.setState(x => ({ cfgs: [x.cfgs[0], { ...x.cfgs[0], hw: x.cfgs[0].hw === 'm4-pro-24gb' ? 'rtx-4090' : 'm4-pro-24gb' }], lanes: [], racePrompt: '', phase: 'idle' as const })) }, removeCompare: () => this.setState(x => ({ cfgs: [x.cfgs[0]], lanes: [], phase: 'idle' as const })), share: () => this.share(), shareLabel: s.shared ? 'Copied' : 'Share', repoUrl: `https://github.com/${REPO}`, starCount: s.stars !== null && s.stars >= STAR_COUNT_MIN ? fmtStars(s.stars) : '', starClick: (button: 'nav' | 'cta') => track('star-click', { button }), // post-run ask: shown whenever a run has finished showStarCta: s.phase === 'done', runpodUrl: RUNPOD_REF, runpodClick: (hw: string) => track('runpod-click', { hw }), shareBtnStyle: `background:${s.shared ? 'oklch(0.62 0.17 30 / .08)' : 'none'}; border:1px solid ${s.shared ? 'oklch(0.62 0.17 30 / .5)' : 'var(--c-border)'}; color:${s.shared ? 'oklch(0.52 0.17 30)' : 'var(--c-text-sec)'}; border-radius:99px; padding:6px 13px; font-size:13px; font-weight:500; cursor:pointer`, wontRun: !compare && c0.fit === 'no' && !busy && !(collapse && s.cfgOpen), wontRunDetail: (() => { const r = rows[0]; return `${r._m.name} at ${r._q.label} needs ${c0.total.toFixed(1)} GB — the ${r._hw.name} has ${c0.usable.toFixed(1)} GB usable. ${suggShow ? '' : 'No quantization of this model fits; pick a smaller model or more memory.'}` })(), suggShow, suggLabel, applySugg: () => suggQ && this.setState(x => ({ cfgs: x.cfgs.map((cc, j) => j === 0 ? { ...cc, quant: suggQ! } : cc) })), tokHelp: 'How many words per second the model streams. ~15 tok/s reads like fast typing; 50+ feels instant.', ttftHelp: 'Time to first token: how long the model reads your prompt before it starts answering. Grows with prompt length.', confHelp: 'How solid this prediction is. Measured = benchmarked on this exact setup. Derived = interpolated from similar setups. Projected = calculated from hardware specs alone.', tipTok: s.tip === 'tok', tipTtft: s.tip === 'ttft', tipConf: s.tip === 'conf', tipTokOn: () => this.setState({ tip: 'tok' }), tipTtftOn: () => this.setState({ tip: 'ttft' }), tipConfOn: () => this.setState({ tip: 'conf' }), tipOff: () => this.setState({ tip: null }), showReplay: s.phase === 'done', replay: () => this.replay(), toggleBreakdown: () => this.setState(x => ({ breakdownOpen: !x.breakdownOpen })), breakdownToggleLabel: s.breakdownOpen ? 'How we calculated this ▴' : 'How we calculated this ▾', breakdownOpen: s.breakdownOpen, scrollRef: (el: HTMLElement | null) => { this._sc = el }, messages: s.history.map((mm, i) => ({ text: mm.text, meta: mm.meta || false as string | false, // rental link only under the latest answer, so older turns don't repeat it rpHw: i === s.history.length - 1 && mm.hw && RUNPOD_HW.has(mm.hw) ? mm.hw : '', wrapStyle: mm.role === 'user' ? 'align-self:flex-end; max-width:540px; animation:bubbleIn .6s cubic-bezier(.22,.61,.36,1) both' : 'align-self:flex-start; max-width:680px', bubbleStyle: mm.role === 'user' ? 'background:var(--c-bg-muted); border-radius:16px 16px 4px 16px; padding:12px 16px; font-size:14.5px; line-height:23px; color:var(--c-text); white-space:pre-wrap' : 'font-size:15px; line-height:26px; color:var(--c-text); white-space:pre-wrap', })), isPrefill: !compare && busy && s.lanes[0] && s.lanes[0].phase === 'prefill', spinner: SPIN[Math.floor(s.prefillT * 10) % 10], prefillStatus: pStatus(prefillFrac), prefillClock: `${s.prefillT.toFixed(1)}s`, isStream: !compare && busy && s.lanes[0] && s.lanes[0].phase === 'stream', liveTokLabel: `${fmt(s.liveTok)} tok/s`, streamText: s.streamText, racePrompt: s.racePrompt, raceLanes, presetName: s.custom !== null ? 'Custom prompt' : PRESETS[s.preset].label, presetDdOpen: s.dd === 'preset', togglePresetDd: (e: Event) => { e.stopPropagation(); this.setState(x => ({ dd: x.dd === 'preset' ? null : 'preset' })) }, presetOpts: PRESETS.map((p, i) => ({ name: p.label, meta: `~${p.tok.toLocaleString()} tok`, pick: () => this.setState({ preset: i, custom: null, dd: null }), style: `display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; text-align:left; border:none; background:${s.custom === null && i === s.preset ? 'var(--c-bg-muted)' : 'none'}; border-radius:9px; padding:8px 10px; font-size:13px; cursor:pointer; color:var(--c-text)`, })), customChipStyle: `font-family:'IBM Plex Mono',monospace; font-size:11.5px; color:${s.custom !== null ? 'var(--c-ink)' : 'var(--c-text-faint)'}; margin-left:auto; flex-shrink:0`, promptTokLabel: `${s.custom !== null ? 'custom · ' : ''}~${ctx.toLocaleString()} tok`, taRows: collapse ? 1 : 2, taPlaceholder: collapse ? `Simulating "${PRESETS[s.preset].label}"…` : `Simulating "${PRESETS[s.preset].label}" — or type your own prompt…`, taStyle: `display:block; width:100%; border:none; resize:none; padding:${collapse ? '12px 16px 4px' : '16px 18px 8px'}; line-height:24px; background:transparent; font-size:15px; color:var(--c-ink)`, promptText: s.custom !== null ? s.custom : '', setCustom: (e: Event) => { const t = e.target as HTMLTextAreaElement; this.setState({ custom: t.value === '' ? null : t.value }) }, runOrStop: () => busy ? this.stop() : this.run(), runLabel: busy ? '■ Stop' : (!runnable ? "Won't run" : compare ? 'Race ▸' : 'Run ▸'), runBtnStyle: (() => { const rc = CONFIG.runButtonColor const bg = rc === 'black' ? 'var(--c-ink)' : rc === 'coral' ? accent : 'var(--c-text-strong)' return `padding:9px 20px; border-radius:99px; font-size:14px; font-weight:600; margin-left:10px; cursor:${!runnable && !busy ? 'default' : 'pointer'}; ${busy ? 'background:var(--c-bg-muted); border:1px solid var(--c-border); color:var(--c-text-strong)' : !runnable ? 'background:none; border:1px dashed var(--c-border-strong); color:var(--c-text-faint)' : `background:${bg}; border:1px solid ${bg}; color:var(--c-bg)`}` })(), } } render() { const v = this.renderVals() return (
{!v.heroMode && this.renderNav(v)}
{v.heroMode && this.renderNav(v)}
{v.heroMode && (

{PAGE_META.h1[0]} {v.feelWord}{PAGE_META.h1[1] === '?' ? '?' : ` ${PAGE_META.h1[1]}`}

Pick hardware and a model.
Feel the real speed before you buy.

)} {v.chatMode && (
{v.messages.map((m, i) => (
{m.text}
{m.meta && (
{m.meta} {m.rpHw && this.renderRunpod(v, m.rpHw)}
)}
))} {v.isPrefill && (
{v.spinner} {v.prefillStatus} {v.prefillClock}
)} {v.isStream && (
● {v.liveTokLabel}
{v.streamText}
)} {v.showStarCta && this.renderStarCta(v)}
)} {v.raceMode && (
{v.racePrompt}
{v.raceLanes.map((L, i) => (
{L.badge} {L.title} {L.status}
{L.norun &&
{L.norunText}
} {L.prefill && (
{L.spinner}{L.prefillStatus}{L.clock}
)} {L.showText && (
{L.text}{L.cursor && }
)} {L.rpHw &&
{this.renderRunpod(v, L.rpHw)}
}
))}
{v.showStarCta && this.renderStarCta(v)}
)} {v.heroMode && this.renderComposer(v)} {v.showHeroSpacer &&
}
{v.heroMode && }
{v.collapse && v.cfgOpen &&
{this.renderConfigBlock(v)}
} {!v.heroMode && this.renderComposer(v)}
) } renderNav(v: ReturnType) { return ( ) } renderStarCta(v: ReturnType) { return ( ) } renderRunpod(v: ReturnType, hw: string) { return ( v.runpodClick(hw)} class="rp" title="Rent this GPU by the hour on Runpod (affiliate link)"> {RUNPOD_LOGO}Try it on Runpod↗ ) } renderComposer(v: ReturnType) { return (
{!v.heroMode &&
}
{v.wontRun && (
This combination won't run
{v.wontRunDetail}
{v.suggShow && ( )}
)}
{v.collapse && ( )} {v.showCompareComposer && ( )} {v.promptTokLabel}
{!v.collapse && this.renderConfigBlock(v)}
) } renderConfigBlock(v: ReturnType) { return ( <> {v.cfgRows.map((r, i) => (
{r.showBadge && {r.rowBadge}} {r.showPreset && (
{v.presetDdOpen && (
{v.presetOpts.map((o, j) => ( ))}
)}
)}
{r.hwDdOpen && (
Sort by {r.hwSortBtns.map(t => )}
{r.hwOpts.map((o, j) => o.hdr ?
{o.label}
: )}
)}
{r.modelDdOpen && (
Sort by {r.modelSortBtns.map(t => )}
{r.modelOpts.map((o, j) => o.hdr ?
{o.label}
: )}
)}
{r.quantDdOpen && (
{r.quantOpts.map((o, j) => ( ))}
)}
{r.showQuantTip && ( ? {r.tipQuant && ( {r.quantHelp} )} )} {r.showFitBadge && ( {r.fitIsFits && } {r.fitIsPartial && } {r.fitIsNo && } {r.fitLabel} {r.showMeter && ( )} {r.tipFit && ( {r.fitHelp} )} )} {r.showRemove && ( )} {r.showAddRace && ( )}
))}
{v.statRows.map((st, i) => ( {st.showBadge && {st.badge}} {st.range} tok/s {st.showTips && ( ? {v.tipTok && ( {v.tokHelp} )} )} TTFT {st.ttft} {st.showTips && ( ? {v.tipTtft && ( {v.ttftHelp} )} )} {st.confLabel} {st.showTips && ( ? {v.tipConf && ( {v.confHelp} )} )} ))}
{v.showCompareBtn && ( )} {v.showShareBtn && } {v.showReplay && ( )}
{v.breakdownOpen && (
{v.breakdownSecs.map((sec, i) => (
{sec.showLabel && (
{sec.label}
)}
{sec.rows.map(b => (
{b.n} {b.f} {b.v}
))}
))}
● measured on real hardware ● derived from nearby benchmarks ● projected from specs alone
)} ) } }