generated from nhcarrigan/template
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
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* @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 };
|
||||
Reference in New Issue
Block a user