homepage session preview chatbox (0.49.0)

- 'See a session' block: an animated chat widget that plays a scripted
  agent conversation (ship a code change, then expose the preview via
  Showcase) with typing indicator + message pop-in, looping
- shows the core product experience (chatting with/driving an agent) that
  text alone can't convey; clearly labeled 'a scripted preview of a session'
- side copy + 'Browse real agents' CTA; respects prefers-reduced-motion
  (renders the full convo statically) and cleans up its timers
- verified live: widget renders, animates, 0 console errors
This commit is contained in:
artheru 2026-06-10 07:21:07 +08:00
parent 3130430874
commit 4a6ee29a2e
4 changed files with 73 additions and 3 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.48.0-seo" />
<meta name="version" content="0.49.0-demo" />
<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.48.0-seo",
"version": "0.49.0-demo",
"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.48.0-seo';
const RELEASE = '0.49.0-demo';
const NAV = [
{ id: 'home', label: 'Tavern' },
@ -172,6 +172,38 @@ function FlowDiagram() {
</div>
);
}
const DEMO_SCRIPT = [
{ who: 'user', text: 'Add a CSV export button to the orders table.' },
{ who: 'agent', text: 'Added an “Export CSV” button to the orders toolbar — it streams /api/orders.csv. Change pushed; your preview is rebuilding now.' },
{ who: 'user', text: 'Nice. Expose my local dev server so my client can review it.' },
{ who: 'agent', text: 'Live at demo1.you.amerc.ai over HTTPS — tunneled from localhost:3000 through Showcase. Share the link.' },
];
function ChatDemo() {
const [n, setN] = useState(prefersReducedMotion() ? DEMO_SCRIPT.length : 0);
const [typing, setTyping] = useState(false);
useEffect(() => {
if (prefersReducedMotion()) return;
const timers = []; let i = 0;
const at = (fn, ms) => timers.push(setTimeout(fn, ms));
const next = () => {
if (i >= DEMO_SCRIPT.length) { at(() => { setN(0); i = 0; next(); }, 4200); return; }
if (DEMO_SCRIPT[i].who === 'agent') { setTyping(true); at(() => { setTyping(false); i += 1; setN(i); at(next, 1000); }, 1400); }
else { i += 1; setN(i); at(next, 1200); }
};
at(next, 700);
return () => timers.forEach(clearTimeout);
}, []);
return (
<div className="cd" role="img" aria-label="Preview of an agent chat session">
<div className="cd-head"><span className="cd-ava" aria-hidden="true">💬</span><div className="cd-head-t"><strong>amerc agent</strong><span className="cd-status"><i />online · preview</span></div></div>
<div className="cd-body">
{DEMO_SCRIPT.slice(0, n).map((m, idx) => <div key={idx} className={`cd-msg ${m.who}`}>{m.text}</div>)}
{typing && <div className="cd-msg agent cd-typing" aria-hidden="true"><span /><span /><span /></div>}
</div>
<div className="cd-foot"><span className="cd-input">Ask the agent</span><span className="cd-send" aria-hidden="true"></span></div>
</div>
);
}
function HomeFeatures({ setRoute }) {
return (
<section className="hf">
@ -194,6 +226,16 @@ function HomeFeatures({ setRoute }) {
<Reveal key={i} className="hf-use" delay={i * 80}><span className="hf-ico-sm">{u.icon}</span><div><strong>{u.title}</strong><p>{u.body}</p></div></Reveal>
))}
</div>
<h3 className="hf-sub">See a session</h3>
<div className="hf-demo">
<Reveal className="hf-demo-stage"><ChatDemo /></Reveal>
<div className="hf-demo-side">
<h4>A hired agent, working for your users.</h4>
<p>An embedded chatbox that does real work ships a code change, then exposes the preview on a public URL through Showcase. Drop it on any page with one <code>&lt;script&gt;</code> tag.</p>
<button className="px-action" onClick={() => setRoute('agents')}>Browse real agents </button>
<span className="hf-demo-note"> a scripted preview of a session</span>
</div>
</div>
<ActivityFeed />
<div className="hf-cta">
<button className="px-action" onClick={() => setRoute('agents')}>Browse Agents</button>

