tavern screen shows live platform stats (0.53.0)

- the in-game cyan screen no longer shows a fake chart; it now cycles the
  real CLASSES / ONLINE / SESSIONS counts (big number + label) over a dim
  sparkline, with a cross-fade between metrics
- stats flow from Home into TavernGame via a ref the render loop reads, so
  the canvas animation stays running while data updates
- ties the brand centerpiece to real product data — the tavern's TV shows
  the live business
- verified live: screen renders the live class count legibly
This commit is contained in:
artheru 2026-06-10 07:46:20 +08:00
parent d9a62b4c3c
commit 0c8db141d1
4 changed files with 21 additions and 8 deletions

View File

@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0e0a14" />
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
<meta name="version" content="0.52.0-changelog" />
<meta name="version" content="0.53.0-livescreen" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />

View File

@ -1,6 +1,6 @@
{
"name": "amerc-site",
"version": "0.52.0-changelog",
"version": "0.53.0-livescreen",
"private": true,
"type": "module",
"scripts": {

View File

@ -7,7 +7,7 @@ import Account from './Account.jsx';
import TavernGame from './TavernGame.jsx';
import { api } from './api.js';
const RELEASE = '0.52.0-changelog';
const RELEASE = '0.53.0-livescreen';
const NAV = [
{ id: 'home', label: 'Tavern' },
@ -103,7 +103,7 @@ function Home({ setRoute }) {
return (
<div className="gm-home">
<div className="gm-hero">
<div className="gm-stage"><TavernGame onHotspot={setRoute} /></div>
<div className="gm-stage"><TavernGame onHotspot={setRoute} stats={stats} /></div>
<div className="gm-caption">
<h1>Hire agents like mercenaries.</h1>
<p>Hire, run, and deliver AI agents across any software boundary as an embedded chatbox, a live web terminal, or a public URL. <b>No infrastructure, just skills.</b></p>

View File

@ -51,8 +51,10 @@ function buildStatic(buf, S) {
ctx.fillStyle = '#0c2030'; ctx.fillRect(198, 6, 116, 50); ctx.strokeStyle = '#46c8e0'; ctx.lineWidth = 2; ctx.strokeRect(199, 7, 114, 48); ctx.fillStyle = '#16415e'; ctx.fillRect(202, 10, 108, 42);
}
export default function TavernGame({ onHotspot }) {
export default function TavernGame({ onHotspot, stats }) {
const canvasRef = useRef(null);
const statsRef = useRef(stats);
useEffect(() => { statsRef.current = stats; }, [stats]);
const [ready, setReady] = useState(false);
useEffect(() => {
const keys = Object.keys(SRC); const S = {}; let loaded = 0, raf, f = 0, buf;
@ -79,9 +81,20 @@ export default function TavernGame({ onHotspot }) {
// spotlight
const g = ctx.createRadialGradient(256, 230, 6, 256, 236, 56); g.addColorStop(0, 'rgba(255,244,210,0.26)'); g.addColorStop(1, 'rgba(255,244,210,0)'); ctx.fillStyle = g; ctx.beginPath(); ctx.ellipse(256, 244, 40, 44, 0, 0, 7); ctx.fill();
chr(ctx, S.princess, dframe, 224 + sway, 184);
// screen content (chart + scanline)
for (let i = 0; i < 13; i++) { const bh = 4 + ((Math.sin(f * 0.09 + i) + 1) * 6 + (i % 3)) | 0; ctx.fillStyle = i % 2 ? '#7fe9ff' : '#2f9fd0'; ctx.fillRect(206 + i * 8, 48 - bh, 5, bh); }
ctx.fillStyle = 'rgba(127,233,255,0.5)'; ctx.fillRect(202, 10 + (f % 42), 108, 1);
// screen content: LIVE platform stats, cycling, over a dim sparkline
const st = statsRef.current;
const items = st ? [['CLASSES', st.classes], ['ONLINE', st.online], ['SESSIONS', st.sessions]] : [['LIVE', '·']];
const phase = Math.floor(f / 150) % items.length;
const [label, val] = items[phase];
for (let i = 0; i < 13; i++) { const bh = 3 + ((Math.sin(f * 0.09 + i) + 1) * 4 + (i % 3)) | 0; ctx.fillStyle = 'rgba(70,170,210,0.30)'; ctx.fillRect(206 + i * 8, 50 - bh, 5, bh); }
const pf = (f % 150) / 150, fade = Math.min(1, Math.min(pf, 1 - pf) * 8 + 0.25);
ctx.textAlign = 'center';
ctx.fillStyle = `rgba(174,242,255,${fade})`; ctx.font = 'bold 18px Georgia, serif';
ctx.fillText(String(val), 256, 33);
ctx.fillStyle = `rgba(95,208,232,${fade})`; ctx.font = '7px Georgia, serif';
ctx.fillText(label, 256, 45);
ctx.textAlign = 'left'; ctx.font = '10px serif';
ctx.fillStyle = 'rgba(127,233,255,0.4)'; ctx.fillRect(202, 10 + (f % 42), 108, 1);
// warm lighting + screen glow
ctx.globalCompositeOperation = 'lighter';
for (const [lx, ly, lr, a] of [[256, 30, 64, 0.16], [40, 40, 30, 0.18], [432, 40, 30, 0.18]]) { const aa = a * (0.85 + 0.15 * Math.sin(f * 0.1 + lx)); const gg = ctx.createRadialGradient(lx, ly, 0, lx, ly, lr); gg.addColorStop(0, `rgba(255,180,90,${aa})`); gg.addColorStop(1, 'rgba(255,170,80,0)'); ctx.fillStyle = gg; ctx.beginPath(); ctx.arc(lx, ly, lr, 0, 7); ctx.fill(); }