119 lines
4.4 KiB
JavaScript
119 lines
4.4 KiB
JavaScript
// End of the game: a retrospective front page. It covers both the normal end of
|
|
// the term and each way of losing power early.
|
|
|
|
import { t, tx, tp, tt, getLang, dateParts, fmt } from "../i18n/i18n.js";
|
|
import { buildReview, dateOfDay, indexCards } from "../engine/index.js";
|
|
import { lineChart } from "./charts.js";
|
|
import { $, esc } from "./desk.js";
|
|
|
|
const MIN_SPAN = 30;
|
|
|
|
// series id in state.history, label key, show the term average or the final value
|
|
const CHARTS = [
|
|
["opinion", "ind.opinion", "avg"],
|
|
["stability", "ind.stability", "avg"],
|
|
["economy", "ind.economy", "avg"],
|
|
["debtRatio", "econ.debt", "final"],
|
|
];
|
|
|
|
export function createEndgame(root, { newsName, endgame, cards, onAgain }) {
|
|
const cardsById = indexCards(cards);
|
|
|
|
root.addEventListener("click", (e) => {
|
|
if (e.target.closest("button[data-again]")) onAgain();
|
|
});
|
|
|
|
let shown = "";
|
|
|
|
function chart(state, [id, label, mode], review) {
|
|
const series = state.history.series[id] ?? [];
|
|
const value = mode === "avg" ? review.metrics[id] : series.at(-1);
|
|
const tag = mode === "avg" ? t("end.averages") : t("end.final");
|
|
|
|
// Zoom on the data (at least MIN_SPAN points tall) so trends are visible; stay within 0-100
|
|
// for indicators, and above 0 for debt.
|
|
const lo = Math.min(...series);
|
|
const hi = Math.max(...series);
|
|
const span = Math.max(hi - lo, MIN_SPAN);
|
|
let min = Math.max(0, (lo + hi) / 2 - span / 2);
|
|
let max = min + span;
|
|
if (id !== "debtRatio" && max > 100) {
|
|
max = 100;
|
|
min = 100 - span;
|
|
}
|
|
return `
|
|
<figure class="end__chart">
|
|
<figcaption><span>${t(label)}</span><b>${esc(fmt(value, 0))}</b><small>${tag}</small></figcaption>
|
|
${lineChart(series, { min, max })}
|
|
</figure>`;
|
|
}
|
|
|
|
function decision(d) {
|
|
const card = cardsById.get(d.id);
|
|
const date = dateParts(dateOfDay(root.dataset.startDate, d.day).iso);
|
|
return `
|
|
<li>
|
|
<span class="end__when">${esc(date.day)} ${esc(date.month)} ${esc(date.year)}</span>
|
|
<b>${esc(tx(card.title))}</b>
|
|
<span class="end__chose">${esc(t("end.chose", { label: tx(card[d.choice].label) }))}</span>
|
|
</li>`;
|
|
}
|
|
|
|
function update(state, bal) {
|
|
if (!state.over) {
|
|
if (shown) {
|
|
root.hidden = true;
|
|
root.innerHTML = "";
|
|
shown = "";
|
|
}
|
|
return;
|
|
}
|
|
const key = `${state.over.type}:${state.over.day}:${getLang()}`;
|
|
if (key === shown) return;
|
|
shown = key;
|
|
root.dataset.startDate = state.startDate;
|
|
|
|
const review = buildReview(state, bal, cards, endgame);
|
|
const banner = endgame.over[state.over.type];
|
|
const entry = endgame.titles.find((x) => x.id === review.titleId);
|
|
const date = dateParts(dateOfDay(state.startDate, state.over.day).iso);
|
|
const lost = state.over.type !== "term";
|
|
|
|
root.innerHTML = `
|
|
<article class="paper paper--news end ${lost ? "end--lost" : ""}">
|
|
<header class="news__mast">
|
|
<h2>${esc(tx(newsName))}</h2>
|
|
<div class="news__edition">${esc(t("news.edition", { date: date.full }))}</div>
|
|
</header>
|
|
<div class="end__banner">
|
|
<h1>${esc(tx(banner.h))}</h1>
|
|
<p>${esc(tx(banner.d))}</p>
|
|
</div>
|
|
<div class="end__grid">
|
|
<section class="end__verdict">
|
|
<h3>${t("end.verdict")}</h3>
|
|
<div class="end__grade" data-grade="${esc(review.grade)}">
|
|
<span class="end__letter">${esc(review.grade)}</span>
|
|
<span>${t("end.grade")}</span>
|
|
</div>
|
|
<p class="end__title">${entry ? esc(tt(entry.title, state.pronouns)) : ""}</p>
|
|
<p class="end__text">${entry ? esc(tp(tx(entry.verdict), state.pronouns)) : ""}</p>
|
|
<p class="end__score">${t("end.score", { n: Math.round(review.score) })}<br>${t("end.served", { days: state.over.day })}</p>
|
|
</section>
|
|
<section class="end__charts">${CHARTS.map((c) => chart(state, c, review)).join("")}</section>
|
|
<section class="end__decisions">
|
|
<h3>${t("end.decisions")}</h3>
|
|
${review.topDecisions.length
|
|
? `<ol>${review.topDecisions.map(decision).join("")}</ol>`
|
|
: `<p class="inbox__idle">${t("end.noDecisions")}</p>`}
|
|
</section>
|
|
</div>
|
|
<footer class="end__foot"><button class="brass brass--wide" data-again>${t("end.again")}</button></footer>
|
|
</article>`;
|
|
root.hidden = false;
|
|
$(root, "button[data-again]").focus();
|
|
}
|
|
|
|
return { update };
|
|
}
|