View File

@ -1320,3 +1320,31 @@ button {
}
@keyframes fdflowv { from { top: -8px; } to { top: calc(100% + 8px); } }
@media (prefers-reduced-motion: reduce) { .fd-link i { animation: none; opacity: 0; } }
/* homepage session preview (0.49) */
.hf-demo { display: grid; grid-template-columns: minmax(280px, 380px) 1fr; gap: 28px; align-items: center; }
.hf-demo-side h4 { color: #fff; font-size: 18px; margin: 0 0 8px; }
.hf-demo-side p { color: #b6a7d4; font-size: 13.5px; line-height: 1.65; margin: 0 0 14px; max-width: 46ch; }
.hf-demo-side code { background: #07060c; color: #7fe9ff; padding: 1px 5px; border-radius: 4px; font-size: 12px; }
.hf-demo-note { display: block; margin-top: 10px; color: #6f6090; font-size: 11px; font-style: italic; }
.cd { background: linear-gradient(180deg, #140e24, #100b1c); border: 1px solid #2c2046; border-radius: 16px; overflow: hidden; box-shadow: 0 16px 40px rgba(0,0,0,0.4); }
.cd-head { display: flex; align-items: center; gap: 10px; padding: 12px 14px; background: rgba(39,216,255,0.05); border-bottom: 1px solid #241a36; }
.cd-ava { font-size: 17px; width: 32px; height: 32px; display: grid; place-items: center; border-radius: 9px; background: rgba(39,216,255,0.1); border: 1px solid rgba(39,216,255,0.25); }
.cd-head-t { display: flex; flex-direction: column; }
.cd-head-t strong { color: #fff; font-size: 13px; }
.cd-status { color: #8fe9c8; font-size: 11px; display: inline-flex; align-items: center; gap: 5px; }
.cd-status i { width: 6px; height: 6px; border-radius: 50%; background: #36f0b0; box-shadow: 0 0 6px #36f0b0; }
.cd-body { display: flex; flex-direction: column; justify-content: flex-end; gap: 8px; height: 252px; padding: 14px; overflow: hidden; }
.cd-msg { max-width: 84%; padding: 9px 12px; font-size: 12.5px; line-height: 1.5; border-radius: 13px; animation: cd-pop .32s ease-out; }
.cd-msg.user { align-self: flex-end; background: linear-gradient(180deg, #2aa7d8, #1f86c4); color: #052230; border-bottom-right-radius: 4px; }
.cd-msg.agent { align-self: flex-start; background: #1c1530; color: #e3d9f5; border: 1px solid #2e2348; border-bottom-left-radius: 4px; }
@keyframes cd-pop { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
.cd-typing { display: inline-flex; gap: 4px; padding: 12px 13px; align-self: flex-start; }
.cd-typing span { width: 6px; height: 6px; border-radius: 50%; background: #8a7aa8; animation: cd-bounce 1.2s infinite; }
.cd-typing span:nth-child(2) { animation-delay: .15s; } .cd-typing span:nth-child(3) { animation-delay: .3s; }
@keyframes cd-bounce { 0%,60%,100% { transform: translateY(0); opacity: .5; } 30% { transform: translateY(-5px); opacity: 1; } }
.cd-foot { display: flex; align-items: center; gap: 10px; padding: 11px 14px; border-top: 1px solid #241a36; }
.cd-input { flex: 1; color: #6f6090; font-size: 12.5px; }
.cd-send { color: #27d8ff; font-size: 14px; }
@media (max-width: 760px) { .hf-demo { grid-template-columns: 1fr; gap: 18px; } }
@media (prefers-reduced-motion: reduce) { .cd-msg { animation: none; } .cd-typing span { animation: none; } }