fix(ui): F8-08 unified number format — mono, fixed 3-decimal, no separators
Engine emits Floats at Fixed3 quantisation; UI now renders them as 3-decimal fixed-point strings without thousand separators, monospaced via var(--font-mono) on .numeric cells, and right-aligned in tables so columns line up on the decimal point. Integer counts render with 0 decimals and no separators; science fractions render as 1-decimal percent (matches the engine's third decimal of precision). Bug fixes from #51 (umbrella #43): - Player Status drive/weapons/shields/cargo: were tech LEVELS rendered through formatPercent (x100) — now use formatFloat (raw level). - Races table: same bug, same fix. Style/UX cleanups: - Inspector field labels lose "stockpile" word ($ / M suffix carries it). - Coordinates drop the parentheses (just "x, y"). - Inspector + report tables unify font sizes with calculator-tab (values 0.85rem mono, labels 0.8rem). Files: - new util: ui/frontend/src/lib/util/number-format.ts - report/format.ts becomes a thin re-export to keep section imports compact - inspector planet / ship-group / actions: drop inline formatNumber, mark numeric <dd> with class="numeric" - table-races (+ bug fix), table-sciences, table-ship-classes, designer-science: drop inline formatters, switch to util, add class="numeric" on numeric <th>/<td> - 17 report section files: class="numeric" on numeric th/td + scoped CSS rule for mono+right-align - i18n en/ru: drop "stockpile" word, drop "%" from tech-level column headers in races + player_status (the "%" was the misleading bit from the bug) - tests/inspector-planet + tests/table-races: update assertions to match the new format Verification: pnpm test (814 passed), pnpm check (0 errors/warnings), pnpm build clean. Refs: #51 (#43 umbrella). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ fractions is a Phase 21 decision documented in
|
||||
validateScience,
|
||||
type ScienceInvalidReason,
|
||||
} from "$lib/util/science-validation";
|
||||
import { formatPercent } from "$lib/util/number-format";
|
||||
|
||||
const rendered = getContext<RenderedReportSource | undefined>(
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
@@ -106,12 +107,7 @@ fractions is a Phase 21 decision documented in
|
||||
const canSave = $derived(validation.ok && draft !== undefined);
|
||||
|
||||
const sumPercent = $derived(drive + weapons + shields + cargo);
|
||||
const sumDisplay = $derived(
|
||||
sumPercent.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 1,
|
||||
}),
|
||||
);
|
||||
const sumDisplay = $derived(sumPercent.toFixed(1));
|
||||
|
||||
$effect(() => {
|
||||
if (!isViewMode) {
|
||||
@@ -119,13 +115,6 @@ fractions is a Phase 21 decision documented in
|
||||
}
|
||||
});
|
||||
|
||||
function formatPercent(fraction: number): string {
|
||||
return (fraction * 100).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
}
|
||||
|
||||
function backToTable(): void {
|
||||
activeView.select("table", { tableEntity: "sciences" });
|
||||
}
|
||||
|
||||
@@ -1,61 +1,44 @@
|
||||
// Shared number / planet formatters for the Phase 23 Report View
|
||||
// sections. Inlined in 10+ components, so factoring keeps each
|
||||
// section component focused on its data shape. The formatters
|
||||
// match the conventions of the per-entity tables (tabular numerals,
|
||||
// one-decimal percent without a `%` suffix — the header carries the
|
||||
// unit) so the report's grids read the same way as the
|
||||
// table-races / table-sciences views.
|
||||
// Number formatters and lookup helpers reused across the report-section
|
||||
// components. The numeric formatters delegate to the project-wide
|
||||
// `lib/util/number-format` so inspector, report tables, and the
|
||||
// calculator panel all render numbers identically.
|
||||
|
||||
import type { ReportPlanet } from "../../../api/game-state";
|
||||
import {
|
||||
formatFloat as formatFloatBase,
|
||||
formatInt,
|
||||
formatPercent as formatPercentBase,
|
||||
} from "$lib/util/number-format";
|
||||
|
||||
/**
|
||||
* formatPercent renders a `[0, 1]` fraction as a one-decimal
|
||||
* percent (without a `%` suffix — the column header carries the
|
||||
* unit). Matches the convention used by `table-races.svelte` and
|
||||
* `table-sciences.svelte`.
|
||||
* formatPercent renders a `[0, 1]` fraction as a one-decimal percent
|
||||
* (without a `%` suffix — the column header carries the unit).
|
||||
* Re-exported from the shared util for backwards-compatible imports
|
||||
* across the report sections.
|
||||
*/
|
||||
export function formatPercent(fraction: number): string {
|
||||
return (fraction * 100).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
}
|
||||
export const formatPercent = formatPercentBase;
|
||||
|
||||
/**
|
||||
* formatCount renders an integer-ish value (population, industry,
|
||||
* planet count, …) without fractional digits and with locale-aware
|
||||
* thousand separators.
|
||||
* planet count, …) with zero fractional digits and no thousand
|
||||
* separators. Alias of the shared `formatInt`.
|
||||
*/
|
||||
export function formatCount(value: number): string {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
}
|
||||
export const formatCount = formatInt;
|
||||
|
||||
/**
|
||||
* formatFloat renders a floating-point value with up to two
|
||||
* fractional digits. Used for stockpiles, distances, cost, mass —
|
||||
* everything the engine emits as a `Float` that is not a fraction.
|
||||
* formatFloat renders an engine `Float` (Fixed3-quantised) with three
|
||||
* fractional digits and no thousand separators. Used for stockpiles,
|
||||
* distances, cost, mass, tech levels — every report payload that is
|
||||
* neither an integer count nor a `[0, 1]` fraction.
|
||||
*/
|
||||
export function formatFloat(value: number): string {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
export const formatFloat = formatFloatBase;
|
||||
|
||||
/**
|
||||
* formatVotes renders a vote weight with up to two decimal digits —
|
||||
* mirrors the races table's column convention so the cumulative
|
||||
* vote totals line up across views.
|
||||
* formatVotes renders a vote weight. Votes travel as the same `Float`
|
||||
* shape as every other float field, so this is a semantic alias of
|
||||
* `formatFloat` kept for readability at the call site.
|
||||
*/
|
||||
export function formatVotes(value: number): string {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
export const formatVotes = formatFloatBase;
|
||||
|
||||
/**
|
||||
* planetLabel renders a planet reference as `#<number> (<name>)` if
|
||||
|
||||
@@ -42,11 +42,11 @@ class when the group lands and a battle roster forms.
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.approaching_groups.column.from")}</th>
|
||||
<th>{i18n.t("game.report.section.approaching_groups.column.to")}</th>
|
||||
<th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.approaching_groups.column.distance")}
|
||||
</th>
|
||||
<th>{i18n.t("game.report.section.approaching_groups.column.speed")}</th>
|
||||
<th>{i18n.t("game.report.section.approaching_groups.column.mass")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.approaching_groups.column.speed")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.approaching_groups.column.mass")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -54,9 +54,9 @@ class when the group lands and a battle roster forms.
|
||||
<tr data-testid="approaching-groups-row">
|
||||
<td>{planetLabel(r.origin, planets)}</td>
|
||||
<td>{planetLabel(r.destination, planets)}</td>
|
||||
<td>{formatFloat(r.distance)}</td>
|
||||
<td>{formatFloat(r.speed)}</td>
|
||||
<td>{formatFloat(r.mass)}</td>
|
||||
<td class="numeric">{formatFloat(r.distance)}</td>
|
||||
<td class="numeric">{formatFloat(r.speed)}</td>
|
||||
<td class="numeric">{formatFloat(r.mass)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -79,7 +79,7 @@ class when the group lands and a battle roster forms.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -93,6 +93,11 @@ class when the group lands and a battle roster forms.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -42,16 +42,16 @@ Decoder sorts by `planetNumber` already.
|
||||
<th>{i18n.t("game.report.section.bombings.column.owner")}</th>
|
||||
<th>{i18n.t("game.report.section.bombings.column.attacker")}</th>
|
||||
<th>{i18n.t("game.report.section.bombings.column.production")}</th>
|
||||
<th>{i18n.t("game.report.section.bombings.column.industry")}</th>
|
||||
<th>{i18n.t("game.report.section.bombings.column.population")}</th>
|
||||
<th>{i18n.t("game.report.section.bombings.column.colonists")}</th>
|
||||
<th>
|
||||
<th class="numeric">{i18n.t("game.report.section.bombings.column.industry")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.bombings.column.population")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.bombings.column.colonists")}</th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.bombings.column.industry_stockpile")}
|
||||
</th>
|
||||
<th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.bombings.column.materials_stockpile")}
|
||||
</th>
|
||||
<th>{i18n.t("game.report.section.bombings.column.attack_power")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.bombings.column.attack_power")}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -67,12 +67,12 @@ Decoder sorts by `planetNumber` already.
|
||||
<td>{b.owner}</td>
|
||||
<td>{b.attacker}</td>
|
||||
<td>{b.production}</td>
|
||||
<td>{formatFloat(b.industry)}</td>
|
||||
<td>{formatFloat(b.population)}</td>
|
||||
<td>{formatFloat(b.colonists)}</td>
|
||||
<td>{formatFloat(b.industryStockpile)}</td>
|
||||
<td>{formatFloat(b.materialsStockpile)}</td>
|
||||
<td>{formatCount(b.attackPower)}</td>
|
||||
<td class="numeric">{formatFloat(b.industry)}</td>
|
||||
<td class="numeric">{formatFloat(b.population)}</td>
|
||||
<td class="numeric">{formatFloat(b.colonists)}</td>
|
||||
<td class="numeric">{formatFloat(b.industryStockpile)}</td>
|
||||
<td class="numeric">{formatFloat(b.materialsStockpile)}</td>
|
||||
<td class="numeric">{formatCount(b.attackPower)}</td>
|
||||
<td>
|
||||
{#if b.wiped}
|
||||
<span
|
||||
@@ -105,7 +105,7 @@ Decoder sorts by `planetNumber` already.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -119,6 +119,11 @@ Decoder sorts by `planetNumber` already.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ has many routes.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
|
||||
@@ -42,20 +42,20 @@ as the local planets table plus an `owner` column.
|
||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.foreign_planets.column.owner")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.population")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
||||
<th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.population")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
||||
</th>
|
||||
<th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
||||
</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.production")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -64,16 +64,16 @@ as the local planets table plus an `owner` column.
|
||||
<td>{p.number}</td>
|
||||
<td>{p.name}</td>
|
||||
<td>{p.owner ?? ""}</td>
|
||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
<td>{formatFloat(p.size ?? 0)}</td>
|
||||
<td>{formatFloat(p.resources ?? 0)}</td>
|
||||
<td>{formatFloat(p.population ?? 0)}</td>
|
||||
<td>{formatFloat(p.industry ?? 0)}</td>
|
||||
<td>{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||
<td>{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||
<td>{formatFloat(p.colonists ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
<td class="numeric">{formatFloat(p.size ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.resources ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.population ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.industry ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.colonists ?? 0)}</td>
|
||||
<td>{p.production ?? "—"}</td>
|
||||
<td>{formatFloat(p.freeIndustry ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.freeIndustry ?? 0)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -96,7 +96,7 @@ as the local planets table plus an `owner` column.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -110,6 +110,11 @@ as the local planets table plus an `owner` column.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@ unit even when the section spans many races.
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -81,10 +81,10 @@ unit even when the section spans many races.
|
||||
data-name={r.name}
|
||||
>
|
||||
<td>{r.name}</td>
|
||||
<td>{formatPercent(r.drive)}</td>
|
||||
<td>{formatPercent(r.weapons)}</td>
|
||||
<td>{formatPercent(r.shields)}</td>
|
||||
<td>{formatPercent(r.cargo)}</td>
|
||||
<td class="numeric">{formatPercent(r.drive)}</td>
|
||||
<td class="numeric">{formatPercent(r.weapons)}</td>
|
||||
<td class="numeric">{formatPercent(r.shields)}</td>
|
||||
<td class="numeric">{formatPercent(r.cargo)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -115,7 +115,7 @@ unit even when the section spans many races.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -129,6 +129,11 @@ unit even when the section spans many races.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -65,12 +65,12 @@ incoming groups.
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.armament")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
||||
<th>{i18n.t("game.report.section.foreign_ship_classes.column.mass")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.foreign_ship_classes.column.mass")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -81,12 +81,12 @@ incoming groups.
|
||||
data-name={r.name}
|
||||
>
|
||||
<td>{r.name}</td>
|
||||
<td>{formatFloat(r.drive)}</td>
|
||||
<td class="numeric">{formatFloat(r.drive)}</td>
|
||||
<td>{r.armament}</td>
|
||||
<td>{formatFloat(r.weapons)}</td>
|
||||
<td>{formatFloat(r.shields)}</td>
|
||||
<td>{formatFloat(r.cargo)}</td>
|
||||
<td>{formatFloat(r.mass)}</td>
|
||||
<td class="numeric">{formatFloat(r.weapons)}</td>
|
||||
<td class="numeric">{formatFloat(r.shields)}</td>
|
||||
<td class="numeric">{formatFloat(r.cargo)}</td>
|
||||
<td class="numeric">{formatFloat(r.mass)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -117,7 +117,7 @@ incoming groups.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -131,6 +131,11 @@ incoming groups.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ to groups the player doesn't own.
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.destination")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.origin")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.range")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -64,8 +64,8 @@ to groups the player doesn't own.
|
||||
{g.origin === null ? "—" : planetLabel(g.origin, planets)}
|
||||
</td>
|
||||
<td>{g.range === null ? "—" : formatFloat(g.range)}</td>
|
||||
<td>{formatFloat(g.speed)}</td>
|
||||
<td>{formatFloat(g.mass)}</td>
|
||||
<td class="numeric">{formatFloat(g.speed)}</td>
|
||||
<td class="numeric">{formatFloat(g.mass)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -88,7 +88,7 @@ to groups the player doesn't own.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -102,6 +102,11 @@ to groups the player doesn't own.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -31,13 +31,13 @@ section is never empty as long as the report has loaded.
|
||||
{:else}
|
||||
<dl class="kv">
|
||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.turn")}</dt>
|
||||
<dd data-testid="galaxy-summary-field-turn">{report.turn}</dd>
|
||||
<dd class="numeric" data-testid="galaxy-summary-field-turn">{report.turn}</dd>
|
||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.size")}</dt>
|
||||
<dd data-testid="galaxy-summary-field-size">
|
||||
<dd class="numeric" data-testid="galaxy-summary-field-size">
|
||||
{report.mapWidth} × {report.mapHeight}
|
||||
</dd>
|
||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.planets")}</dt>
|
||||
<dd data-testid="galaxy-summary-field-planets">{report.planetCount}</dd>
|
||||
<dd class="numeric" data-testid="galaxy-summary-field-planets">{report.planetCount}</dd>
|
||||
<dt>{i18n.t("game.report.section.galaxy_summary.field.race")}</dt>
|
||||
<dd data-testid="galaxy-summary-field-race">{report.race}</dd>
|
||||
</dl>
|
||||
@@ -60,7 +60,7 @@ section is never empty as long as the report has loaded.
|
||||
grid-template-columns: max-content 1fr;
|
||||
gap: 0.3rem 1rem;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.kv dt {
|
||||
color: var(--color-text-muted);
|
||||
@@ -73,4 +73,7 @@ section is never empty as long as the report has loaded.
|
||||
color: var(--color-text);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.kv dd.numeric {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -44,7 +44,7 @@ in orbit has neither); empty cells in those columns are normal.
|
||||
<th>{i18n.t("game.report.section.my_fleets.column.destination")}</th>
|
||||
<th>{i18n.t("game.report.section.my_fleets.column.origin")}</th>
|
||||
<th>{i18n.t("game.report.section.my_fleets.column.range")}</th>
|
||||
<th>{i18n.t("game.report.section.my_fleets.column.speed")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_fleets.column.speed")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -58,7 +58,7 @@ in orbit has neither); empty cells in those columns are normal.
|
||||
{f.origin === null ? "—" : planetLabel(f.origin, planets)}
|
||||
</td>
|
||||
<td>{f.range === null ? "—" : formatFloat(f.range)}</td>
|
||||
<td>{formatFloat(f.speed)}</td>
|
||||
<td class="numeric">{formatFloat(f.speed)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -81,7 +81,7 @@ in orbit has neither); empty cells in those columns are normal.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -95,6 +95,11 @@ in orbit has neither); empty cells in those columns are normal.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -41,20 +41,20 @@ column set (matches `ReportPlanet` shape).
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.population")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
||||
<th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.population")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.industry")}</th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
||||
</th>
|
||||
<th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
||||
</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.colonists")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.production")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.free_industry")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -62,16 +62,16 @@ column set (matches `ReportPlanet` shape).
|
||||
<tr data-testid="my-planets-row" data-number={p.number}>
|
||||
<td>{p.number}</td>
|
||||
<td>{p.name}</td>
|
||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
<td>{formatFloat(p.size ?? 0)}</td>
|
||||
<td>{formatFloat(p.resources ?? 0)}</td>
|
||||
<td>{formatFloat(p.population ?? 0)}</td>
|
||||
<td>{formatFloat(p.industry ?? 0)}</td>
|
||||
<td>{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||
<td>{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||
<td>{formatFloat(p.colonists ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
<td class="numeric">{formatFloat(p.size ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.resources ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.population ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.industry ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.colonists ?? 0)}</td>
|
||||
<td>{p.production ?? "—"}</td>
|
||||
<td>{formatFloat(p.freeIndustry ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.freeIndustry ?? 0)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -94,7 +94,7 @@ column set (matches `ReportPlanet` shape).
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -108,6 +108,11 @@ column set (matches `ReportPlanet` shape).
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -39,20 +39,20 @@ table).
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
||||
<th>{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.drive")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.weapons")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.shields")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_sciences.column.cargo")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows as r (r.name)}
|
||||
<tr data-testid="my-sciences-row" data-name={r.name}>
|
||||
<td>{r.name}</td>
|
||||
<td>{formatPercent(r.drive)}</td>
|
||||
<td>{formatPercent(r.weapons)}</td>
|
||||
<td>{formatPercent(r.shields)}</td>
|
||||
<td>{formatPercent(r.cargo)}</td>
|
||||
<td class="numeric">{formatPercent(r.drive)}</td>
|
||||
<td class="numeric">{formatPercent(r.weapons)}</td>
|
||||
<td class="numeric">{formatPercent(r.shields)}</td>
|
||||
<td class="numeric">{formatPercent(r.cargo)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -75,7 +75,7 @@ table).
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -89,6 +89,11 @@ table).
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -40,22 +40,22 @@ drafts immediately, matching the ship-class designer's behaviour.
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.drive")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.armament")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.weapons")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.shields")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_classes.column.cargo")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows as r (r.name)}
|
||||
<tr data-testid="my-ship-classes-row" data-name={r.name}>
|
||||
<td>{r.name}</td>
|
||||
<td>{formatFloat(r.drive)}</td>
|
||||
<td class="numeric">{formatFloat(r.drive)}</td>
|
||||
<td>{r.armament}</td>
|
||||
<td>{formatFloat(r.weapons)}</td>
|
||||
<td>{formatFloat(r.shields)}</td>
|
||||
<td>{formatFloat(r.cargo)}</td>
|
||||
<td class="numeric">{formatFloat(r.weapons)}</td>
|
||||
<td class="numeric">{formatFloat(r.shields)}</td>
|
||||
<td class="numeric">{formatFloat(r.cargo)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -78,7 +78,7 @@ drafts immediately, matching the ship-class designer's behaviour.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -92,6 +92,11 @@ drafts immediately, matching the ship-class designer's behaviour.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ shown together with `load` when carrying.
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.destination")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.origin")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.range")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.speed")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_ship_groups.column.mass")}</th>
|
||||
<th>{i18n.t("game.report.section.my_ship_groups.column.fleet")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -77,8 +77,8 @@ shown together with `load` when carrying.
|
||||
{g.origin === null ? "—" : planetLabel(g.origin, planets)}
|
||||
</td>
|
||||
<td>{g.range === null ? "—" : formatFloat(g.range)}</td>
|
||||
<td>{formatFloat(g.speed)}</td>
|
||||
<td>{formatFloat(g.mass)}</td>
|
||||
<td class="numeric">{formatFloat(g.speed)}</td>
|
||||
<td class="numeric">{formatFloat(g.mass)}</td>
|
||||
<td>{g.fleet ?? "—"}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -102,7 +102,7 @@ shown together with `load` when carrying.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -116,6 +116,11 @@ shown together with `load` when carrying.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ highlight so the user can locate themselves quickly.
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
type RenderedReportSource,
|
||||
} from "$lib/rendered-report.svelte";
|
||||
import { formatCount, formatPercent, formatVotes } from "./format";
|
||||
import { formatCount, formatFloat, formatVotes } from "./format";
|
||||
|
||||
const rendered = getContext<RenderedReportSource | undefined>(
|
||||
RENDERED_REPORT_CONTEXT_KEY,
|
||||
@@ -37,14 +37,14 @@ highlight so the user can locate themselves quickly.
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.player_status.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.drive")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.weapons")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.shields")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.cargo")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.population")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.industry")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.planets")}</th>
|
||||
<th>{i18n.t("game.report.section.player_status.column.votes")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.drive")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.weapons")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.shields")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.cargo")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.population")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.industry")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.planets")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.player_status.column.votes")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -73,14 +73,14 @@ highlight so the user can locate themselves quickly.
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>{formatPercent(p.drive)}</td>
|
||||
<td>{formatPercent(p.weapons)}</td>
|
||||
<td>{formatPercent(p.shields)}</td>
|
||||
<td>{formatPercent(p.cargo)}</td>
|
||||
<td>{formatCount(p.population)}</td>
|
||||
<td>{formatCount(p.industry)}</td>
|
||||
<td>{formatCount(p.planets)}</td>
|
||||
<td>{formatVotes(p.votesReceived)}</td>
|
||||
<td class="numeric">{formatFloat(p.drive)}</td>
|
||||
<td class="numeric">{formatFloat(p.weapons)}</td>
|
||||
<td class="numeric">{formatFloat(p.shields)}</td>
|
||||
<td class="numeric">{formatFloat(p.cargo)}</td>
|
||||
<td class="numeric">{formatCount(p.population)}</td>
|
||||
<td class="numeric">{formatCount(p.industry)}</td>
|
||||
<td class="numeric">{formatCount(p.planets)}</td>
|
||||
<td class="numeric">{formatVotes(p.votesReceived)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -103,7 +103,7 @@ highlight so the user can locate themselves quickly.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -117,6 +117,11 @@ highlight so the user can locate themselves quickly.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ reads `#17 (Castle)` rather than just `#17`.
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.ships_in_production.column.planet")}</th>
|
||||
<th>{i18n.t("game.report.section.ships_in_production.column.class")}</th>
|
||||
<th>{i18n.t("game.report.section.ships_in_production.column.cost")}</th>
|
||||
<th>
|
||||
<th class="numeric">{i18n.t("game.report.section.ships_in_production.column.cost")}</th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.ships_in_production.column.prod_used")}
|
||||
</th>
|
||||
<th>{i18n.t("game.report.section.ships_in_production.column.percent")}</th>
|
||||
<th>{i18n.t("game.report.section.ships_in_production.column.free")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.ships_in_production.column.free")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -58,10 +58,10 @@ reads `#17 (Castle)` rather than just `#17`.
|
||||
>
|
||||
<td>{planetLabel(r.planetNumber, planets)}</td>
|
||||
<td>{r.class}</td>
|
||||
<td>{formatFloat(r.cost)}</td>
|
||||
<td>{formatFloat(r.prodUsed)}</td>
|
||||
<td class="numeric">{formatFloat(r.cost)}</td>
|
||||
<td class="numeric">{formatFloat(r.prodUsed)}</td>
|
||||
<td>{(r.percent * 100).toFixed(1)}</td>
|
||||
<td>{formatFloat(r.freeIndustry)}</td>
|
||||
<td class="numeric">{formatFloat(r.freeIndustry)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -84,7 +84,7 @@ reads `#17 (Castle)` rather than just `#17`.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -98,6 +98,11 @@ reads `#17 (Castle)` rather than just `#17`.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -37,15 +37,15 @@ radar that doesn't even resolve to a planet.
|
||||
<table class="grid" data-testid="unidentified-groups-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.unidentified_groups.column.x")}</th>
|
||||
<th>{i18n.t("game.report.section.unidentified_groups.column.y")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.unidentified_groups.column.x")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.unidentified_groups.column.y")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows as g, i (i)}
|
||||
<tr data-testid="unidentified-groups-row">
|
||||
<td>{formatFloat(g.x)}</td>
|
||||
<td>{formatFloat(g.y)}</td>
|
||||
<td class="numeric">{formatFloat(g.x)}</td>
|
||||
<td class="numeric">{formatFloat(g.y)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -68,7 +68,7 @@ radar that doesn't even resolve to a planet.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -82,6 +82,11 @@ radar that doesn't even resolve to a planet.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -42,13 +42,13 @@ are intentionally omitted.
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.name")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||
<th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.size")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.resources")}</th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.my_planets.column.industry_stockpile")}
|
||||
</th>
|
||||
<th>
|
||||
<th class="numeric">
|
||||
{i18n.t("game.report.section.my_planets.column.materials_stockpile")}
|
||||
</th>
|
||||
</tr>
|
||||
@@ -58,11 +58,11 @@ are intentionally omitted.
|
||||
<tr data-testid="uninhabited-planets-row" data-number={p.number}>
|
||||
<td>{p.number}</td>
|
||||
<td>{p.name}</td>
|
||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
<td>{formatFloat(p.size ?? 0)}</td>
|
||||
<td>{formatFloat(p.resources ?? 0)}</td>
|
||||
<td>{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||
<td>{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
<td class="numeric">{formatFloat(p.size ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.resources ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.industryStockpile ?? 0)}</td>
|
||||
<td class="numeric">{formatFloat(p.materialsStockpile ?? 0)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -85,7 +85,7 @@ are intentionally omitted.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -99,6 +99,11 @@ are intentionally omitted.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ else is known.
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.number")}</th>
|
||||
<th>{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.my_planets.column.coordinates")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each rows as p (p.number)}
|
||||
<tr data-testid="unknown-planets-row" data-number={p.number}>
|
||||
<td>{p.number}</td>
|
||||
<td>{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
<td class="numeric">{formatFloat(p.x)}, {formatFloat(p.y)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -70,7 +70,7 @@ else is known.
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
@@ -84,6 +84,11 @@ else is known.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ explanatory text on the races table.
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{i18n.t("game.report.section.votes.column.race")}</th>
|
||||
<th>{i18n.t("game.report.section.votes.column.votes")}</th>
|
||||
<th class="numeric">{i18n.t("game.report.section.votes.column.votes")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each races as r (r.name)}
|
||||
<tr data-testid="votes-received-row" data-race={r.name}>
|
||||
<td>{r.name}</td>
|
||||
<td>{formatVotes(r.votesReceived)}</td>
|
||||
<td class="numeric">{formatVotes(r.votesReceived)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
@@ -113,7 +113,7 @@ explanatory text on the races table.
|
||||
.grid {
|
||||
border-collapse: collapse;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th,
|
||||
.grid td {
|
||||
|
||||
@@ -35,6 +35,10 @@ data fetching is performed here — the layout is responsible.
|
||||
} from "../../sync/order-draft.svelte";
|
||||
import type { Relation } from "../../sync/order-types";
|
||||
import ViewState from "$lib/ui/view-state.svelte";
|
||||
import {
|
||||
formatFloat,
|
||||
formatInt,
|
||||
} from "$lib/util/number-format";
|
||||
|
||||
type SortColumn =
|
||||
| "name"
|
||||
@@ -122,31 +126,6 @@ data fetching is performed here — the layout is responsible.
|
||||
return sortDirection === "asc" ? "ascending" : "descending";
|
||||
}
|
||||
|
||||
// Render a fraction in `[0, 1]` as a one-decimal percent
|
||||
// (`0.225` → `"22.5"`). The conversion is value-only — no `%`
|
||||
// suffix — so the column header carries the unit. Matches the
|
||||
// sciences-table convention.
|
||||
function formatPercent(fraction: number): string {
|
||||
return (fraction * 100).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
}
|
||||
|
||||
function formatCount(value: number): string {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function formatVotes(value: number): string {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
}
|
||||
|
||||
async function setStance(acceptor: string, relation: Relation): Promise<void> {
|
||||
if (draft === undefined) return;
|
||||
// No-op when the row already reflects the requested stance — the
|
||||
@@ -192,7 +171,7 @@ data fetching is performed here — the layout is responsible.
|
||||
<span class="summary-label">
|
||||
{i18n.t("game.table.races.votes.mine")}:
|
||||
</span>
|
||||
<span class="summary-value">{formatVotes(myVotes)}</span>
|
||||
<span class="summary-value numeric">{formatFloat(myVotes)}</span>
|
||||
</span>
|
||||
<label class="summary-cell vote-picker">
|
||||
<span class="summary-label">
|
||||
@@ -244,7 +223,7 @@ data fetching is performed here — the layout is responsible.
|
||||
<thead>
|
||||
<tr>
|
||||
{#each COLUMNS as column (column)}
|
||||
<th aria-sort={ariaSort(column)}>
|
||||
<th aria-sort={ariaSort(column)} class:numeric={column !== "name"}>
|
||||
<button
|
||||
type="button"
|
||||
class="sort"
|
||||
@@ -267,23 +246,29 @@ data fetching is performed here — the layout is responsible.
|
||||
{#each sorted as r (r.name)}
|
||||
<tr data-testid="races-row" data-name={r.name}>
|
||||
<td data-testid="races-cell-name">{r.name}</td>
|
||||
<td data-testid="races-cell-drive">{formatPercent(r.drive)}</td>
|
||||
<td data-testid="races-cell-weapons">
|
||||
{formatPercent(r.weapons)}
|
||||
<td class="numeric" data-testid="races-cell-drive">
|
||||
{formatFloat(r.drive)}
|
||||
</td>
|
||||
<td data-testid="races-cell-shields">
|
||||
{formatPercent(r.shields)}
|
||||
<td class="numeric" data-testid="races-cell-weapons">
|
||||
{formatFloat(r.weapons)}
|
||||
</td>
|
||||
<td data-testid="races-cell-cargo">{formatPercent(r.cargo)}</td>
|
||||
<td data-testid="races-cell-population">
|
||||
{formatCount(r.population)}
|
||||
<td class="numeric" data-testid="races-cell-shields">
|
||||
{formatFloat(r.shields)}
|
||||
</td>
|
||||
<td data-testid="races-cell-industry">
|
||||
{formatCount(r.industry)}
|
||||
<td class="numeric" data-testid="races-cell-cargo">
|
||||
{formatFloat(r.cargo)}
|
||||
</td>
|
||||
<td data-testid="races-cell-planets">{formatCount(r.planets)}</td>
|
||||
<td data-testid="races-cell-votes">
|
||||
{formatVotes(r.votesReceived)}
|
||||
<td class="numeric" data-testid="races-cell-population">
|
||||
{formatInt(r.population)}
|
||||
</td>
|
||||
<td class="numeric" data-testid="races-cell-industry">
|
||||
{formatInt(r.industry)}
|
||||
</td>
|
||||
<td class="numeric" data-testid="races-cell-planets">
|
||||
{formatInt(r.planets)}
|
||||
</td>
|
||||
<td class="numeric" data-testid="races-cell-votes">
|
||||
{formatFloat(r.votesReceived)}
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
@@ -359,6 +344,9 @@ data fetching is performed here — the layout is responsible.
|
||||
color: var(--color-text);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.summary-value.numeric {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.vote-picker select {
|
||||
font: inherit;
|
||||
padding: 0.2rem 0.4rem;
|
||||
@@ -403,7 +391,7 @@ data fetching is performed here — the layout is responsible.
|
||||
padding: 0.4rem 0.6rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th {
|
||||
color: var(--color-text-muted);
|
||||
@@ -411,6 +399,14 @@ data fetching is performed here — the layout is responsible.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid th.numeric .sort {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.grid tbody tr:hover {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ data fetching is performed here — the shell is responsible.
|
||||
ORDER_DRAFT_CONTEXT_KEY,
|
||||
OrderDraftStore,
|
||||
} from "../../sync/order-draft.svelte";
|
||||
import { formatPercent } from "$lib/util/number-format";
|
||||
|
||||
type SortColumn = "name" | "drive" | "weapons" | "shields" | "cargo";
|
||||
type SortDirection = "asc" | "desc";
|
||||
@@ -103,16 +104,6 @@ data fetching is performed here — the shell is responsible.
|
||||
return sortDirection === "asc" ? "ascending" : "descending";
|
||||
}
|
||||
|
||||
// Render a fraction in `[0, 1]` as a one-decimal percent
|
||||
// (`0.225` → `"22.5"`). The conversion is value-only — no `%`
|
||||
// suffix — so callers can decorate independently.
|
||||
function formatPercent(fraction: number): string {
|
||||
return (fraction * 100).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
}
|
||||
|
||||
function openDesigner(name: string): void {
|
||||
activeView.select("designer-science", { scienceId: name });
|
||||
}
|
||||
@@ -174,7 +165,7 @@ data fetching is performed here — the shell is responsible.
|
||||
<thead>
|
||||
<tr>
|
||||
{#each COLUMNS as column (column)}
|
||||
<th aria-sort={ariaSort(column)}>
|
||||
<th aria-sort={ariaSort(column)} class:numeric={column !== "name"}>
|
||||
<button
|
||||
type="button"
|
||||
class="sort"
|
||||
@@ -201,14 +192,18 @@ data fetching is performed here — the shell is responsible.
|
||||
ondblclick={() => openDesigner(sci.name)}
|
||||
>
|
||||
<td data-testid="sciences-cell-name">{sci.name}</td>
|
||||
<td data-testid="sciences-cell-drive">{formatPercent(sci.drive)}</td>
|
||||
<td data-testid="sciences-cell-weapons">
|
||||
<td class="numeric" data-testid="sciences-cell-drive">
|
||||
{formatPercent(sci.drive)}
|
||||
</td>
|
||||
<td class="numeric" data-testid="sciences-cell-weapons">
|
||||
{formatPercent(sci.weapons)}
|
||||
</td>
|
||||
<td data-testid="sciences-cell-shields">
|
||||
<td class="numeric" data-testid="sciences-cell-shields">
|
||||
{formatPercent(sci.shields)}
|
||||
</td>
|
||||
<td data-testid="sciences-cell-cargo">{formatPercent(sci.cargo)}</td>
|
||||
<td class="numeric" data-testid="sciences-cell-cargo">
|
||||
{formatPercent(sci.cargo)}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
@@ -283,7 +278,7 @@ data fetching is performed here — the shell is responsible.
|
||||
padding: 0.4rem 0.6rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th {
|
||||
color: var(--color-text-muted);
|
||||
@@ -291,6 +286,14 @@ data fetching is performed here — the shell is responsible.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid th.numeric .sort {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.grid tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ data fetching is performed here — the layout is responsible.
|
||||
} from "../../sync/order-draft.svelte";
|
||||
import { calculatorLoadRequest } from "$lib/calculator/load-request.svelte";
|
||||
import ViewState from "$lib/ui/view-state.svelte";
|
||||
import { formatFloat } from "$lib/util/number-format";
|
||||
|
||||
type SortColumn =
|
||||
| "name"
|
||||
@@ -106,10 +107,6 @@ data fetching is performed here — the layout is responsible.
|
||||
return sortDirection === "asc" ? "ascending" : "descending";
|
||||
}
|
||||
|
||||
function formatNumber(value: number): string {
|
||||
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function openInCalculator(name: string): void {
|
||||
calculatorLoadRequest.request(name);
|
||||
}
|
||||
@@ -171,7 +168,7 @@ data fetching is performed here — the layout is responsible.
|
||||
<thead>
|
||||
<tr>
|
||||
{#each COLUMNS as column (column)}
|
||||
<th aria-sort={ariaSort(column)}>
|
||||
<th aria-sort={ariaSort(column)} class:numeric={column !== "name"}>
|
||||
<button
|
||||
type="button"
|
||||
class="sort"
|
||||
@@ -198,11 +195,21 @@ data fetching is performed here — the layout is responsible.
|
||||
ondblclick={() => openInCalculator(cls.name)}
|
||||
>
|
||||
<td data-testid="ship-classes-cell-name">{cls.name}</td>
|
||||
<td data-testid="ship-classes-cell-drive">{formatNumber(cls.drive)}</td>
|
||||
<td data-testid="ship-classes-cell-armament">{cls.armament}</td>
|
||||
<td data-testid="ship-classes-cell-weapons">{formatNumber(cls.weapons)}</td>
|
||||
<td data-testid="ship-classes-cell-shields">{formatNumber(cls.shields)}</td>
|
||||
<td data-testid="ship-classes-cell-cargo">{formatNumber(cls.cargo)}</td>
|
||||
<td class="numeric" data-testid="ship-classes-cell-drive">
|
||||
{formatFloat(cls.drive)}
|
||||
</td>
|
||||
<td class="numeric" data-testid="ship-classes-cell-armament">
|
||||
{cls.armament}
|
||||
</td>
|
||||
<td class="numeric" data-testid="ship-classes-cell-weapons">
|
||||
{formatFloat(cls.weapons)}
|
||||
</td>
|
||||
<td class="numeric" data-testid="ship-classes-cell-shields">
|
||||
{formatFloat(cls.shields)}
|
||||
</td>
|
||||
<td class="numeric" data-testid="ship-classes-cell-cargo">
|
||||
{formatFloat(cls.cargo)}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
@@ -277,7 +284,7 @@ data fetching is performed here — the layout is responsible.
|
||||
padding: 0.4rem 0.6rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.grid th {
|
||||
color: var(--color-text-muted);
|
||||
@@ -285,6 +292,14 @@ data fetching is performed here — the layout is responsible.
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.grid th.numeric,
|
||||
.grid td.numeric {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
.grid th.numeric .sort {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.grid tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user