Tooltips pass: weather, purse, badges, chips, sheet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 11:23:26 +01:00
parent cdb109e554
commit 400d8a2657
2 changed files with 24 additions and 18 deletions

View file

@ -1883,10 +1883,10 @@ SEASON_DAYS = 7
# `speed` multiplies action time (below 1 is faster), `yield` multiplies output, # `speed` multiplies action time (below 1 is faster), `yield` multiplies output,
# `food` multiplies what the townsfolk eat, `firewood` is logs per person per hour. # `food` multiplies what the townsfolk eat, `firewood` is logs per person per hour.
WEATHER = { WEATHER = {
"clear": {"name": "Clear skies", "icon": "🌤️", "text": "Nothing out of the ordinary"}, "clear": {"name": "Clear skies", "icon": "🌤️", "text": "No effect on work or the city"},
"rain": {"name": "Rain", "icon": "🌧️", "speed": {"farming": 0.9}, "text": "Farming 10% faster"}, "rain": {"name": "Rain", "icon": "🌧️", "speed": {"farming": 0.9}, "text": "Farming 10% faster"},
"heat": {"name": "Heat wave", "icon": "🥵", "speed": {"farming": 0.85}, "food": 1.15, "heat": {"name": "Heat wave", "icon": "🥵", "speed": {"farming": 0.85}, "food": 1.15,
"text": "Farming 15% faster, but food spoils faster"}, "text": "Farming 15% faster, but food spoils: the city eats 15% more"},
"storm": {"name": "Storm", "icon": "⛈️", "speed": {"fishing": 1.25}, "text": "Fishing 25% slower"}, "storm": {"name": "Storm", "icon": "⛈️", "speed": {"fishing": 1.25}, "text": "Fishing 25% slower"},
"fog": {"name": "Fog", "icon": "🌫️", "speed": {"reconnaissance": 1.3, "hunting": 1.1}, "fog": {"name": "Fog", "icon": "🌫️", "speed": {"reconnaissance": 1.3, "hunting": 1.1},
"text": "Scouting 30% and hunting 10% slower"}, "text": "Scouting 30% and hunting 10% slower"},
@ -2609,5 +2609,5 @@ def content() -> dict:
"city_uses": {item: city_uses(item) for item in ITEMS}, "city_uses": {item: city_uses(item) for item in ITEMS},
"prayers": [{**prayer, "text": describe_prayer(prayer)} for prayer in PRAYERS], "max_prayers": MAX_PRAYERS, "prayers": [{**prayer, "text": describe_prayer(prayer)} for prayer in PRAYERS], "max_prayers": MAX_PRAYERS,
"potion_charges": POTION_CHARGES, "problems": PROBLEMS, "fittings": list(FITTING_BY_ID.values()), "potion_charges": POTION_CHARGES, "problems": PROBLEMS, "fittings": list(FITTING_BY_ID.values()),
"equip_slots": EQUIP_SLOTS, "eat_below": EAT_BELOW, "regen_per_hour": REGEN_PER_HOUR, "equip_slots": EQUIP_SLOTS, "eat_below": EAT_BELOW, "class_speed": CLASS_SPEED, "regen_per_hour": REGEN_PER_HOUR,
} }

View file

