feat: add number format config, resource cap, and modal scroll fix

- Add user-configurable number format (suffix/scientific/engineering)
  - Suffix: K/M/B/T through Dc (1e33), then letter-based a/b/c... indefinitely
  - Scientific: 1.23e15 style via toExponential
  - Engineering: exponent always a multiple of 3 (1.23E15)
  - Stored in ProfileSettings, fetched from profile API on load
  - Picker UI in EditProfileModal with live examples
- Cap all resource accumulation at 1e300 (RESOURCE_CAP constant)
  - Per-resource FULL badge with tooltip in ResourceBar
  - Amber notice strip when any resource is at cap
  - handleClick also respects the cap
- Make EditProfileModal scrollable with viewport margin
  - Flex column layout with sticky header, scrollable form body
  - Bio textarea preserved as resizable with min-height
- Fix ReferenceError: formatNumber not defined in BossPanel/AchievementPanel
  - Pass formatNumber as prop to BossCard and AchievementCard
  - Pass formatNumber as parameter to conditionDescription
This commit is contained in:
2026-03-06 18:59:43 -08:00
committed by Naomi Carrigan
parent 24beaf3131
commit 5ad2c44399
15 changed files with 290 additions and 65 deletions
+10
View File
@@ -10,9 +10,14 @@ import { authMiddleware } from "../middleware/auth.js";
export const profileRouter = new Hono();
const VALID_NUMBER_FORMATS = new Set(["suffix", "scientific", "engineering"]);
const parseProfileSettings = (raw: unknown): ProfileSettings => {
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
const obj = raw as Record<string, unknown>;
const numberFormat = VALID_NUMBER_FORMATS.has(obj.numberFormat as string)
? (obj.numberFormat as ProfileSettings["numberFormat"])
: "suffix";
return {
showTotalGold: obj.showTotalGold !== false,
showTotalClicks: obj.showTotalClicks !== false,
@@ -22,6 +27,7 @@ const parseProfileSettings = (raw: unknown): ProfileSettings => {
showQuestsCompleted: obj.showQuestsCompleted !== false,
showAdventurersRecruited: obj.showAdventurersRecruited !== false,
showAchievementsUnlocked: obj.showAchievementsUnlocked !== false,
numberFormat,
};
}
return { ...DEFAULT_PROFILE_SETTINGS };
@@ -73,6 +79,9 @@ profileRouter.put("/", authMiddleware, async (context) => {
const characterName = (body.characterName ?? "").trim().slice(0, 32);
const bio = (body.bio ?? "").trim().slice(0, 200);
const numberFormat = VALID_NUMBER_FORMATS.has(body.profileSettings?.numberFormat as string)
? (body.profileSettings?.numberFormat as ProfileSettings["numberFormat"])
: "suffix";
const profileSettings: ProfileSettings = {
showTotalGold: body.profileSettings?.showTotalGold !== false,
showTotalClicks: body.profileSettings?.showTotalClicks !== false,
@@ -82,6 +91,7 @@ profileRouter.put("/", authMiddleware, async (context) => {
showQuestsCompleted: body.profileSettings?.showQuestsCompleted !== false,
showAdventurersRecruited: body.profileSettings?.showAdventurersRecruited !== false,
showAchievementsUnlocked: body.profileSettings?.showAchievementsUnlocked !== false,
numberFormat,
};
if (!characterName) {