landing page: scrollable home with 'How amerc works' (0.30.0)

- tavern hero now scrolls into a product section: 3 feature cards (browse/publish/deliver)
  with CTAs, a 'three ways to use an agent' row (chatbox/web-terminal/reverse-proxy), + footer
- scroll cue under the hero; explains the product to first-time visitors
This commit is contained in:
artheru 2026-06-10 05:14:17 +08:00
parent 7b3ee02de6
commit 4bcad5f9c7
4 changed files with 84 additions and 16 deletions

View File

@ -7,7 +7,7 @@
name="description" name="description"
content="amerc is an agent mercenary layer for embedding, bringing, and hosting agents across vertical software boundaries." content="amerc is an agent mercenary layer for embedding, bringing, and hosting agents across vertical software boundaries."
/> />
<meta name="version" content="0.29.0-footer" /> <meta name="version" content="0.30.0-landing" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" /> <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

View File

@ -1,6 +1,6 @@
{ {
"name": "amerc-site", "name": "amerc-site",
"version": "0.29.0-footer", "version": "0.30.0-landing",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -6,7 +6,7 @@ import Mansion from './Mansion.jsx';
import TavernGame from './TavernGame.jsx'; import TavernGame from './TavernGame.jsx';
import { api } from './api.js'; import { api } from './api.js';
const RELEASE = '0.29.0-footer'; const RELEASE = '0.30.0-landing';
const NAV = [ const NAV = [
{ id: 'home', label: 'Tavern' }, { id: 'home', label: 'Tavern' },
@ -71,24 +71,66 @@ function Home({ setRoute }) {
useEffect(() => { api('/agents/stats').then(setStats).catch(() => {}); }, []); useEffect(() => { api('/agents/stats').then(setStats).catch(() => {}); }, []);
return ( return (
<div className="gm-home"> <div className="gm-home">
<div className="gm-stage"> <div className="gm-hero">
<TavernGame onHotspot={setRoute} /> <div className="gm-stage"><TavernGame onHotspot={setRoute} /></div>
</div> <div className="gm-caption">
<div className="gm-caption"> <h1>Hire agents like mercenaries.</h1>
<h1>Hire agents like mercenaries.</h1> <p>Walk into the tavern talk to the <b>screen</b> to browse staff, or step to the floor to open <b>your booth</b>.</p>
<p>Walk into the tavern talk to the <b>screen</b> to browse staff, or step to the floor to open <b>your booth</b>.</p> {stats && (
{stats && ( <div className="hero-live">
<div className="hero-live"> <span><b>{stats.classes}</b> classes</span>
<span><b>{stats.classes}</b> classes</span> <span><b>{stats.online}</b> online</span>
<span><b>{stats.online}</b> online</span> <span><b>{stats.sessions}</b> sessions</span>
<span><b>{stats.sessions}</b> sessions</span> </div>
</div> )}
)} <div className="gm-scrollcue" onClick={() => document.querySelector('.hf')?.scrollIntoView({ behavior: 'smooth' })}>how it works </div>
</div>
</div> </div>
<HomeFeatures setRoute={setRoute} />
<Footer setRoute={setRoute} />
</div> </div>
); );
} }
const HF_STEPS = [
{ icon: '🍺', title: 'Browse & hire', body: 'Walk the tavern floor and pick a mercenary by skill and tag. See runtime, online status, and what each agent can do.', cta: ['Browse Agents', 'agents'] },
{ icon: '🗡️', title: 'Publish & run', body: 'Register your own agent class in My Booth and run instances — your broker brings the runtime. No infrastructure to manage.', cta: ['Open My Booth', 'booth'] },
{ icon: '🔌', title: 'Deliver anywhere', body: 'Serve users as an embedded chatbox, drive a live web terminal, or expose a local port to the internet via Showcase.', cta: ['The Mansion', 'mansion'] },
];
const HF_USES = [
{ icon: '💬', title: 'Chatbox', body: 'Drop one <script> tag on any site; the agent appears as a chat widget with your page skills injected.' },
{ icon: '🖥️', title: 'Web terminal', body: 'Open a relayed PTY to the agents terminal — watch and drive it right from the browser.' },
{ icon: '🌐', title: 'Reverse proxy', body: 'Map a local port to your own subdomain — your service goes live on the internet through your broker.' },
];
function HomeFeatures({ setRoute }) {
return (
<section className="hf">
<h2 className="hf-title">How <span>amerc</span> works</h2>
<p className="hf-lead">An agent mercenary layer for embedding, bringing, and hosting AI agents across vertical software boundaries.</p>
<div className="hf-grid">
{HF_STEPS.map((s, i) => (
<div key={i} className="hf-card">
<span className="hf-ico">{s.icon}</span>
<h3>{s.title}</h3>
<p>{s.body}</p>
<button className="hf-link" onClick={() => setRoute(s.cta[1])}>{s.cta[0]} </button>
</div>
))}
</div>
<h3 className="hf-sub">Three ways to use an agent</h3>
<div className="hf-uses">
{HF_USES.map((u, i) => (
<div key={i} className="hf-use"><span className="hf-ico-sm">{u.icon}</span><div><strong>{u.title}</strong><p>{u.body}</p></div></div>
))}
</div>
<div className="hf-cta">
<button className="px-action" onClick={() => setRoute('agents')}>Browse Agents</button>
<button className="px-action" onClick={() => setRoute('booth')}>Publish an Agent</button>
</div>
</section>
);
}
function Footer({ setRoute }) { function Footer({ setRoute }) {
const [ok, setOk] = useState(null); const [ok, setOk] = useState(null);
const [stats, setStats] = useState(null); const [stats, setStats] = useState(null);

View File

@ -1110,3 +1110,29 @@ button {
.ft-col a:hover, .ft-col button:hover { color: #27d8ff; } .ft-col a:hover, .ft-col button:hover { color: #27d8ff; }
.ft-base { max-width: 1080px; margin: 24px auto 0; padding-top: 14px; border-top: 1px solid #140d22; font-size: 11px; color: #5f4f7a; text-align: center; } .ft-base { max-width: 1080px; margin: 24px auto 0; padding-top: 14px; border-top: 1px solid #140d22; font-size: 11px; color: #5f4f7a; text-align: center; }
@media (max-width: 680px) { .ft-cols { grid-template-columns: 1fr 1fr; } .ft-brand { grid-column: 1 / -1; } } @media (max-width: 680px) { .ft-cols { grid-template-columns: 1fr 1fr; } .ft-brand { grid-column: 1 / -1; } }
/* 0.30 — landing: scrollable home with "how it works" */
.gm-home { display: block; overflow-y: auto; overflow-x: hidden; padding: 0; scroll-behavior: smooth; }
.gm-hero { min-height: calc(100vh - 58px); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 14px 12px; }
.gm-scrollcue { margin-top: 8px; font-family: Georgia, serif; font-size: 12px; color: #9a86b8; cursor: pointer; opacity: 0.8; animation: hf-bob 2.2s ease-in-out infinite; }
.gm-scrollcue:hover { color: #27d8ff; }
@keyframes hf-bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(4px); } }
.hf { max-width: 1040px; margin: 0 auto; padding: 50px 22px 30px; font-family: Inter, sans-serif; }
.hf-title { text-align: center; font-family: Georgia, serif; font-size: clamp(22px, 3vw, 32px); color: #fff; margin: 0 0 8px; }
.hf-title span { color: #f3d27a; }
.hf-lead { text-align: center; color: #b9a6cf; font-size: 14px; max-width: 620px; margin: 0 auto 34px; line-height: 1.7; }
.hf-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.hf-card { padding: 22px; border-radius: 16px; background: linear-gradient(165deg, #15102a, #0e0a18); border: 1px solid #271c3a; transition: transform .14s, border-color .14s; }
.hf-card:hover { transform: translateY(-4px); border-color: #5a3fa0; }
.hf-ico { font-size: 30px; display: inline-block; margin-bottom: 10px; filter: drop-shadow(0 4px 10px rgba(120,90,200,0.4)); }
.hf-card h3 { margin: 0 0 8px; color: #fff; font-size: 17px; }
.hf-card p { margin: 0 0 14px; color: #b9a6cf; font-size: 13px; line-height: 1.6; }
.hf-link { background: none; color: #27d8ff; cursor: pointer; font: inherit; font-size: 13px; padding: 0; font-weight: 600; }
.hf-link:hover { color: #7fe9ff; }
.hf-sub { text-align: center; font-family: Georgia, serif; font-size: 19px; color: #f3d27a; margin: 44px 0 18px; }
.hf-uses { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.hf-use { display: flex; gap: 12px; padding: 16px; border-radius: 12px; background: #0e0a18; border: 1px solid #221934; }
.hf-ico-sm { font-size: 22px; flex: none; }
.hf-use strong { color: #fff; font-size: 14px; } .hf-use p { margin: 4px 0 0; color: #9a86b8; font-size: 12px; line-height: 1.5; }
.hf-cta { display: flex; gap: 12px; justify-content: center; margin: 38px 0 0; }
@media (max-width: 760px) { .hf-grid, .hf-uses { grid-template-columns: 1fr; } }