@ -217,8 +217,8 @@ function setMenu(open) {
document.documentElement.classList.toggle("menu-open", open); document.documentElement.classList.toggle("menu-open", open);
} }
function nav(id, icon, label, meta = "", extra = "") { function nav(id, icon, label, meta = "", extra = "", tip = "") {
return `<button class="nav ${page === id ? "on" : ""}" data-nav="${id}"><span class="ic">${icon}</span><span class="label">${label}</span>${extra}<span class="meta">${meta}</span></button>`; return `<button class="nav ${page === id ? "on" : ""}" data-nav="${id}"${tip ? ` title="${esc(tip)}"` : ""}><span class="ic">${icon}</span><span class="label">${label}</span>${extra}<span class="meta">${meta}</span></button>`;
} }
function sidebar() { function sidebar() {
@ -230,16 +230,16 @@ function sidebar() {
<div class="me"> <div class="me">
<div class="who">${esc(me.name)}</div> <div class="who">${esc(me.name)}</div>
<div class="cls">${cls.icon} ${cls.name} · ${HEART} ${fmt(me.hp)}/${me.max_hp}</div> <div class="cls">${cls.icon} ${cls.name} · ${HEART} ${fmt(me.hp)}/${me.max_hp}</div>
<div class="purse"><span class="gold" title="Gold">${COIN} ${fmt(me.gold)}</span><span class="favour" title="Favour">${FAVOUR} ${fmt(me.favour)}</span></div> <div class="purse"><span class="gold" title="Gold: from fights and selling. Fund buildings with it or give it to the treasury">${COIN} ${fmt(me.gold)}</span><span class="favour" title="Favour: earned by giving to the city. Spend it on the Council page, or on extra bank slots">${FAVOUR} ${fmt(me.favour)}</span></div>
</div> </div>
<div class="navhead">City</div> <div class="navhead">City</div>
${nav("town", "🏘️", "Town square", `${S.city.population}`)} ${nav("town", "🏘️", "Town square", `${S.city.population}`, "", `Population ${S.city.population}`)}
${nav("council", "📜", "Council & threats", "", raid ? `<span class="alert"></span>` : "")} ${nav("council", "📜", "Council & threats", "", raid ? `<span class="alert"></span>` : "", raid ? "A threat is coming" : "")}
${nav("board", "💬", "Town board")} ${nav("board", "💬", "Town board")}
${nav("roster", "👥", "Guild roster", `${S.players.length}`)} ${nav("roster", "👥", "Guild roster", `${S.players.length}`, "", `${S.players.length} ${S.players.length === 1 ? "adventurer" : "adventurers"}`)}
<div class="navhead">You</div> <div class="navhead">You</div>
${nav("character", cls.icon, "Character", `${skillOf("combat").icon} ${me.levels.combat}`)} ${nav("character", cls.icon, "Character", `${skillOf("combat").icon} ${me.levels.combat}`, "", `Combat level ${me.levels.combat}`)}
${nav("bank", "🏦", "Bank", `${me.slots_used}/${me.slots}`)}`; ${nav("bank", "🏦", "Bank", `${me.slots_used}/${me.slots}`, "", `${me.slots_used} of ${me.slots} bank slots used`)}`;
let group = null; let group = null;
for (const skill of C.skills) { for (const skill of C.skills) {
if (skill.class && skill.class !== me.cls) continue; if (skill.class && skill.class !== me.cls) continue;
@ -283,7 +283,7 @@ function topbar() {
chips += `<span class="chip muted">Idle${S.me.stopped ? ` · ${esc(S.me.stopped)}` : ""}</span>`; chips += `<span class="chip muted">Idle${S.me.stopped ? ` · ${esc(S.me.stopped)}` : ""}</span>`;
} }
const w = S.happenings?.weather; const w = S.happenings?.weather;
if (w) chips += `<button class="chip" data-nav="town" title="${esc(w.name)}: ${esc(w.text)}">${w.icon}</button>`; if (w) chips += `<button class="chip" data-nav="town" title="${esc(weatherTip(w))}">${w.icon}</button>`;
if (S.upcoming) chips += `<button class="chip ${["raid", "dragon"].includes(S.upcoming.kind) ? "threat" : ""}" data-nav="council">${eventIcon(S.upcoming.kind)} ${eventWhen(S.upcoming, true)}</button>`; if (S.upcoming) chips += `<button class="chip ${["raid", "dragon"].includes(S.upcoming.kind) ? "threat" : ""}" data-nav="council">${eventIcon(S.upcoming.kind)} ${eventWhen(S.upcoming, true)}</button>`;
return `<button class="menu-btn" data-menu aria-label="Menu">☰</button><h1>${esc(title)}</h1>${chips}`; return `<button class="menu-btn" data-menu aria-label="Menu">☰</button><h1>${esc(title)}</h1>${chips}`;
} }
@ -317,7 +317,7 @@ function itemChip(id, qty, have) {
// Progress towards a building cost: "have / need". // Progress towards a building cost: "have / need".
function costChip(icon, name, need, have) { function costChip(icon, name, need, have) {
return `<span class="item-chip ${have < need ? "short" : ""}" title="${esc(name)}">${icon} ${fmt(have)} <span class="have">/ ${fmt(need)}</span></span>`; return `<span class="item-chip ${have < need ? "short" : ""}" title="${esc(`${name}: ${have < need ? `${fmt(need - have)} more needed` : "done"}`)}">${icon} ${fmt(have)} <span class="have">/ ${fmt(need)}</span></span>`;
} }
// --- skill pages ---------------------------------------------------------- // --- skill pages ----------------------------------------------------------
@ -532,7 +532,7 @@ function bank() {
return parts; return parts;
}); });
let slots = stacks.map(([id, q]) => ` let slots = stacks.map(([id, q]) => `
<button class="slot ${selected === id ? "on" : ""}" data-select="${id}" title="${esc(item(id).name)}: ${fmt(me.bank[id])} in all"> <button class="slot ${selected === id ? "on" : ""}" data-select="${id}" title="${esc(item(id).name)}: ${fmt(me.bank[id])}${me.bank[id] > me.stack ? ` across ${Math.ceil(me.bank[id] / me.stack)} slots` : ""}">
<span class="qty ${q >= me.stack ? "full" : ""}">${fmt(q)}</span>${item(id).icon}<span class="nm">${esc(item(id).name)}</span> <span class="qty ${q >= me.stack ? "full" : ""}">${fmt(q)}</span>${item(id).icon}<span class="nm">${esc(item(id).name)}</span>
</button>`).join(""); </button>`).join("");
for (let i = stacks.length; i < me.slots; i++) slots += `<div class="slot empty"></div>`; for (let i = stacks.length; i < me.slots; i++) slots += `<div class="slot empty"></div>`;
@ -665,9 +665,15 @@ function siteBanner() {
</div></div>`; </div></div>`;
} }
// "Rain: farming 10% faster. Clear skies in 3h 20m."
function weatherTip(w) {
const next = w.next === w.kind ? `Staying ${w.name.toLowerCase()}` : w.next_name;
return `${w.name}: ${w.text.charAt(0).toLowerCase()}${w.text.slice(1)}. ${next} in ${dur(w.until - serverNow())}.`;
}
function weatherCard() { function weatherCard() {
const h = S.happenings, w = h.weather; const h = S.happenings, w = h.weather;
return `<div class="stat" title="${esc(w.text)}"><div class="k">${h.season.icon} ${h.season.name}, day ${h.season.day}</div> return `<div class="stat" title="${esc(`${weatherTip(w)} Weather changes every few hours; each season lasts a week.`)}"><div class="k">${h.season.icon} ${h.season.name}, day ${h.season.day}</div>
<div class="v">${w.icon} <small>${esc(w.name)}</small></div> <div class="v">${w.icon} <small>${esc(w.name)}</small></div>
<div class="note">${w.next === w.kind ? `Staying ${esc(w.name.toLowerCase())}` : `${w.next_icon} ${esc(w.next_name)}`} in ${countdown(w.until)}</div></div>`; <div class="note">${w.next === w.kind ? `Staying ${esc(w.name.toLowerCase())}` : `${w.next_icon} ${esc(w.next_name)}`} in ${countdown(w.until)}</div></div>`;
} }
@ -883,7 +889,7 @@ function board() {
function roster() { function roster() {
return `<div class="grid wide">${S.players.map((p) => { return `<div class="grid wide">${S.players.map((p) => {
const cls = C.classes[p.cls]; const cls = C.classes[p.cls];
const levels = C.skills.filter((s) => !s.class || s.class === p.cls).map((s) => `<div class="lvl ${p.levels[s.id] <= 1 ? "low" : ""}" title="${s.name}"><span class="ico">${s.icon}</span><span class="nm">${s.name}</span><b>${p.levels[s.id]}</b></div>`).join(""); const levels = C.skills.filter((s) => !s.class || s.class === p.cls).map((s) => `<div class="lvl ${p.levels[s.id] <= 1 ? "low" : ""}" title="${s.name} level ${p.levels[s.id]}"><span class="ico">${s.icon}</span><span class="nm">${s.name}</span><b>${p.levels[s.id]}</b></div>`).join("");
return `<div class="panel person"><div class="top"><span class="ico">${cls.icon}</span><div> return `<div class="panel person"><div class="top"><span class="ico">${cls.icon}</span><div>
<b>${esc(p.name)}</b> <span class="muted small">${cls.name} · total level ${p.total}</span> <b>${esc(p.name)}</b> <span class="muted small">${cls.name} · total level ${p.total}</span>
<div class="small">${p.doing ? `${skillOf(p.skill).icon} ${esc(p.doing)}` : `<span class="muted">Idle</span>`}${p.garrison ? ` · <span class="tag warn">On guard</span>` : ""}</div> <div class="small">${p.doing ? `${skillOf(p.skill).icon} ${esc(p.doing)}` : `<span class="muted">Idle</span>`}${p.garrison ? ` · <span class="tag warn">On guard</span>` : ""}</div>
@ -925,8 +931,8 @@ function characterSheet(uid) {
const floor = C.xp_table[level], ceil = C.xp_table[level + 1]; const floor = C.xp_table[level], ceil = C.xp_table[level + 1];
const pct = ceil ? (xp - floor) / (ceil - floor) * 100 : 100; const pct = ceil ? (xp - floor) / (ceil - floor) * 100 : 100;
const tool = p.equip[`tool_${s.id}`]; const tool = p.equip[`tool_${s.id}`];
const marks = (cls.fast.includes(s.id) ? `<span title="${cls.name} bonus">${cls.icon}</span>` : "") const marks = (cls.fast.includes(s.id) ? `<span title="${cls.name}: ${Math.round((1 - C.class_speed) * 100)}% faster">${cls.icon}</span>` : "")
+ (tool ? `<span title="${esc(item(tool).name)}">${item(tool).icon}</span>` : ""); + (tool ? `<span title="${esc(`${item(tool).name}: ${Math.round(item(tool).speed * 100)}% faster`)}">${item(tool).icon}</span>` : "");
return `<${mine ? `button data-nav="skill:${s.id}"` : "div"} class="sheet-skill"> return `<${mine ? `button data-nav="skill:${s.id}"` : "div"} class="sheet-skill">
<span class="ico">${s.icon}</span> <span class="ico">${s.icon}</span>
<span class="grow"><span class="row1"><b>${s.name}</b><span class="marks">${marks}</span><span class="lv">${level}</span></span> <span class="grow"><span class="row1"><b>${s.name}</b><span class="marks">${marks}</span><span class="lv">${level}</span></span>