browse: roster storefront (0.44.0)

- storefront header 'The mercenary roster' with a value-prop subtitle
- emoji category chips (uses TAG_EMOJI) so tags read as a directory
- always-present dashed 'Publish your own' card in the grid — the roster
  never looks empty and it actively drives supply into My Booth
- verified live: header, emoji chips, and add-card all render
This commit is contained in:
artheru 2026-06-10 06:50:31 +08:00
parent 0ddc585e49
commit 9e762b15ba
5 changed files with 34 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.43.0-lean" />
<meta name="version" content="0.44.0-roster" />
<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.43.0-lean",
"version": "0.44.0-roster",
"private": true,
"type": "module",
"scripts": {

View File

@ -28,14 +28,17 @@ export function AgentBrowse({ auth, setRoute }) {
return (
<div className="ap">
<div className="ap-head">
<h2>Browse Agents</h2>
<input className="ap-search" placeholder="search classes…" value={q} onChange={(e) => setQ(e.target.value)} />
<div className="ap-store-head">
<div className="ap-store-intro">
<h2>The mercenary roster</h2>
<p>Hire a ready-made agent by skill and tag chat with it, embed it on your site, or drive its terminal. Bring your own and it joins the roster.</p>
</div>
<input className="ap-search" placeholder="search the roster…" value={q} onChange={(e) => setQ(e.target.value)} />
</div>
<div className="ap-tags">
<button className={`ap-tag${!tag ? ' on' : ''}`} onClick={() => setTag('')}>all</button>
<button className={`ap-tag${!tag ? ' on' : ''}`} onClick={() => setTag('')}> all</button>
{Object.entries(tags).sort((a, b) => b[1] - a[1]).map(([t, n]) => (
<button key={t} className={`ap-tag${tag === t ? ' on' : ''}`} onClick={() => setTag(t)}>{t} <em>{n}</em></button>
<button key={t} className={`ap-tag${tag === t ? ' on' : ''}`} onClick={() => setTag(t)}>{TAG_EMOJI[t] || '🗡️'} {t} <em>{n}</em></button>
))}
</div>
{err && <p className="px-auth-err">{err}</p>}
@ -54,6 +57,14 @@ export function AgentBrowse({ auth, setRoute }) {
</div>
</button>
))}
{classes.length > 0 && (
<button className="ap-card ap-card-add" onClick={() => setRoute('booth')}>
<span className="ap-add-plus" aria-hidden="true"></span>
<strong>Publish your own</strong>
<p>Register an agent class and run it from your booth your mercenary joins the roster right here.</p>
<span className="ap-add-cta">Open My Booth </span>
</button>
)}
{!classes.length && (
<div className="ap-empty-start">
<h3>No agents here yet be the first.</h3>

View File

@ -7,7 +7,7 @@ import Account from './Account.jsx';
import TavernGame from './TavernGame.jsx';
import { api } from './api.js';
const RELEASE = '0.43.0-lean';
const RELEASE = '0.44.0-roster';
const NAV = [
{ id: 'home', label: 'Tavern' },

View File

@ -1252,3 +1252,18 @@ button {
.ap-booth-welcome li { color: #d6ccec; font-size: 13px; line-height: 1.55; }
.ap-booth-welcome li b { color: #f0e9ff; }
.ap-on-word { color: #36f0b0; font-weight: 600; }
/* browse storefront header + publish-your-own card (0.44 vividness) */
.ap-store-head { display: flex; align-items: flex-end; gap: 18px; margin-bottom: 16px; flex-wrap: wrap; }
.ap-store-intro { flex: 1 1 320px; }
.ap-store-intro h2 { font-family: Georgia, "Times New Roman", serif; font-size: 26px; color: #fff; margin: 0 0 6px; text-shadow: 1px 1px 0 rgba(0,0,0,0.4); }
.ap-store-intro p { margin: 0; color: #b6a7d4; font-size: 13.5px; line-height: 1.6; max-width: 64ch; }
.ap-store-head .ap-search { margin-left: 0; min-width: 200px; }
.ap-card-add { align-items: flex-start; justify-content: center; text-align: left; background: linear-gradient(180deg, rgba(39,216,255,0.05), rgba(20,14,30,0.3));
border: 1.5px dashed #3a5a6e; transition: border-color 0.18s, transform 0.18s, box-shadow 0.18s; }
.ap-card-add:hover { border-color: #46c8e0; transform: translateY(-2px); box-shadow: 0 8px 22px rgba(0,0,0,0.35); }
.ap-add-plus { font-size: 26px; line-height: 1; color: #46c8e0; width: 42px; height: 42px; display: grid; place-items: center; border-radius: 11px;
background: rgba(39,216,255,0.1); border: 1px solid rgba(39,216,255,0.25); }
.ap-card-add strong { color: #eaf6ff; font-size: 15px; }
.ap-card-add p { margin: 0; color: #9d8fc0; font-size: 12.5px; line-height: 1.55; }
.ap-add-cta { color: #46c8e0; font-size: 12.5px; font-weight: 600; margin-top: 2px; }