- Server-authoritative Node/WS server: sessions, zones (town + instanced homes + public venue interiors), NPC routines, economy, relationships, persistence - TypeScript + Three.js client: character creator, Maple Court district, enterable venues (café, city hall, gym, store, arcade), build mode with 58-item furniture catalog, needs/mood systems, phone UI, day/night + weather, procedural audio - Verified by two-client e2e protocol suite and headless-browser walkthrough
198 lines
9.0 KiB
HTML
198 lines
9.0 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
|
<meta name="theme-color" content="#141826" />
|
|
<title>LifeTown Online</title>
|
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='46' fill='%23ff7e5f'/%3E%3Cpath d='M28 62V44l22-18 22 18v18a4 4 0 0 1-4 4H32a4 4 0 0 1-4-4z' fill='%23fff'/%3E%3C/svg%3E" />
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<canvas id="gl"></canvas>
|
|
|
|
<!-- ===== Title / login screen ===== -->
|
|
<div id="screen-title" class="screen">
|
|
<div class="title-sky"></div>
|
|
<div class="title-card glass">
|
|
<div class="wordmark">Life<span>Town</span></div>
|
|
<div class="tagline">your town · your people · your story</div>
|
|
<div class="field-row">
|
|
<input id="inp-name" maxlength="20" placeholder="Choose your name…" autocomplete="off" spellcheck="false"/>
|
|
</div>
|
|
<button id="btn-play" class="btn btn-primary btn-big">Enter LifeTown</button>
|
|
<button id="btn-newchar" class="btn btn-ghost">Create New Character</button>
|
|
<div class="hint" id="title-hint"></div>
|
|
</div>
|
|
<div class="title-foot">An original multiplayer life simulation · v0.1 MVP</div>
|
|
</div>
|
|
|
|
<!-- ===== Character creator ===== -->
|
|
<div id="screen-creator" class="screen hidden">
|
|
<div class="creator-layout">
|
|
<div id="creator-preview"></div>
|
|
<div class="creator-panel glass">
|
|
<div class="wordmark small">Design your resident</div>
|
|
<div class="creator-scroll">
|
|
<section>
|
|
<label>Name</label>
|
|
<input id="cr-name" maxlength="20" placeholder="Your name" autocomplete="off"/>
|
|
<label>Age · <span id="cr-age-val">25</span></label>
|
|
<input type="range" id="cr-age" min="18" max="70" value="25"/>
|
|
</section>
|
|
<section>
|
|
<label>Body</label>
|
|
<div class="chips" data-cr="bodyType"></div>
|
|
<label>Height</label>
|
|
<input type="range" id="cr-height" min="85" max="112" value="97"/>
|
|
</section>
|
|
<section><label>Skin tone</label><div class="swatches" data-cr="skin"></div></section>
|
|
<section>
|
|
<label>Hair style</label><div class="chips" data-cr="hair"></div>
|
|
<label>Hair colour</label><div class="swatches" data-cr="hairColor"></div>
|
|
</section>
|
|
<section><label>Eyes</label><div class="chips" data-cr="eyes"></div>
|
|
<label>Eye colour</label><div class="swatches" data-cr="eyeColor"></div></section>
|
|
<section>
|
|
<label>Brows</label><div class="chips" data-cr="brows"></div>
|
|
<label>Nose</label><div class="chips" data-cr="nose"></div>
|
|
<label>Mouth</label><div class="chips" data-cr="mouth"></div>
|
|
</section>
|
|
<section>
|
|
<label>Outfit top</label><div class="chips" data-cr="outfitTop"></div>
|
|
<label>Top colour</label><div class="swatches" data-cr="topColor"></div>
|
|
<label>Bottom</label><div class="chips" data-cr="outfitBottom"></div>
|
|
<label>Bottom colour</label><div class="swatches" data-cr="bottomColor"></div>
|
|
<label>Shoes</label><div class="chips" data-cr="shoes"></div>
|
|
</section>
|
|
<section><label>Accessory</label><div class="chips" data-cr="accessory"></div></section>
|
|
<section>
|
|
<label>Personality <span class="dim">(pick up to 3)</span></label>
|
|
<div class="chips multi" data-cr="personality"></div>
|
|
</section>
|
|
<section>
|
|
<label>Voice</label><div class="chips" data-cr="voice"></div>
|
|
</section>
|
|
</div>
|
|
<button id="btn-startlife" class="btn btn-primary btn-big">Start My Life ✨</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ===== In-game HUD ===== -->
|
|
<div id="hud" class="hidden">
|
|
<!-- top bar -->
|
|
<div id="topbar">
|
|
<div class="pill money-pill" title="TownCoins"><span class="coin">◉</span><span id="tb-money">0</span></div>
|
|
<div class="pill clock-pill"><span id="tb-clock">07:30</span> · <span id="tb-day">Day 1</span></div>
|
|
<div class="pill weather-pill"><span id="tb-weather">☀️</span><span id="tb-zone">Maple Court</span></div>
|
|
<div class="pill online-pill" title="Residents online"><span>👥</span><span id="tb-online">1</span></div>
|
|
<div class="top-actions">
|
|
<button class="iconbtn" id="btn-audio" title="Sound">🔊</button>
|
|
<button class="iconbtn" id="btn-fullscreen" title="Fullscreen">⛶</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- notifications -->
|
|
<div id="toasts"></div>
|
|
|
|
<!-- right side: quests / context -->
|
|
<div id="context-panel" class="glass">
|
|
<div class="cp-title">✦ Now</div>
|
|
<div id="cp-content"></div>
|
|
</div>
|
|
|
|
<!-- bottom bar -->
|
|
<div id="bottombar">
|
|
<div id="need-rings"></div>
|
|
<div id="mood-chip" class="glass"><span id="mood-emoji">🙂</span><span id="mood-label">Okay</span></div>
|
|
<div id="action-bar">
|
|
<button class="abtn" data-act="interact">👋<span>Act</span></button>
|
|
<button class="abtn" data-act="emote">💃<span>Dance</span></button>
|
|
<button class="abtn" data-act="phone">📱<span>Phone</span></button>
|
|
<button class="abtn" data-act="map">🗺️<span>Map</span></button>
|
|
<button class="abtn" data-act="bag">🎒<span>Bag</span></button>
|
|
<button class="abtn" data-act="home">🏠<span>Home</span></button>
|
|
<button class="abtn" data-act="build">🔨<span>Build</span></button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- interaction prompt -->
|
|
<div id="prompt" class="hidden"><kbd id="prompt-key">E</kbd><span id="prompt-text"></span></div>
|
|
|
|
<!-- speech bubble layer anchor -->
|
|
<div id="bubble-layer"></div>
|
|
|
|
<!-- chat -->
|
|
<div id="chat-panel">
|
|
<div id="chat-log"></div>
|
|
<div class="chat-input-row">
|
|
<input id="chat-input" placeholder="Say something… (Enter)" maxlength="240" autocomplete="off"/>
|
|
<select id="chat-channel"><option value="say">Say</option><option value="global">Global</option></select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- mobile controls -->
|
|
<div id="touch-ui" class="hidden">
|
|
<div id="joy-base"><div id="joy-knob"></div></div>
|
|
<div id="touch-buttons">
|
|
<button id="tb-interact">✋</button>
|
|
<button id="tb-jump-run">🏃</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- interaction wheel -->
|
|
<div id="wheel" class="hidden glass"></div>
|
|
|
|
<!-- phone -->
|
|
<div id="phone" class="hidden">
|
|
<div class="phone-frame">
|
|
<div class="phone-top"><span id="phone-clock">07:30</span><span>LTE ◉◉◉</span></div>
|
|
<div id="phone-screen"></div>
|
|
<div class="phone-dock" id="phone-dock"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- build mode UI -->
|
|
<div id="buildbar" class="hidden">
|
|
<div class="bb-title">🔨 Build Mode</div>
|
|
<div class="bb-tools">
|
|
<button class="tool active" data-tool="place">🛋 Place</button>
|
|
<button class="tool" data-tool="move">✥ Move</button>
|
|
<button class="tool" data-tool="rotate">⟳ Rotate</button>
|
|
<button class="tool" data-tool="duplicate">⧉ Copy</button>
|
|
<button class="tool" data-tool="delete">🗑 Sell</button>
|
|
</div>
|
|
<button id="btn-shop" class="btn btn-accent">🛒 Shop Furniture</button>
|
|
<button id="btn-exit-build" class="btn btn-ghost">✓ Live Mode</button>
|
|
</div>
|
|
|
|
<!-- catalog modal -->
|
|
<div id="catalog" class="modal hidden"><div class="modal-card glass">
|
|
<div class="modal-head"><span>🛒 MapleMart Catalogue</span><button class="iconbtn" id="catalog-close">✕</button></div>
|
|
<div class="cat-tabs" id="cat-tabs"></div>
|
|
<div id="cat-grid" class="cat-grid"></div>
|
|
</div></div>
|
|
|
|
<!-- inventory modal -->
|
|
<div id="inventory" class="modal hidden"><div class="modal-card glass">
|
|
<div class="modal-head"><span>🎒 Inventory</span><button class="iconbtn" id="inv-close">✕</button></div>
|
|
<div id="inv-grid" class="cat-grid"></div>
|
|
</div></div>
|
|
|
|
<!-- generic dialog -->
|
|
<div id="dialog" class="modal hidden"><div class="modal-card glass small">
|
|
<div class="modal-head"><span id="dialog-title"></span><button class="iconbtn" id="dialog-close">✕</button></div>
|
|
<div id="dialog-body"></div>
|
|
</div></div>
|
|
|
|
<!-- minimap -->
|
|
<canvas id="minimap" width="180" height="180"></canvas>
|
|
</div>
|
|
|
|
<div id="loading" class="hidden"><div class="loader-ring"></div><div id="loading-text">Warming up LifeTown…</div></div>
|
|
</div>
|
|
<script type="module" src="/src/main.ts"></script>
|
|
</body>
|
|
</html>
|