frontpage: use the painterly concept art as the hero, Georgia fonts

- Home is now the concept tavern art composed in layers (background image +
  interactive hotspot layer for nav/Browse Agents/Enter My Booth + live stat chips)
  instead of procedural canvas art
- fonts switched to Georgia serif site-wide (matches the art); topbar hidden on home
- removed unused TopDownTavern procedural renderer

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
artheru 2026-06-09 23:31:44 +08:00
parent af7f9addbf
commit 564ce38c9c
7 changed files with 58 additions and 200 deletions

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "amerc-site",
"version": "0.22.1-tavern",
"version": "0.23.0-hero",
"private": true,
"type": "module",
"scripts": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -2,10 +2,9 @@ import React, { useCallback, useEffect, useState } from 'react';
import { Menu, X } from 'lucide-react';
import { useAuth, AuthPanel, AdminConsole } from './Auth.jsx';
import { AgentBrowse, BoothDashboard } from './AgentPlatform.jsx';
import TopDownTavern from './TopDownTavern.jsx';
import { api } from './api.js';
const RELEASE = '0.22.1-tavern';
const RELEASE = '0.23.0-hero';
const NAV = [
{ id: 'home', label: 'Tavern' },
@ -64,36 +63,38 @@ function PixelTopbar({ route, setRoute, auth }) {
);
}
function StatsStrip() {
const [s, setS] = useState(null);
useEffect(() => { api('/agents/stats').then(setS).catch(() => {}); }, []);
const items = [
['classes', 'agent classes'], ['instances', 'instances'], ['online', 'online now'],
['sessions', 'live sessions'], ['owners', 'publishers'],
];
return (
<div className="td-stats">
{items.map(([k, label]) => (
<div key={k} className="td-stat"><b>{s ? s[k] : '—'}</b><span>{label}</span></div>
))}
</div>
function Home({ setRoute, auth }) {
const [stats, setStats] = useState(null);
useEffect(() => { api('/agents/stats').then(setStats).catch(() => {}); }, []);
const Hot = ({ s, to, title, glow }) => (
<button className={`hero-hot${glow ? ' hero-glow' : ''}`} style={s} onClick={() => setRoute(to)} title={title} aria-label={title} />
);
}
function Home({ setRoute }) {
return (
<div className="td-home">
<div className="td-stage">
<TopDownTavern onHotspot={setRoute} />
<div className="td-hero">
<h1>Hire agents like mercenaries.</h1>
<p>Publish an agent <b>class</b>, run <b>instances</b>, serve users in live <b>sessions</b> as a chatbox or a web terminal.</p>
<div className="td-hero-cta">
<button className="px-action" onClick={() => setRoute('agents')}>Browse Agents</button>
<button className="px-action" onClick={() => setRoute('booth')}>Publish an Agent</button>
<div className="hero-wrap">
<div className="hero-stage">
<img className="hero-img" src="/scene2d/tavern-hero.jpg" alt="amerc — hire agents like mercenaries" />
{/* top nav, over the painted bar */}
<Hot s={{ left: '0.5%', top: '1%', width: '19%', height: '17%' }} to="home" title="amerc home" />
<Hot s={{ left: '34%', top: '3.5%', width: '8.5%', height: '8%' }} to="agents" title="Staff" />
<Hot s={{ left: '41.5%', top: '3.5%', width: '9%', height: '8%' }} to="agents" title="Pricing" />
<Hot s={{ left: '65.5%', top: '3.5%', width: '9.5%', height: '8%' }} to="signin" title="Sign Up" />
<Hot s={{ left: '74%', top: '3.5%', width: '8%', height: '8%' }} to="signin" title="Login" />
<Hot s={{ left: '81.5%', top: '3.5%', width: '11%', height: '8%' }} to="booth" title="My Booth" />
{/* big interactive zones */}
<Hot s={{ left: '27%', top: '31%', width: '38%', height: '25%' }} to="agents" title="Browse Agents" glow />
<Hot s={{ left: '39%', top: '73%', width: '23.5%', height: '13%' }} to="booth" title="Enter My Booth" glow />
<Hot s={{ left: '75%', top: '23%', width: '22%', height: '28%' }} to="agents" title="Capabilities" />
{/* live platform stat chips (subtle, bottom-left — keeps the art clean) */}
{stats && (
<div className="hero-live">
<span><b>{stats.classes}</b> classes</span>
<span><b>{stats.online}</b> online</span>
<span><b>{stats.sessions}</b> sessions</span>
</div>
</div>
<StatsStrip />
)}
{auth?.user
? <div className="hero-acct">{auth.user.handle}{auth.user.role === 'admin' ? ' · admin' : ''} · <button onClick={() => auth.logout()}>log out</button></div>
: null}
</div>
</div>
);
@ -102,7 +103,7 @@ function Home({ setRoute }) {
function Page({ children }) { return <div className="td-page">{children}</div>; }
function Scene({ route, setRoute, auth }) {
if (route === 'home') return <Home setRoute={setRoute} />;
if (route === 'home') return <Home setRoute={setRoute} auth={auth} />;
if (route === 'agents') return <Page><AgentBrowse auth={auth} setRoute={setRoute} /></Page>;
if (route === 'booth') return <Page><BoothDashboard auth={auth} setRoute={setRoute} /></Page>;
if (route === 'signin') return <Page><div className="td-signin"><div className="px-board" style={{ position: 'static', transform: 'none', width: 'min(380px,92vw)', margin: '0 auto' }}>
@ -122,7 +123,7 @@ export default function Scene2DApp() {
return (
<main className="px-app td-app">
<h1 className="sr-only">amerc agent mercenary tavern</h1>
<PixelTopbar route={route} setRoute={setRoute} auth={auth} />
{route !== 'home' && <PixelTopbar route={route} setRoute={setRoute} auth={auth} />}
<Scene route={route} setRoute={setRoute} auth={auth} />
</main>
);

View File

@ -1,159 +0,0 @@
import React, { useEffect, useRef, useState } from 'react';
// Painterly 3/4 top-down RPG tavern smooth high-res canvas, warm lighting,
// cohesive with the painted character sprites.
const W = 1200, H = 760;
const rnd = (s) => { let x = Math.sin(s * 127.1) * 43758.5453; return x - Math.floor(x); };
function roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); }
function ellipse(ctx, x, y, rx, ry, c) { ctx.fillStyle = c; ctx.beginPath(); ctx.ellipse(x, y, rx, ry, 0, 0, 7); ctx.fill(); }
function buildScene(buf) {
const ctx = buf.getContext('2d');
// ---- floor: warm wood with radial light + planks ----
const fg = ctx.createLinearGradient(0, 180, 0, H);
fg.addColorStop(0, '#5a3a20'); fg.addColorStop(0.5, '#6e4727'); fg.addColorStop(1, '#4a2e18');
ctx.fillStyle = fg; ctx.fillRect(0, 180, W, H - 180);
for (let y = 200; y < H; y += 46) { // plank seams (perspective: wider toward bottom)
ctx.strokeStyle = 'rgba(40,24,12,0.55)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
ctx.strokeStyle = 'rgba(150,100,60,0.10)'; ctx.beginPath(); ctx.moveTo(0, y + 2); ctx.lineTo(W, y + 2); ctx.stroke();
for (let x = 0; x < W; x += 150) { ctx.strokeStyle = 'rgba(40,24,12,0.4)'; ctx.beginPath(); ctx.moveTo(x + rnd(x + y) * 60, y); ctx.lineTo(x + rnd(x + y) * 60, y + 46); ctx.stroke(); }
}
// ---- rug (center, patterned) ----
const cx = W / 2, ry = 470;
for (const [rw, rh, col] of [[420, 220, '#5a1a24'], [400, 205, '#7a2330'], [300, 150, '#5a1a24']]) { ctx.fillStyle = col; ctx.beginPath(); ctx.ellipse(cx, ry, rw / 2, rh / 2, 0, 0, 7); ctx.fill(); }
ctx.strokeStyle = '#d6a846'; ctx.lineWidth = 4; ctx.beginPath(); ctx.ellipse(cx, ry, 195, 98, 0, 0, 7); ctx.stroke();
ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(cx, ry - 70); ctx.lineTo(cx + 150, ry); ctx.lineTo(cx, ry + 70); ctx.lineTo(cx - 150, ry); ctx.closePath(); ctx.stroke();
// ---- back wall (top) stone with gradient + rail ----
const wg = ctx.createLinearGradient(0, 0, 0, 200);
wg.addColorStop(0, '#241c30'); wg.addColorStop(1, '#3a2f48');
ctx.fillStyle = wg; ctx.fillRect(0, 0, W, 190);
for (let y = 0; y < 180; y += 36) for (let x = (Math.floor(y / 36) % 2 ? 0 : -40); x < W; x += 80) { ctx.strokeStyle = 'rgba(18,12,26,0.7)'; ctx.lineWidth = 2; ctx.strokeRect(x, y, 80, 36); ctx.strokeStyle = 'rgba(120,108,140,0.15)'; ctx.beginPath(); ctx.moveTo(x + 2, y + 2); ctx.lineTo(x + 78, y + 2); ctx.stroke(); }
// wainscot rail
const rg = ctx.createLinearGradient(0, 175, 0, 200); rg.addColorStop(0, '#8a5a2e'); rg.addColorStop(1, '#5a3a1c');
ctx.fillStyle = rg; ctx.fillRect(0, 175, W, 22); ctx.fillStyle = 'rgba(180,130,80,0.5)'; ctx.fillRect(0, 175, W, 3);
ctx.fillStyle = 'rgba(0,0,0,0.35)'; ctx.fillRect(0, 197, W, 8); // shadow below rail
// windows (night sky)
for (const wx of [150, W - 230]) {
ctx.fillStyle = '#5a3a1c'; roundRect(ctx, wx - 6, 30, 92, 110, 8); ctx.fill();
const sg = ctx.createLinearGradient(0, 36, 0, 132); sg.addColorStop(0, '#16243f'); sg.addColorStop(1, '#0c1428');
ctx.fillStyle = sg; ctx.fillRect(wx, 36, 80, 98);
ctx.fillStyle = '#cfe0ff'; for (let i = 0; i < 9; i++) ctx.fillRect(wx + rnd(i + wx) * 76, 38 + rnd(i * 3) * 92, 2, 2);
ctx.fillStyle = '#5a3a1c'; ctx.fillRect(wx + 38, 36, 4, 98); ctx.fillRect(wx, 82, 80, 4);
}
// hearth (center back)
const hx = cx - 110;
ctx.fillStyle = '#2a2333'; roundRect(ctx, hx, 8, 220, 188, 10); ctx.fill();
ctx.fillStyle = '#1a1422'; roundRect(ctx, hx + 24, 50, 172, 130, 8); ctx.fill();
ctx.fillStyle = '#120a08'; roundRect(ctx, hx + 36, 66, 148, 110, 6); ctx.fill(); // fire cavity (fire animated)
const mg = ctx.createLinearGradient(0, 188, 0, 210); mg.addColorStop(0, '#9a6a36'); mg.addColorStop(1, '#5a3a1c');
ctx.fillStyle = mg; ctx.fillRect(hx - 14, 188, 248, 18);
// back-bar shelf with bottles (under rail, sides of hearth)
for (const sx of [40, hx + 250]) {
ctx.fillStyle = '#4a3018'; ctx.fillRect(sx, 150, 230, 30);
ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.fillRect(sx, 176, 230, 5);
const cols = ['#4a9a64', '#b46a52', '#6a82c8', '#caa54a', '#8a5ab0'];
for (let i = 0; i < 11; i++) { const bx = sx + 8 + i * 20, bc = cols[i % 5]; ctx.fillStyle = bc; roundRect(ctx, bx, 124, 9, 30, 3); ctx.fill(); ctx.fillStyle = 'rgba(255,255,255,0.25)'; ctx.fillRect(bx + 1, 126, 2, 22); }
}
// ---- bar counter (front of bartender) ----
const barY = 360, barW = 520, barX = cx - barW / 2;
ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.beginPath(); ctx.ellipse(cx, barY + 70, barW / 2 + 10, 30, 0, 0, 7); ctx.fill();
const bg = ctx.createLinearGradient(0, barY, 0, barY + 64); bg.addColorStop(0, '#a06a38'); bg.addColorStop(0.18, '#7a4a24'); bg.addColorStop(1, '#5a3418');
ctx.fillStyle = bg; roundRect(ctx, barX, barY, barW, 64, 10); ctx.fill();
ctx.fillStyle = 'rgba(200,150,90,0.6)'; roundRect(ctx, barX + 6, barY + 4, barW - 12, 8, 4); ctx.fill(); // top sheen
for (let i = 1; i < 7; i++) { ctx.strokeStyle = 'rgba(40,24,12,0.5)'; ctx.beginPath(); ctx.moveTo(barX + i * barW / 7, barY + 16); ctx.lineTo(barX + i * barW / 7, barY + 60); ctx.stroke(); }
for (const mx of [barX + 70, barX + barW - 90]) { ctx.fillStyle = '#caa54a'; roundRect(ctx, mx, barY + 14, 22, 26, 4); ctx.fill(); ctx.fillStyle = '#e8dca0'; roundRect(ctx, mx + 2, barY + 10, 18, 8, 3); ctx.fill(); }
// ---- tables + stools (sides) ----
for (const [tx, ty] of [[230, 600], [W - 230, 600]]) {
ctx.fillStyle = 'rgba(0,0,0,0.4)'; ellipse(ctx, tx, ty + 22, 70, 24, 'rgba(0,0,0,0.4)');
ellipse(ctx, tx, ty, 64, 40, '#5a3418'); ellipse(ctx, tx, ty - 4, 60, 36, '#7a4a24'); ellipse(ctx, tx - 12, ty - 10, 30, 16, '#9a6a3a');
ctx.fillStyle = '#e8dca0'; ctx.fillRect(tx - 4, ty - 14, 8, 14); // candle
}
// ---- barrels (left) + crates (right) ----
for (const [bx, by] of [[70, 580], [120, 612], [92, 660]]) {
ellipse(ctx, bx + 22, by + 50, 26, 8, 'rgba(0,0,0,0.4)');
const bgr = ctx.createLinearGradient(bx, 0, bx + 44, 0); bgr.addColorStop(0, '#6a4422'); bgr.addColorStop(0.5, '#8a5a30'); bgr.addColorStop(1, '#5a3618');
ctx.fillStyle = bgr; roundRect(ctx, bx, by, 44, 54, 8); ctx.fill();
ctx.fillStyle = '#c8a040'; ctx.fillRect(bx, by + 10, 44, 5); ctx.fillRect(bx, by + 38, 44, 5);
ellipse(ctx, bx + 22, by + 4, 22, 7, '#9a6a3a');
}
for (const [cx2, cy2] of [[W - 130, 600], [W - 78, 632]]) {
ellipse(ctx, cx2 + 26, cy2 + 52, 30, 8, 'rgba(0,0,0,0.4)');
ctx.fillStyle = '#7a4a24'; roundRect(ctx, cx2, cy2, 52, 50, 4); ctx.fill();
ctx.fillStyle = 'rgba(180,130,80,0.5)'; ctx.fillRect(cx2 + 3, cy2 + 3, 46, 4);
ctx.strokeStyle = '#5a3418'; ctx.lineWidth = 3; ctx.strokeRect(cx2 + 3, cy2 + 3, 46, 44); ctx.beginPath(); ctx.moveTo(cx2 + 4, cy2 + 4); ctx.lineTo(cx2 + 48, cy2 + 46); ctx.stroke();
}
// banners
for (const [bx, col] of [[60, '#3cc8f0'], [W - 96, '#d6a846']]) {
ctx.fillStyle = col; ctx.globalAlpha = 0.9; ctx.beginPath(); ctx.moveTo(bx, 6); ctx.lineTo(bx + 36, 6); ctx.lineTo(bx + 36, 110); ctx.lineTo(bx + 18, 124); ctx.lineTo(bx, 110); ctx.closePath(); ctx.fill(); ctx.globalAlpha = 1;
ctx.fillStyle = 'rgba(0,0,0,0.25)'; ctx.fillRect(bx + 24, 6, 12, 104);
ctx.fillStyle = '#d6a846'; roundRect(ctx, bx + 12, 48, 12, 12, 2); ctx.fill();
}
}
const LIGHTS = [[W / 2, 90, 150, 0.55, '255,170,90'], [W / 2, 360, 220, 0.22, '255,180,100'], [150, 60, 80, 0.3, '255,200,120'], [W - 190, 60, 80, 0.3, '255,200,120'], [230, 586, 70, 0.32, '255,190,110'], [W - 230, 586, 70, 0.32, '255,190,110']];
export default function TopDownTavern({ onHotspot }) {
const canvasRef = useRef(null);
useEffect(() => {
const buf = document.createElement('canvas'); buf.width = W; buf.height = H; buildScene(buf);
const ctx = canvasRef.current.getContext('2d');
let raf, f = 0;
const render = () => {
f++;
ctx.drawImage(buf, 0, 0);
// hearth fire
const hx = W / 2 - 74;
for (let i = 0; i < 6; i++) {
const fl = 0.55 + 0.45 * Math.sin(f * 0.16 + i * 1.3), bx = hx + 8 + i * 22;
const fgr = ctx.createLinearGradient(0, 176, 0, 100); fgr.addColorStop(0, '#ff8a2a'); fgr.addColorStop(1, 'rgba(255,210,90,0)');
ctx.fillStyle = fgr; ctx.beginPath(); ctx.moveTo(bx, 178); ctx.quadraticCurveTo(bx + 8, 150 - fl * 30, bx + 11, 120 - fl * 26); ctx.quadraticCurveTo(bx + 14, 150 - fl * 30, bx + 22, 178); ctx.fill();
}
// warm lighting
ctx.globalCompositeOperation = 'lighter';
for (const [lx, ly, lr, la, rgb] of LIGHTS) {
const a = la * (0.85 + 0.15 * Math.sin(f * 0.09 + lx));
const g = ctx.createRadialGradient(lx, ly, 0, lx, ly, lr); g.addColorStop(0, `rgba(${rgb},${a})`); g.addColorStop(1, `rgba(${rgb},0)`);
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(lx, ly, lr, 0, 7); ctx.fill();
}
ctx.globalCompositeOperation = 'source-over';
// vignette
const vg = ctx.createRadialGradient(W / 2, H * 0.5, H * 0.28, W / 2, H * 0.5, H * 0.82);
vg.addColorStop(0, 'rgba(0,0,0,0)'); vg.addColorStop(1, 'rgba(8,4,12,0.7)');
ctx.fillStyle = vg; ctx.fillRect(0, 0, W, H);
// dust
ctx.fillStyle = 'rgba(255,220,160,0.45)';
for (let i = 0; i < 22; i++) { const dx = (i * 67 + f * 0.35) % W, dy = (i * 53 + Math.sin(f * 0.018 + i) * 24 + 220) % H; ctx.fillRect(dx, dy, 2, 2); }
raf = requestAnimationFrame(render);
};
render();
return () => cancelAnimationFrame(raf);
}, []);
return (
<div className="td-room">
<canvas ref={canvasRef} width={W} height={H} className="td-canvas" />
<div className="td-overlay">
<Npc sprite="/scene2d/orc.png" x={50} y={20} h={36} label="Grommash · Bartender" say="Pick a mercenary, friend." onClick={() => onHotspot?.('agents')} />
<Npc sprite="/scene2d/dwarf.png" x={20} y={56} h={30} label="Durn · Patron" say="My booth runs three agents." onClick={() => onHotspot?.('booth')} />
<Npc sprite="/scene2d/elf.png" x={80} y={52} h={33} flip label="Aelis · Guide" say="Browse the roster, or open your booth." onClick={() => onHotspot?.('agents')} />
<button className="td-hot td-hot-board" style={{ left: '70%', top: '40%' }} onClick={() => onHotspot?.('agents')}>
<span className="td-hot-tag">NOTICE BOARD</span><strong>Browse Agents </strong>
</button>
<button className="td-hot td-hot-door" style={{ left: '50%', top: '90%' }} onClick={() => onHotspot?.('booth')}>Enter My Booth </button>
</div>
</div>
);
}
function Npc({ sprite, x, y, h, flip, label, say, onClick }) {
const [hover, setHover] = useState(false);
return (
<button className="td-npc" style={{ left: `${x}%`, top: `${y}%`, height: `${h}%` }}
onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onClick={onClick} aria-label={label}>
{hover && <span className="td-say"><strong>{label}</strong><em>{say}</em></span>}
<img src={sprite} alt="" draggable={false} style={{ transform: flip ? 'scaleX(-1)' : 'none' }} />
<span className="td-npc-shadow" />
</button>
);
}

View File

@ -489,7 +489,7 @@ button {
inset: 0;
display: flex;
flex-direction: column;
font-family: "Press Start 2P", ui-monospace, "Courier New", monospace;
font-family: Georgia, "Times New Roman", serif;
background: #07060c;
overflow: hidden;
}
@ -778,7 +778,7 @@ button {
.cs-app { position: fixed; inset: 0; display: flex; flex-direction: column;
background: #0a0e17; color: #dce6f5; font-family: Inter, ui-sans-serif, system-ui, sans-serif; --accent: #27d8ff; }
.cs-loading { margin: auto; color: #6c7b96; font-family: ui-monospace, monospace; }
.cs-brand { display: flex; align-items: center; gap: 8px; font-family: "Press Start 2P", monospace; font-size: 15px; color: #fff; }
.cs-brand { display: flex; align-items: center; gap: 8px; font-family: Georgia, "Times New Roman", serif; font-size: 15px; color: #fff; }
.cs-brand small { font-family: Inter, sans-serif; font-size: 11px; color: var(--accent); letter-spacing: 1px; }
.cs-gem { width: 12px; height: 12px; background: var(--accent); transform: rotate(45deg); box-shadow: 0 0 0 2px #07273a; }
@ -796,7 +796,7 @@ button {
.cs-login { display: flex; flex-direction: column; gap: 12px; width: 330px; padding: 26px 24px; color: #fff0d7;
background: linear-gradient(180deg, #2a1608, #160a04); border: 3px solid #000; border-radius: 4px;
box-shadow: inset 0 0 0 2px #6b3a1c, inset 0 0 26px rgba(0,0,0,0.7), 6px 6px 0 #000, 0 24px 60px rgba(0,0,0,0.6);
font-family: "Press Start 2P", ui-monospace, monospace; }
font-family: Georgia, "Times New Roman", serif; }
.cs-login .cs-brand { justify-content: center; margin-bottom: 8px; color: #f7bc5d; }
.cs-login .cs-gem { background: #27d8ff; }
.cs-tabs { display: flex; gap: 6px; }
@ -935,20 +935,20 @@ button {
.td-say { position: absolute; bottom: 102%; left: 50%; transform: translateX(-50%); width: max-content; max-width: 180px; padding: 6px 8px; font-size: 8px; line-height: 1.6; color: #1c1206; background: var(--cream); border: 2px solid #000; box-shadow: 2px 2px 0 #000; text-align: center; z-index: 6; }
.td-say strong { color: #7a3d0c; } .td-say em { display: block; font-style: normal; color: #3a2a14; margin-top: 2px; }
.td-hot { position: absolute; transform: translate(-50%, -50%); font-family: "Press Start 2P", monospace; cursor: pointer; z-index: 4; }
.td-hot { position: absolute; transform: translate(-50%, -50%); font-family: Georgia, "Times New Roman", serif; cursor: pointer; z-index: 4; }
.td-hot-board { display: flex; flex-direction: column; gap: 4px; align-items: center; padding: 8px 14px; color: #cdeaff; background: rgba(8,20,36,0.82); border: 2px solid #27d8ff; box-shadow: 0 0 18px rgba(39,216,255,0.4), 3px 3px 0 #000; font-size: 11px; }
.td-hot-board .td-hot-tag { font-size: 6px; letter-spacing: 2px; color: #27d8ff; }
.td-hot-board:hover { background: rgba(12,30,52,0.92); }
.td-hot-door { padding: 8px 14px; font-size: 10px; color: #36f0b0; background: rgba(8,24,18,0.82); border: 2px solid #36f0b0; box-shadow: 0 0 16px rgba(54,240,176,0.35), 3px 3px 0 #000; }
.td-hot-door:hover { background: rgba(12,34,26,0.92); }
.td-hero { position: absolute; left: 2.5%; top: 6%; width: min(40%, 380px); z-index: 4; font-family: "Press Start 2P", monospace;
.td-hero { position: absolute; left: 2.5%; top: 6%; width: min(40%, 380px); z-index: 4; font-family: Georgia, "Times New Roman", serif;
padding: 16px 18px; background: linear-gradient(180deg, rgba(12,6,18,0.72), rgba(12,6,18,0.45)); border-radius: 10px; backdrop-filter: blur(2px); }
.td-hero h1 { font-size: clamp(13px, 2.1vw, 22px); line-height: 1.5; color: #fff; text-shadow: 2px 2px 0 #000, 0 0 18px rgba(0,0,0,0.8); margin: 0 0 10px; }
.td-hero p { font-family: Inter, sans-serif; font-size: clamp(10px, 1.3vw, 13px); line-height: 1.7; color: #e7d4b6; text-shadow: 1px 1px 2px #000; margin: 0 0 12px; }
.td-hero-cta { display: flex; gap: 8px; flex-wrap: wrap; }
.td-stats { position: absolute; left: 50%; bottom: 3%; transform: translateX(-50%); display: flex; gap: 6px; z-index: 4; }
.td-stat { display: flex; flex-direction: column; align-items: center; padding: 6px 10px; background: rgba(8,12,22,0.8); border: 2px solid #000; box-shadow: 2px 2px 0 #000; font-family: "Press Start 2P", monospace; }
.td-stat { display: flex; flex-direction: column; align-items: center; padding: 6px 10px; background: rgba(8,12,22,0.8); border: 2px solid #000; box-shadow: 2px 2px 0 #000; font-family: Georgia, "Times New Roman", serif; }
.td-stat b { font-size: 14px; color: #27d8ff; } .td-stat span { font-size: 6px; color: #9fb0d0; margin-top: 3px; letter-spacing: 1px; }
.td-page { flex: 1; overflow: auto; background: #0a0710 radial-gradient(circle at 50% -10%, #1a0f22, #0a0710 60%); }
@ -957,7 +957,7 @@ button {
/* agent platform */
.ap { max-width: 1080px; margin: 0 auto; padding: 24px 20px 60px; color: #e7d4b6; font-family: Inter, sans-serif; }
.ap-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.ap-head h2 { font-family: "Press Start 2P", monospace; font-size: 18px; color: #fff; margin: 0; }
.ap-head h2 { font-family: Georgia, "Times New Roman", serif; font-size: 18px; color: #fff; margin: 0; }
.ap-by { color: #9a86b8; font-size: 13px; }
.ap-search { margin-left: auto; padding: 9px 12px; border-radius: 8px; background: #140d1e; border: 1px solid #2e2440; color: #eaf2ff; font-family: inherit; }
.ap-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 18px; }
@ -975,7 +975,7 @@ button {
.ap-card-foot { display: flex; justify-content: space-between; font-size: 11px; color: #8a7aa8; margin-top: auto; }
.ap-online { display: flex; align-items: center; gap: 5px; } .ap-online i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.ap-hint { color: #8a7aa8; font-size: 12px; line-height: 1.6; }
.ap-h4 { font-family: "Press Start 2P", monospace; font-size: 10px; color: #27d8ff; margin: 16px 0 8px; }
.ap-h4 { font-family: Georgia, "Times New Roman", serif; font-size: 10px; color: #27d8ff; margin: 16px 0 8px; }
.ap-modal { position: fixed; inset: 0; background: rgba(4,6,14,0.75); display: flex; align-items: center; justify-content: center; z-index: 200; padding: 16px; }
.ap-modal-box { width: min(640px, 96vw); max-height: 88vh; overflow: auto; background: #120c1e; border: 2px solid #2e2440; border-radius: 14px; padding: 18px; }
@ -1010,3 +1010,19 @@ button {
.td-stage { width: 96vw; } .td-stats { position: static; transform: none; flex-wrap: wrap; justify-content: center; margin-top: 10px; }
.td-home { flex-direction: column; } .ap-booth-grid { grid-template-columns: 1fr; }
}
/* ===================================================================== */
/* 0.23 — concept-art hero frontpage (interactive layers) */
/* ===================================================================== */
.hero-wrap { position: relative; flex: 1; display: flex; align-items: center; justify-content: center; background: radial-gradient(circle at 50% 40%, #14100c, #000); overflow: hidden; }
.hero-stage { position: relative; width: min(100vw, calc((100vh - 0px) * 1.777)); aspect-ratio: 1672 / 941; box-shadow: 0 0 80px rgba(0,0,0,0.8); }
.hero-img { width: 100%; height: 100%; display: block; object-fit: cover; }
.hero-hot { position: absolute; background: transparent; border: 0; padding: 0; cursor: pointer; border-radius: 10px; transition: box-shadow .14s ease, background .14s ease; }
.hero-hot:hover { background: rgba(70,200,255,0.10); box-shadow: 0 0 0 2px rgba(80,205,255,0.55), 0 0 26px rgba(70,200,255,0.45); }
.hero-glow:hover { background: rgba(70,200,255,0.07); box-shadow: 0 0 34px rgba(70,200,255,0.4); }
.hero-live { position: absolute; left: 2.5%; bottom: 3.5%; display: flex; gap: 10px; font-family: Georgia, serif; }
.hero-live span { font-size: clamp(10px, 1.05vw, 14px); color: #cfe0ff; background: rgba(8,16,30,0.66); border: 1px solid rgba(80,200,255,0.35); border-radius: 8px; padding: 6px 11px; backdrop-filter: blur(2px); }
.hero-live b { color: #5fe0ff; font-size: 1.18em; }
.hero-acct { position: absolute; right: 2.5%; bottom: 3.5%; font-family: Georgia, serif; font-size: clamp(10px, 1vw, 13px); color: #dfe8ff; background: rgba(8,16,30,0.66); border: 1px solid rgba(120,150,200,0.35); border-radius: 8px; padding: 6px 11px; }
.hero-acct button { color: #5fe0ff; background: none; cursor: pointer; font-family: inherit; font-size: inherit; text-decoration: underline; }
@media (max-width: 640px) { .hero-live span:nth-child(3) { display: none; } }