Mood bar: gradient, growing / falling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-28 15:36:38 +00:00
parent 9daa648017
commit faf186f102
3 changed files with 10 additions and 10 deletions

View file

@ -4585,7 +4585,6 @@ def happiness(world: dict, now: float) -> dict:
band = mood(world) band = mood(world)
return { return {
"score": round(city["happiness"], 3), "target": round(target, 3), "name": band["name"], "icon": band["icon"], "score": round(city["happiness"], 3), "target": round(target, 3), "name": band["name"], "icon": band["icon"],
"heading": next(b["name"] for b in MOODS if target < b["below"]),
"effects": {key: band[key] for key in ("growth", "militia", "work", "leave")}, "effects": {key: band[key] for key in ("growth", "militia", "work", "leave")},
"needs": {key: {**needs[key], "score": round(needs[key]["score"], 3), "weight": NEEDS[key]} for key in active}, "needs": {key: {**needs[key], "score": round(needs[key]["score"], 3), "weight": NEEDS[key]} for key in active},
"locked": [key for key in NEEDS if key not in active], "locked": [key for key in NEEDS if key not in active],

View file

@ -2054,11 +2054,12 @@ const NEED_NAMES = { food: ["🍲", "Food"], warmth: ["🔥", "Firewood"], cloth
const NEED_TIERS = { warmth: "Village", clothes: "Town" }; const NEED_TIERS = { warmth: "Village", clothes: "Town" };
const pct = (x) => `${Math.round(x * 100)}%`; const pct = (x) => `${Math.round(x * 100)}%`;
const moodTrend = (h) => Math.abs(h.target - h.score) < .005 ? "Steady" : h.target > h.score ? "↗ Growing" : "↘ Falling";
function moodStat() { function moodStat() {
const h = S.city.happiness; const h = S.city.happiness;
const trend = Math.abs(h.target - h.score) < .005 ? "Steady" : `${h.target > h.score ? "↗" : "↘"} ${h.heading}`; return `<div class="stat" title="${esc(`Mood ${pct(h.score)}`)}"><div class="k">Mood ${h.icon}</div>
return `<div class="stat" title="${esc(`Mood ${pct(h.score)}, heading to ${pct(h.target)}`)}"><div class="k">Mood ${h.icon}</div> <div class="v ${h.score < .4 ? "bad" : ""}">${h.name}</div><div class="note">${moodTrend(h)}</div></div>`;
<div class="v ${h.score < .4 ? "bad" : ""}">${h.name}</div><div class="note">${trend}</div></div>`;
} }
function moodPanel() { function moodPanel() {
@ -2070,8 +2071,8 @@ function moodPanel() {
const mods = h.mods.map((m) => `<span class="tag ${m.value < 0 ? "bad" : "good"}">${esc(m.name)} ${m.value > 0 ? "+" : "−"}${pct(Math.abs(m.value))}</span>`).join(""); const mods = h.mods.map((m) => `<span class="tag ${m.value < 0 ? "bad" : "good"}">${esc(m.name)} ${m.value > 0 ? "+" : "−"}${pct(Math.abs(m.value))}</span>`).join("");
const effects = [`🌱 Growth ${pct(e.growth)}`, `🛡️ Guards ${pct(e.militia)}`, `🔨 Labour ${pct(e.work)}`, `${COIN} Taxes ${pct(e.work)}`, const effects = [`🌱 Growth ${pct(e.growth)}`, `🛡️ Guards ${pct(e.militia)}`, `🔨 Labour ${pct(e.work)}`, `${COIN} Taxes ${pct(e.work)}`,
...(e.leave ? [`<span class="bad">🚶 ${pct(e.leave)} leave a day</span>`] : [])]; ...(e.leave ? [`<span class="bad">🚶 ${pct(e.leave)} leave a day</span>`] : [])];
return `<div class="panel mood"><h2>${h.icon} Townsfolk · ${h.name}</h2> return `<div class="panel mood"><h2>${h.icon} Townsfolk · ${h.name} · ${moodTrend(h)}</h2>
<div class="mood-bar" title="${esc(`Now ${pct(h.score)} · heading to ${pct(h.target)}`)}"><span style="width:${h.score * 100}%;--c:var(--${h.score < .4 ? "bad" : h.score < .6 ? "warn" : "good"})"></span><i style="left:${h.target * 100}%"></i></div> <div class="mood-bar" title="${esc(`Mood ${pct(h.score)}`)}"><span style="width:${100 - h.score * 100}%"></span></div>
<div class="mood-effects small">${effects.join(" · ")}</div> <div class="mood-effects small">${effects.join(" · ")}</div>
<div class="needs">${needs}</div> <div class="needs">${needs}</div>
${mods || locked ? `<div class="mood-tags">${mods}${locked}</div>` : ""}</div>`; ${mods || locked ? `<div class="mood-tags">${mods}${locked}</div>` : ""}</div>`;

View file

@ -879,10 +879,10 @@ button.swatch { width: 36px; height: 36px; }
.qp-then .x { background: none; border: 0; color: var(--muted); cursor: pointer; font-size: 11px; padding: 0 2px; } .qp-then .x { background: none; border: 0; color: var(--muted); cursor: pointer; font-size: 11px; padding: 0 2px; }
/* Townsfolk mood */ /* Townsfolk mood */
.mood-bar { position: relative; height: 12px; border-radius: 99px; margin: 4px 0 8px; /* The whole bar is the red → yellow → green scale; the part above the mood is covered by the track. */
background: linear-gradient(90deg, #e2645c33, #e3a04333 40%, #5cbf6a33); } .mood-bar { position: relative; height: 12px; border-radius: 99px; margin: 4px 0 8px; overflow: hidden;
.mood-bar > span { display: block; height: 100%; border-radius: 99px; background: var(--c); } background: linear-gradient(90deg, var(--bad), #e3c34a 50%, var(--good)); }
.mood-bar > i { position: absolute; top: -3px; bottom: -3px; width: 2px; margin-left: -1px; background: var(--text); border-radius: 2px; } .mood-bar > span { position: absolute; top: 0; right: 0; bottom: 0; background: var(--bg); }
.mood-effects { color: var(--muted); margin-bottom: 12px; } .mood-effects { color: var(--muted); margin-bottom: 12px; }
.needs { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); } .needs { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.need-head { display: flex; justify-content: space-between; margin-bottom: 8px; } .need-head { display: flex; justify-content: space-between; margin-bottom: 8px; }