Files
lifetown/client/src/ui/common.ts
T
deepseek 93b018e42b LifeTown Online — browser multiplayer life-sim MVP
- 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
2026-08-23 07:01:24 +00:00

62 lines
2.5 KiB
TypeScript

// LifeTown Online — UI helpers shared across modules.
import { bus } from '../state';
import { audio } from '../audio';
export function $(sel: string): HTMLElement { return document.querySelector(sel) as HTMLElement; }
export function $$(sel: string): HTMLElement[] { return [...document.querySelectorAll(sel)] as any; }
export function el(tag: string, cls?: string, html?: string): HTMLElement {
const e = document.createElement(tag);
if (cls) e.className = cls;
if (html != null) e.innerHTML = html;
return e;
}
let toastN = 0;
export function toast(title: string, text = '', icon = '✨', kind: '' | 'gold' | 'coral' = '', onClick?: () => void) {
const box = $('#toasts');
const t = el('div', `toast ${kind}`);
t.innerHTML = `<span class="t-icon">${icon}</span><div><b>${title}</b><p>${text}</p></div>`;
if (onClick) t.addEventListener('click', () => { onClick(); t.remove(); });
box.appendChild(t);
toastN++;
setTimeout(() => { t.remove(); toastN--; }, 5200);
while (box.children.length > 4) box.firstChild!.remove();
}
bus.on('srv:notify', (m: any) => {
toast(m.title || '', m.text || '', m.icon || '✨', m.title === 'Promotion!' ? 'gold' : '',
m.action?.type === 'visit' ? () => netVisit(m.action.home) : undefined);
});
bus.on('srv:achievement', (m: any) => {
audio.achievement();
toast(`🏆 Achievement — ${m.name}`, m.desc, '🏆', 'gold');
});
import { net } from '../net';
function netVisit(home: string) { net.send({ t: 'zone', id: home }); }
export function openDialog(title: string, buildBody: (body: HTMLElement, close: () => void) => void) {
const modal = $('#dialog');
$('#dialog-title').textContent = title;
const body = $('#dialog-body');
body.innerHTML = '';
modal.classList.remove('hidden');
const close = () => modal.classList.add('hidden');
($('#dialog-close') as HTMLElement).onclick = close;
buildBody(body, close);
audio.click();
}
export function closeDialog() { $('#dialog').classList.add('hidden'); }
export function fmtMoney(n: number): string {
return n >= 1e6 ? `${(n / 1e6).toFixed(2)}M` : n.toLocaleString('en-US');
}
/** emoji icon per furniture category */
export const CAT_EMOJI: Record<string, string> = {
bed: '🛏️', sofa: '🛋️', table: '🪵', chair: '🪑', tv: '📺', pc: '🖥️',
fridge: '🧊', stove: '🍳', shower: '🚿', toilet: '🚽', sink: '🚰',
wardrobe: '🗄️', shelf: '📚', plant: '🪴', decor: '🕯️', light: '💡',
rug: '🧶', paint: '🖼️', elec: '🎮',
};