art: painted hero ambient + mobile (3.1.0)

- ambient life over the painted scene: flickering warm lantern glows +
  pulsing cool crystal/bar light (CSS, screen-blend, no JS)
- mobile: hide the tiny diegetic tabs/wood-bubbles, center the roster
  board, and add a clear Browse/Legion/Mansion tap-button row under the
  tagline so the tavern is usable on phones

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
artheru 2026-06-11 14:05:17 +08:00
parent 610b932140
commit 8c4729b4ea
5 changed files with 36 additions and 3 deletions

View File

@ -17,7 +17,7 @@
@media (prefers-reduced-motion: reduce) { .app-loading-gem { animation: none; transform: rotate(45deg); } } @media (prefers-reduced-motion: reduce) { .app-loading-gem { animation: none; transform: rotate(45deg); } }
</style> </style>
<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="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="3.0.0-painted" /> <meta name="version" content="3.1.0-paintedmobile" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" /> <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" /> <link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/apple-touch-icon.png" />

View File

@ -1,6 +1,6 @@
{ {
"name": "amerc-site", "name": "amerc-site",
"version": "3.0.0-painted", "version": "3.1.0-paintedmobile",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -12,7 +12,7 @@ import { api } from './api.js';
const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>); const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>);
const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>); const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>);
const RELEASE = '3.0.0-painted'; const RELEASE = '3.1.0-paintedmobile';
const NAV = [ const NAV = [
{ id: 'home', label: 'Tavern' }, { id: 'home', label: 'Tavern' },
@ -124,6 +124,11 @@ function Home({ setRoute, auth }) {
<div className="gm-bottombar"> <div className="gm-bottombar">
<h1 className="gm-tagline">Hire agents like mercenaries<span className="gm-cursor">_</span></h1> <h1 className="gm-tagline">Hire agents like mercenaries<span className="gm-cursor">_</span></h1>
<p className="gm-tagsub">every fixture in the tavern is a door walk in, or read the <button className="gm-link" onClick={() => setDlg('tour')}>tour</button></p> <p className="gm-tagsub">every fixture in the tavern is a door walk in, or read the <button className="gm-link" onClick={() => setDlg('tour')}>tour</button></p>
<div className="gm-cta-row">
<button className="gm-cta-btn gm-cta-primary" onClick={() => setRoute('agents')}> Browse Agents</button>
<button className="gm-cta-btn gm-cta-secondary" onClick={() => setRoute('legion')}>🚩 My Legion</button>
<button className="gm-cta-btn gm-cta-secondary" onClick={() => setRoute('mansion')}>🏰 My Mansion</button>
</div>
{stats && ( {stats && (
<div className="hero-live"> <div className="hero-live">
<span><b><Count n={stats.classes} /></b> classes</span> <span><b><Count n={stats.classes} /></b> classes</span>
@ -477,6 +482,7 @@ function StatusPage() {
} }
const CHANGELOG = [ const CHANGELOG = [
{ v: '3.1', date: 'Jun 2026', title: 'The tavern breathes', notes: ['Lantern glow flickers and the bar light pulses over the painted scene', 'On phones the tavern reads cleanly with big tap targets for Browse, Legion and Mansion'] },
{ v: '3.0', date: 'Jun 2026', title: 'A painted tavern', notes: ['The home tavern is now a hand-painted scene — an elf host, an orc bartender and a dwarf patron in a lamplit hall — replacing the old pixel tiles', 'Every painted fixture is still a door: the board opens the roster, the barkeep signs you in, the elf pitches the tour, the side passages lead to your Legion and Mansion'] }, { v: '3.0', date: 'Jun 2026', title: 'A painted tavern', notes: ['The home tavern is now a hand-painted scene — an elf host, an orc bartender and a dwarf patron in a lamplit hall — replacing the old pixel tiles', 'Every painted fixture is still a door: the board opens the roster, the barkeep signs you in, the elf pitches the tour, the side passages lead to your Legion and Mansion'] },
{ v: '2.5', date: 'Jun 2026', title: 'Chat speaks markdown', notes: ['Chat messages now render markdown — code blocks, lists, tables and links — so an agent can answer with formatted, clickable replies (it picks its own links, to the netdisk or anywhere)'] }, { v: '2.5', date: 'Jun 2026', title: 'Chat speaks markdown', notes: ['Chat messages now render markdown — code blocks, lists, tables and links — so an agent can answer with formatted, clickable replies (it picks its own links, to the netdisk or anywhere)'] },
{ v: '2.4', date: 'Jun 2026', title: 'The Quartermaster command post', notes: ['The admin Quartermaster is now amercs internal management station — members, agents, companies & products, whiteboards and netdisk all in one panel', 'A command-post dashboard with live counts and quick links to git, docs, the PM workspace and the webagent relay'] }, { v: '2.4', date: 'Jun 2026', title: 'The Quartermaster command post', notes: ['The admin Quartermaster is now amercs internal management station — members, agents, companies & products, whiteboards and netdisk all in one panel', 'A command-post dashboard with live counts and quick links to git, docs, the PM workspace and the webagent relay'] },

View File

@ -11,6 +11,9 @@ export default function TavernGame({ onAction, stats, auth }) {
return ( return (
<div className="gm-room pt-room"> <div className="gm-room pt-room">
<img className="pt-bg" src={`${P}bg.webp`} alt="The amerc mercenary tavern" /> <img className="pt-bg" src={`${P}bg.webp`} alt="The amerc mercenary tavern" />
<div className="pt-ambient" aria-hidden="true">
<span className="pt-glow warm a" /><span className="pt-glow warm b" /><span className="pt-glow cool c" /><span className="pt-glow cool d" />
</div>
<div className="pt-vignette" aria-hidden="true" /> <div className="pt-vignette" aria-hidden="true" />
{/* patrons + bartender */} {/* patrons + bartender */}

View File

@ -1928,3 +1928,27 @@ button {
.pt-board { width: 46%; padding: 8px; } .pt-board { width: 46%; padding: 8px; }
.pt-bub-dwarf { display: none; } .pt-bub-dwarf { display: none; }
} }
/* painted hero: ambient life + mobile (3.1) */
.pt-ambient { position: absolute; inset: 0; pointer-events: none; mix-blend-mode: screen; z-index: 1; }
.pt-glow { position: absolute; border-radius: 50%; filter: blur(8px); }
.pt-glow.warm { background: radial-gradient(circle, rgba(255,180,90,0.55), rgba(255,170,80,0) 70%); }
.pt-glow.cool { background: radial-gradient(circle, rgba(90,200,255,0.5), rgba(70,200,235,0) 70%); }
@keyframes ptFlicker { 0%, 100% { opacity: 0.85; } 42% { opacity: 0.6; } 55% { opacity: 1; } 70% { opacity: 0.72; } }
@keyframes ptPulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 0.95; } }
.pt-glow.a { left: 6%; top: 12%; width: 13%; aspect-ratio: 1; animation: ptFlicker 3.3s ease-in-out infinite; }
.pt-glow.b { left: 80%; top: 13%; width: 13%; aspect-ratio: 1; animation: ptFlicker 3.9s ease-in-out infinite 0.7s; }
.pt-glow.c { left: 9%; top: 36%; width: 12%; aspect-ratio: 1; animation: ptPulse 4.5s ease-in-out infinite; }
.pt-glow.d { left: 44%; top: 56%; width: 22%; aspect-ratio: 2.4; border-radius: 40%; animation: ptPulse 5s ease-in-out infinite 0.5s; }
/* mobile tap CTAs under the scene (diegetic hotspots are too small to tap) */
.gm-cta-row { display: none; gap: 8px; flex-wrap: wrap; justify-content: center; margin-top: 8px; }
.gm-cta-row .gm-cta-btn { font-size: 13px; padding: 9px 14px; }
@media (max-width: 700px) {
.pt-tab, .pt-bub-orc, .pt-bub-dwarf { display: none; }
.pt-board { left: 50%; transform: translateX(-50%); width: 64%; top: 6%; }
.pt-board:hover { transform: translateX(-50%) translateY(-2px); }
.pt-elf-wrap { height: 80%; right: 0; }
.gm-cta-row { display: flex; }
.gm-stage3 .gm-room { width: 100%; }
}