mansion: a moonlit villa estate (2.3.0)

My Mansion gets a CSS villa scene — dusk sky, glowing/flickering windows,
waving flag, garden hedge — with the live services (Showcase, File-mapper,
Net-disk) presented as 'chambers of the estate'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
artheru 2026-06-11 09:30:06 +08:00
parent e308c9be4d
commit 6b83b661fa
5 changed files with 50 additions and 9 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="2.2.0-warcamp" /> <meta name="version" content="2.3.0-villa" />
<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": "2.2.0-warcamp", "version": "2.3.0-villa",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -10,17 +10,33 @@ export default function Mansion({ auth, setRoute }) {
const handle = loggedIn ? auth.user.handle.toLowerCase().replace(/[^a-z0-9]/g, '') : 'you'; const handle = loggedIn ? auth.user.handle.toLowerCase().replace(/[^a-z0-9]/g, '') : 'you';
return ( return (
<div className="ap mn"> <div className="ap mn">
<header className="mn-hero"> <header className="mn-villa">
<div className="mn-hero-emblem" aria-hidden="true">🏰</div> <div className="mn-villa-scene" aria-hidden="true">
<div className="mn-hero-body"> <span className="mn-villa-moon" />
<span className="mn-villa-star" style={{ left: '18%', top: '22%' }} />
<span className="mn-villa-star" style={{ left: '40%', top: '12%' }} />
<span className="mn-villa-star" style={{ left: '70%', top: '30%' }} />
<div className="mn-villa-flag"><i /></div>
<div className="mn-villa-roof" />
<div className="mn-villa-house">
<div className="mn-villa-windows">
{Array.from({ length: 6 }).map((_, i) => <span key={i} className={`mn-win${i === 4 ? ' dark' : ''}`} style={{ animationDelay: `${i * 0.4}s` }} />)}
</div>
<div className="mn-villa-door" />
</div>
<div className="mn-villa-hedge" />
</div>
<div className="mn-villa-body">
<span className="mn-villa-tag">🏰 THE ESTATE</span>
<h2>My Mansion</h2> <h2>My Mansion</h2>
<p>A hall of amerc services that put your work on the open internet each lives behind amerc's edge and is brought to life by your own broker. No servers to rent, no ports to forward.</p> <p>Your estate on the open internet a villa of amerc services, each room brought to life by your own broker. No servers to rent, no ports to forward.</p>
{loggedIn {loggedIn
? <span className="mn-hero-you"><i className="mn-live-dot" /> {auth.user.handle}'s rooms are open</span> ? <span className="mn-hero-you"><i className="mn-live-dot" /> {auth.user.handle}'s estate the gates are open</span>
: <button className="px-action" onClick={() => setRoute('signin')}>Log in to open a room </button>} : <button className="px-action" onClick={() => setRoute('signin')}>Log in to open the gates </button>}
</div> </div>
</header> </header>
<h3 className="mn-wing-title"> Chambers of the estate</h3>
<div className="mn-rooms"> <div className="mn-rooms">
<ShowcaseRoom loggedIn={loggedIn} handle={handle} setRoute={setRoute} /> <ShowcaseRoom loggedIn={loggedIn} handle={handle} setRoute={setRoute} />
<FileMapperRoom loggedIn={loggedIn} setRoute={setRoute} /> <FileMapperRoom loggedIn={loggedIn} setRoute={setRoute} />

View File

@ -11,7 +11,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 = '2.2.0-warcamp'; const RELEASE = '2.3.0-villa';
const NAV = [ const NAV = [
{ id: 'home', label: 'Tavern' }, { id: 'home', label: 'Tavern' },
@ -476,6 +476,7 @@ function StatusPage() {
} }
const CHANGELOG = [ const CHANGELOG = [
{ v: '2.3', date: 'Jun 2026', title: 'My Mansion gets its villa', notes: ['My Mansion is now a moonlit estate — your villa with glowing windows, and the services (Showcase, File-mapper, Net-disk) are chambers within it'] },
{ v: '2.2', date: 'Jun 2026', title: 'My Legion becomes a war camp', notes: ['Your classes are banners and each instance is a soldier — alive and bobbing when online, standing by when pending, fallen when down', 'Tap a soldier to command it: chat with your own running agent, watch its live terminal, or stand it down', 'Statuses refresh on their own so the camp stays current'] }, { v: '2.2', date: 'Jun 2026', title: 'My Legion becomes a war camp', notes: ['Your classes are banners and each instance is a soldier — alive and bobbing when online, standing by when pending, fallen when down', 'Tap a soldier to command it: chat with your own running agent, watch its live terminal, or stand it down', 'Statuses refresh on their own so the camp stays current'] },
{ v: '2.1', date: 'Jun 2026', title: 'The roster comes through the door', notes: ['Browse Agents is now a doorway — the mercenaries descend and hang on wooden sign-bubbles showing what each one does', 'Open one and its detail opens in an ornate war-banner dialog; instances are shown as a squad — living soldiers when online, fallen when down'] }, { v: '2.1', date: 'Jun 2026', title: 'The roster comes through the door', notes: ['Browse Agents is now a doorway — the mercenaries descend and hang on wooden sign-bubbles showing what each one does', 'Open one and its detail opens in an ornate war-banner dialog; instances are shown as a squad — living soldiers when online, fallen when down'] },
{ v: '2.0', date: 'Jun 2026', title: 'The tavern becomes the interface', notes: ['The home page is now a full-screen game scene — every fixture is a door: the grand door is the roster, the bar signs you in, the banners are your Legion, the side passage leads to My Mansion', 'Speech bubbles everywhere: the dancer pitches the tour in a glowing holo-panel, the barkeep greets you by name', 'Details open in ornate in-game dialogs instead of plain pages'] }, { v: '2.0', date: 'Jun 2026', title: 'The tavern becomes the interface', notes: ['The home page is now a full-screen game scene — every fixture is a door: the grand door is the roster, the bar signs you in, the banners are your Legion, the side passage leads to My Mansion', 'Speech bubbles everywhere: the dancer pitches the tour in a glowing holo-panel, the barkeep greets you by name', 'Details open in ornate in-game dialogs instead of plain pages'] },

View File

@ -1807,3 +1807,27 @@ button {
.lg-tui-head { padding: 5px 11px; background: #0d1320; color: #5b6b7d; font-size: 10px; letter-spacing: 0.6px; text-transform: uppercase; border-bottom: 1px solid #1c2438; } .lg-tui-head { padding: 5px 11px; background: #0d1320; color: #5b6b7d; font-size: 10px; letter-spacing: 0.6px; text-transform: uppercase; border-bottom: 1px solid #1c2438; }
.lg-tui pre { margin: 0; padding: 11px; font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: #9fe6c0; max-height: 240px; overflow: auto; white-space: pre-wrap; word-break: break-word; } .lg-tui pre { margin: 0; padding: 11px; font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: #9fe6c0; max-height: 240px; overflow: auto; white-space: pre-wrap; word-break: break-word; }
.lg-rouse { margin-top: 2px; } .lg-rouse { margin-top: 2px; }
/* ===== My Mansion = a villa estate (2.3) ===== */
.mn-villa { display: flex; align-items: stretch; gap: 22px; margin-bottom: 18px; background: linear-gradient(180deg, #15102a, #0d0a18); border: 1px solid #2a2440; border-radius: 16px; padding: 16px; overflow: hidden; }
.mn-villa-scene { position: relative; flex: 0 0 auto; width: 300px; height: 200px; border-radius: 12px; overflow: hidden; background: linear-gradient(180deg, #1a1840 0%, #3a2a5a 55%, #5a3a4a 78%, #2a1d2a 100%); box-shadow: inset 0 0 30px rgba(0,0,0,0.5); }
.mn-villa-moon { position: absolute; right: 26px; top: 22px; width: 36px; height: 36px; border-radius: 50%; background: radial-gradient(circle at 38% 35%, #fff6e0, #ffd98a 60%, #e8b860); box-shadow: 0 0 24px rgba(255,220,150,0.6); }
.mn-villa-star { position: absolute; width: 3px; height: 3px; border-radius: 50%; background: #fff; box-shadow: 0 0 5px #fff; animation: mnTwinkle 3s ease-in-out infinite; }
@keyframes mnTwinkle { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
.mn-villa-roof { position: absolute; left: 50px; bottom: 102px; width: 0; height: 0; border-left: 100px solid transparent; border-right: 100px solid transparent; border-bottom: 46px solid #5a2e22; filter: drop-shadow(0 -2px 0 #7a3e2e); }
.mn-villa-house { position: absolute; left: 70px; bottom: 26px; width: 160px; height: 80px; background: linear-gradient(180deg, #d8c79a, #b89a6e); border-radius: 3px; box-shadow: inset 0 0 16px rgba(90,46,34,0.35); }
.mn-villa-windows { position: absolute; inset: 12px 18px 30px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 16px; }
.mn-win { border-radius: 2px; background: #ffcf6a; box-shadow: 0 0 10px rgba(255,207,106,0.85); animation: mnFlicker 4s ease-in-out infinite; }
.mn-win.dark { background: #4a3a2a; box-shadow: none; }
@keyframes mnFlicker { 0%, 100% { opacity: 0.92; } 45% { opacity: 0.7; } 70% { opacity: 1; } }
.mn-villa-door { position: absolute; left: 50%; bottom: 0; width: 26px; height: 38px; transform: translateX(-50%); background: #5a3a1c; border-radius: 13px 13px 0 0; box-shadow: inset 0 0 6px rgba(0,0,0,0.5); }
.mn-villa-flag { position: absolute; left: 50%; bottom: 144px; width: 2px; height: 30px; background: #3a2a1a; transform: translateX(-50%); z-index: 3; }
.mn-villa-flag i { position: absolute; top: 0; left: 2px; width: 22px; height: 13px; background: #c44; clip-path: polygon(0 0, 100% 0, 80% 50%, 100% 100%, 0 100%); animation: mnWave 2.5s ease-in-out infinite; transform-origin: left; }
@keyframes mnWave { 0%, 100% { transform: skewY(0deg); } 50% { transform: skewY(-6deg); } }
.mn-villa-hedge { position: absolute; left: 0; right: 0; bottom: 0; height: 28px; background: repeating-radial-gradient(circle at 14px 18px, #2c4a2e 0 12px, #234023 12px 16px); background-color: #1e3620; }
.mn-villa-body { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 7px; min-width: 0; }
.mn-villa-tag { font-size: 10px; letter-spacing: 1.6px; color: #ffd75e; align-self: flex-start; background: rgba(255,215,94,0.08); border: 1px solid #5a4a1c; padding: 3px 10px; border-radius: 999px; }
.mn-villa-body h2 { margin: 2px 0; font-family: Georgia, serif; font-size: 27px; color: #f3e9d2; }
.mn-villa-body p { margin: 0 0 6px; color: #9b8fb8; font-size: 13.5px; line-height: 1.5; max-width: 540px; }
.mn-wing-title { font-family: Georgia, serif; color: #cbb8e8; font-size: 16px; margin: 4px 2px 12px; letter-spacing: 0.5px; }
@media (max-width: 680px) { .mn-villa { flex-direction: column; align-items: center; } .mn-villa-scene { width: 100%; max-width: 320px; } .mn-villa-body { align-items: center; text-align: center; } .mn-villa-tag { align-self: center; } }