Files
elysium/apps/web/src/components/game/aboutPanel.tsx
T
hikari d1d1f70c75 chore: fix lint, ensure full CI pipeline passes, add verify checklist
- Fix strict-boolean-expressions in 7 route files (runtime body validation)
- Fix no-unnecessary-condition in profile.ts and offlineProgress.ts (defensive null checks)
- Extend v8 ignore next-N counts in game.ts to reach 100% coverage
- Add CI requirements to CLAUDE.md (lint + build + test must pass before commit)
- Add manual verification checklist (verify.md)
- Remove progress.md
2026-03-08 13:59:38 -07:00

347 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file About panel component displaying changelog and how-to-play guide.
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable max-lines-per-function -- HOW_TO_PLAY data and render logic */
/* eslint-disable max-lines -- HOW_TO_PLAY data makes this file long */
import { type JSX, useEffect, useState } from "react";
import { getAbout } from "../../api/client.js";
import type { AboutResponse } from "@elysium/types";
const howToPlay = [
{
body:
"Hire adventurers to earn gold and essence automatically. Each tier is"
+ " more powerful than the last. Adventurers also contribute combat"
+ " power for boss fights — the more you recruit, the stronger your"
+ " party becomes.",
title: "⚔️ Adventurers",
},
{
body:
"Click the guild hall to earn gold manually. Upgrades and equipment can"
+ " dramatically increase your gold per click. Clicking is especially"
+ " powerful in the early game and when saving up for big purchases.",
title: "👆 Clicking",
},
{
body:
"Purchase upgrades to multiply the gold and essence output of specific"
+ " adventurer tiers, or boost your whole guild. Upgrades are permanent"
+ " for the current run and compound with each other.",
title: "🔧 Upgrades",
},
{
body:
"Send your guild on quests that complete over time and reward gold,"
+ " essence, crystals, equipment, and upgrades. Multiple quests can run"
+ " simultaneously. Completing quests also unlocks new zones.",
title: "📜 Quests",
},
{
body:
"Challenge zone bosses to earn large one-time rewards and unlock new"
+ " zones. Your party's combat power is based on the number and tier of"
+ " adventurers you've recruited. Defeated bosses cannot be re-fought,"
+ " but undefeated bosses regenerate HP over time.",
title: "👹 Boss Fights",
},
{
body:
"New zones unlock when you defeat the final boss AND complete the final"
+ " quest of the previous zone. Each zone contains new bosses and"
+ " quests with progressively greater rewards.",
title: "🗺️ Zones",
},
{
body:
"Earn equipment from boss drops and quest rewards. Each piece provides"
+ " bonuses to gold income, click power, or combat. Rarer equipment"
+ " provides stronger bonuses. Equip matching set pieces (2 or 3 of a"
+ " named set) to unlock escalating set bonuses shown at the top of the"
+ " Equipment panel.",
title: "🗡️ Equipment & Sets",
},
{
body:
"When you've progressed far enough, you can prestige to earn runestones"
+ " — a permanent currency that persists across all runs. Prestige"
+ " resets your current run but grants a production multiplier that"
+ " stacks with every prestige.",
title: "⭐ Prestige",
},
{
body:
"Spend runestones in the Prestige Shop on permanent upgrades that carry"
+ " over across all future runs. These upgrades multiply income, click"
+ " power, essence, and crystal gain — making each new run more powerful"
+ " than the last.",
title: "🔮 Runestones & Prestige Upgrades",
},
{
body:
"Purchase the Autonomous Ascension upgrade in the Prestige Shop"
+ " (100 runestones) to unlock the Auto-Prestige toggle. When enabled,"
+ " you will automatically ascend the moment you reach the prestige"
+ " threshold, using your current character name. Toggle it on and off"
+ " freely from the Prestige Shop.",
title: "⚙️ Auto-Prestige",
},
{
body:
"Earn achievements by hitting milestones — total gold earned, bosses"
+ " defeated, quests completed, and more. Achievements are purely"
+ " cosmetic and track your long-term progress across all prestige runs.",
title: "🏆 Achievements",
},
{
body:
"Complete daily challenges for bonus rewards including gold, essence,"
+ " crystals, and runestones. Challenges reset each day and vary in"
+ " difficulty. Completing all daily challenges gives an extra bonus"
+ " reward.",
title: "📅 Daily Challenges",
},
{
body:
"Send scouts to explore areas within each zone. Explorations run in"
+ " real-time and reward gold, essence, and crafting materials when"
+ " collected. Each area has a set duration — short explorations are"
+ " faster but longer ones offer rarer finds. A 📖 icon marks areas"
+ " you've collected from at least once, unlocking a Codex entry.",
title: "🗺️ Exploration",
},
{
body:
"Use materials gathered from exploration to craft permanent bonuses."
+ " Each recipe provides a multiplier to gold income, essence income,"
+ " click power, or combat power — all of which stack and persist across"
+ " prestige runs. Check the Crafting tab to see your material inventory"
+ " and available recipes per zone.",
title: "⚗️ Crafting",
},
{
body:
"Defeating bosses, completing quests, acquiring equipment, hiring"
+ " adventurers, purchasing upgrades, unlocking prestige upgrades,"
+ " discovering new zones, collecting from exploration areas, and"
+ " crafting recipes all permanently unlock lore entries in the Codex."
+ " A badge appears on the Codex tab and a toast notification pops up"
+ " each time new lore is discovered. Collect all 472 entries to build"
+ " a complete picture of the world of Elysium.",
title: "📖 Codex",
},
{
body:
"Visit the Character tab to write about your character and guild. Fill"
+ " in your character's name, pronouns, race, class, and backstory,"
+ " then create a guild with its own name and lore. Your character sheet"
+ " is visible on your public profile page.",
title: "📋 Character Sheet",
},
{
body:
"Earn Titles by reaching milestones — defeating bosses, completing"
+ " quests, prestiging, and more. Once unlocked, titles are yours"
+ " forever and are never lost on prestige or transcendence resets. Set"
+ " your active title from the Character tab to display it on your"
+ " character sheet and public profile.",
title: "🏅 Titles",
},
{
body:
"Defeat bosses to earn equipment drops: weapons, armour, and trinkets."
+ " Each item provides bonuses to gold income, combat power, or click"
+ " power. Only one item per slot can be equipped at a time — visit the"
+ " Equipment panel to manage your loadout. Your currently equipped"
+ " items are displayed on your character sheet and public profile.",
title: "🗡️ Equipment",
},
{
body:
"Compete with other adventurers on the public Leaderboards page!"
+ " Categories include Lifetime Gold, Bosses Defeated, Quests"
+ " Completed, Achievements, Prestige Count, Transcendence Count, and"
+ " Apotheosis Count. Click any player's row to view their character"
+ " sheet. You can opt out of appearing on leaderboards via the Privacy"
+ " section in your profile settings.",
title: "🏆 Leaderboards",
},
{
body:
"Log in every day to earn escalating rewards! Each consecutive day"
+ " awards more gold, and the 7th day of your streak grants bonus"
+ " crystals. Your streak resets if you miss a day. A week multiplier"
+ " increases all rewards the longer your overall streak runs. Your"
+ " current streak is displayed on your character sheet.",
title: "🔥 Daily Login Bonus",
},
{
body:
"Toggle automation in the Quests and Boss Encounters panels! Auto-Quest"
+ " automatically sends your party on the highest-zone available quest"
+ " as soon as one completes, skipping quests whose combat power"
+ " requirement isn't met. Auto-Boss automatically challenges the"
+ " highest available boss as soon as one is ready. Both can be toggled"
+ " on or off at any time using the 🤖 Auto button in each panel"
+ " header.",
title: "🤖 Auto-Quest & Auto-Boss",
},
{
body:
"Unlock companions by reaching certain milestones across all your runs."
+ " Each companion provides a powerful permanent bonus: increased"
+ " passive gold, click gold, boss damage, essence income, or reduced"
+ " quest time. You can only have one companion active at a time —"
+ " choose wisely based on your current strategy! Companions are"
+ " unlocked permanently once their condition is met and will never be"
+ " lost.",
title: "👥 Companions",
},
{
body:
"Your progress is automatically saved to the cloud every 30 seconds"
+ " whilst you play. You can also force a manual save at any time using"
+ " the sync button in the resource bar. Your save is protected by HMAC"
+ " validation to ensure data integrity.",
title: "☁️ Cloud Saves",
},
{
body:
"Transcendence is the ultimate prestige layer, unlocked by defeating"
+ " The Absolute One (requires Prestige 90). Transcending performs a"
+ " nuclear reset — wiping resources, prestige, runestones, upgrades,"
+ " and equipment — but grants Echoes based on your prestige count"
+ " (fewer prestiges = more Echoes). Echoes are permanent and survive"
+ " all future resets. Spend them in the Echo Shop on lasting"
+ " multipliers: passive income, combat power, prestige"
+ " quality-of-life, and Echo meta upgrades that amplify future Echo"
+ " yields.",
title: "🌌 Transcendence",
},
{
body:
"Apotheosis is the final act — a complete dissolution of everything you"
+ " have built, including your prestige and transcendence progress. It"
+ " is unlocked once you have purchased every Transcendence upgrade. In"
+ " exchange for this total reset, you receive the Apotheosis badge:"
+ " pure bragging rights, a mark of reaching the absolute pinnacle of"
+ " the game. Apotheosis can be achieved multiple times; each cycle"
+ " requires purchasing all Transcendence upgrades again. Your Codex"
+ " entries and lifetime profile statistics are always preserved.",
title: "✨ Apotheosis",
},
{
body:
"The Story tab contains 22 chapters that unlock as you progress. The"
+ " first 18 unlock when you defeat the final boss of each zone."
+ " Chapters 19 and 20 unlock after your first and fifth prestige"
+ " respectively. Chapter 21 unlocks on your first transcendence, and"
+ " Chapter 22 on your first apotheosis. Each chapter presents a"
+ " narrative moment and three choices — the choice you make is recorded"
+ " on your Character Sheet and shapes your guild's story. Story"
+ " progress is permanent and survives all resets.",
title: "📖 Story",
},
];
const formatDate = (dateString: string): string => {
return new Date(dateString).toLocaleDateString("en-GB", {
day: "numeric",
month: "short",
year: "numeric",
});
};
/**
* Renders the about panel with changelog and how-to-play sections.
* @returns The JSX element.
*/
const aboutPanel = (): JSX.Element => {
const [ about, setAbout ] = useState<AboutResponse | null>(null);
const [ error, setError ] = useState<string | null>(null);
const [ expandedRelease, setExpandedRelease ] = useState<string | null>(null);
useEffect(() => {
getAbout().
then(setAbout).
catch((caughtError: unknown) => {
setError(
caughtError instanceof Error
? caughtError.message
: "Failed to load about data.",
);
});
}, []);
return (
<section className="panel about-panel">
<h2>{"️ About"}</h2>
<h3 className="stats-section-header">{"📋 Changelog"}</h3>
{error !== null && <p className="about-error">{error}</p>}
{about === null && error === null
&& <p className="about-loading">{"Loading changelog..."}</p>
}
{about !== null && about.releases.length === 0
&& <p className="about-empty">{"No releases yet."}</p>
}
{about !== null && about.releases.length > 0
&& <ul className="about-releases">
{about.releases.map((release) => {
function handleToggle(): void {
setExpandedRelease(
expandedRelease === release.tag_name
? null
: release.tag_name,
);
}
return (
<li className="about-release" key={release.tag_name}>
<button
className="about-release-header"
onClick={handleToggle}
type="button"
>
<span className="about-release-tag">
{release.name.length > 0
? release.name
: release.tag_name}
</span>
<span className="about-release-date">
{formatDate(release.published_at)}
</span>
<span className="about-release-chevron">
{expandedRelease === release.tag_name
? "▲"
: "▼"}
</span>
</button>
{expandedRelease === release.tag_name
&& <pre className="about-release-body">{release.body}</pre>
}
</li>
);
})}
</ul>
}
<h3 className="stats-section-header">{"📖 How to Play"}</h3>
<ul className="about-how-to-play">
{howToPlay.map((section) => {
return (
<li className="about-htp-section" key={section.title}>
<h4 className="about-htp-title">{section.title}</h4>
<p className="about-htp-body">{section.body}</p>
</li>
);
})}
</ul>
</section>
);
};
export { aboutPanel as AboutPanel };