Release v0.1.4 2026-07-12
This commit is contained in:
+53
-34
@@ -5,7 +5,7 @@ import { useActiveHunter, useFrontendStore } from "../frontend/store";
|
||||
import type { GameModeId, SaveSlotId, SaveSlotState } from "../frontend/types";
|
||||
import { useMenuController, type MenuAction } from "../input/useMenuController";
|
||||
import { HEALER_CLASSES, HEALER_CLASS_ORDER } from "../game/healers";
|
||||
import { BOSS_DEFINITIONS, BOSS_ORDER } from "../game/bossCatalog";
|
||||
import { AVAILABLE_BOSS_IDS, BOSS_DEFINITIONS, BOSS_GROUP_BY_ID } from "../game/bossCatalog";
|
||||
import { selectRandomBossPair } from "../game/roguelike";
|
||||
import type { BossId } from "../game/types";
|
||||
import {
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
gearBonusText,
|
||||
gearUpgradeCosts,
|
||||
} from "../game/progression/gear";
|
||||
import { DIFFICULTIES, DIFFICULTY_BY_SLUG, bossCoinDrop } from "../game/progression/loot";
|
||||
import { DIFFICULTIES, DIFFICULTY_BY_SLUG, bossGroupDrop } from "../game/progression/loot";
|
||||
import {
|
||||
ACTIVE_INFUSION_MIN_GEAR_LEVEL,
|
||||
PASSIVE_INFUSIONS,
|
||||
@@ -388,7 +388,7 @@ function HomeScreen() {
|
||||
</div>
|
||||
<div className="home-secondary-actions">
|
||||
<FocusButton id="profile" focusedId={controller.focusedId} focus={controller.focus} onClick={() => navigate("profile")}><i>♙</i><span><strong>Hunter Profile</strong><small>Stats & collection log</small></span><b>›</b></FocusButton>
|
||||
<FocusButton id="gear" focusedId={controller.focusedId} focus={controller.focus} onClick={() => navigate("gear")}><i>⚒</i><span><strong>Gear Upgrade</strong><small>Spend boss coins</small></span><b>›</b></FocusButton>
|
||||
<FocusButton id="gear" focusedId={controller.focusedId} focus={controller.focus} onClick={() => navigate("gear")}><i>⚒</i><span><strong>Gear Upgrade</strong><small>Spend group drops</small></span><b>›</b></FocusButton>
|
||||
<FocusButton id="settings" focusedId={controller.focusedId} focus={controller.focus} onClick={() => navigate("settings")}><i>⚙</i><span><strong>Settings</strong><small>Audio, display, controls</small></span><b>›</b></FocusButton>
|
||||
</div>
|
||||
<ControllerLegend back />
|
||||
@@ -424,10 +424,10 @@ function ProfileScreen() {
|
||||
const hunter = useActiveHunter();
|
||||
const navigate = useFrontendStore((state) => state.navigate);
|
||||
const collections = useMemo(() => hunter ? buildCollections(hunter.collectionLog, hunter.stats.bossKills) : [], [hunter]);
|
||||
const [bossId, setBossId] = useState(collections[0]?.bossId ?? "");
|
||||
const collection = collections.find((boss) => boss.bossId === bossId) ?? collections[0];
|
||||
const [groupId, setGroupId] = useState(collections[0]?.groupId ?? "");
|
||||
const collection = collections.find((group) => group.groupId === groupId) ?? collections[0];
|
||||
const actions = useMemo<MenuAction[]>(() => [
|
||||
...collections.map((boss) => ({ id: boss.bossId, run: () => setBossId(boss.bossId) })),
|
||||
...collections.map((group) => ({ id: `group-${group.groupId}`, run: () => setGroupId(group.groupId) })),
|
||||
{ id: "back", run: () => navigate("home") },
|
||||
], [collections, navigate]);
|
||||
const controller = useMenuController(actions, { onBack: () => navigate("home") });
|
||||
@@ -441,22 +441,22 @@ function ProfileScreen() {
|
||||
top={
|
||||
<FrontSurface className="profile-surface" ariaLabel="Hunter profile collection log">
|
||||
<header className="front-screen-header"><BrandMark compact /><div><span>Hunter profile</span><h1>Collection log</h1></div><FocusButton id="back" focusedId={controller.focusedId} focus={controller.focus} className="header-back" onClick={() => navigate("home")}>B · Back</FocusButton></header>
|
||||
<div className="collection-heading"><span><small>Boss spoils</small><h2>{collection.bossName}</h2></span><b>{earned} / {collection.drops.length} discovered</b></div>
|
||||
<div className="collection-heading"><span><small>Shared group drops · Core: {collection.coreMechanic}</small><h2>Group {collection.groupLetter} · {collection.groupName}</h2></span><b>{earned} / {collection.drops.length} discovered</b></div>
|
||||
<div className="collection-grid">
|
||||
{collection.drops.map((drop) => (
|
||||
<article key={drop.id} className={`collection-drop rarity-${drop.rarity.toLowerCase()} ${drop.count === 0 ? "is-missing" : ""}`}>
|
||||
<span className="drop-icon">{drop.icon}<b>{drop.count}</b></span>
|
||||
<small>{drop.rarity}</small><strong>{drop.name}</strong>
|
||||
<p>{drop.count ? `${drop.count} earned` : collection.defeated ? "Not yet earned" : "Defeat boss to reveal"}</p>
|
||||
<p>{drop.count ? `${drop.count} earned` : collection.defeated ? "Not yet earned" : `Defeat a Group ${collection.groupLetter} boss`}</p>
|
||||
<small>{drop.chance}{drop.itemLevel ? ` · iLvl ${drop.itemLevel}` : ""}</small>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
<div className="collection-note"><i>✦</i><span><strong>Every drop stays counted.</strong><small>Duplicates increase quantity instead of disappearing.</small></span></div>
|
||||
<div className="collection-note"><i>✦</i><span><strong>Boss pets stay individual.</strong><small>{collection.bosses.map((boss) => `${boss.bossName}: ${boss.kills} kills · ${boss.pet.count} pets`).join(" · ")}</small></span></div>
|
||||
</FrontSurface>
|
||||
}
|
||||
bottom={
|
||||
<FrontSurface className="profile-context" bottom ariaLabel="Hunter statistics and boss list">
|
||||
<FrontSurface className="profile-context" bottom ariaLabel="Hunter statistics and mechanic group list">
|
||||
<header className="context-header"><span>{hunter.hunterName} · {activeHealer.name} stats</span><b>LEVEL {activeProgress.level}</b></header>
|
||||
<div className="profile-stats">
|
||||
<span><small>Total boss kills</small><strong>{hunter.stats.totalBossKills}</strong></span>
|
||||
@@ -464,9 +464,9 @@ function ProfileScreen() {
|
||||
<span><small>Allies saved</small><strong>{hunter.stats.alliesSaved}</strong></span>
|
||||
<span><small>Healing done</small><strong>{hunter.stats.healingDone.toLocaleString()}</strong></span>
|
||||
</div>
|
||||
<div className="boss-log"><span>Boss records</span>{collections.map((boss) => (
|
||||
<FocusButton key={boss.bossId} id={boss.bossId} focusedId={controller.focusedId} focus={controller.focus} className={boss.bossId === collection.bossId ? "is-selected" : ""} onClick={() => setBossId(boss.bossId)}>
|
||||
<i>{boss.defeated ? "♜" : "?"}</i><span><strong>{boss.bossName}</strong><small>{hunter.stats.bossKills[boss.bossId] ?? 0} kills</small></span><b>{boss.drops.filter((drop) => drop.count > 0).length}/{boss.drops.length}</b>
|
||||
<div className="boss-log"><span>Mechanic groups</span>{collections.map((group) => (
|
||||
<FocusButton key={group.groupId} id={`group-${group.groupId}`} focusedId={controller.focusedId} focus={controller.focus} className={group.groupId === collection.groupId ? "is-selected" : ""} onClick={() => setGroupId(group.groupId)}>
|
||||
<i>{group.defeated ? group.groupLetter : "?"}</i><span><strong>Group {group.groupLetter} · {group.groupName}</strong><small>{group.bosses.reduce((sum, boss) => sum + boss.kills, 0)} kills · {group.coreMechanic}</small></span><b>{group.drops.filter((drop) => drop.count > 0).length}/{group.drops.length}</b>
|
||||
</FocusButton>
|
||||
))}</div>
|
||||
</FrontSurface>
|
||||
@@ -558,7 +558,7 @@ function GearScreen() {
|
||||
<DualDisplayFrame
|
||||
top={
|
||||
<FrontSurface className="gear-surface" ariaLabel="Gear upgrade workshop">
|
||||
<header className="front-screen-header"><BrandMark compact /><div><span>Boss coin workshop</span><h1>Gear & Infusions</h1></div><div className="gear-mode-tabs"><FocusButton id="workshop-upgrade" focusedId={controller.focusedId} focus={controller.focus} className={workshopMode === "upgrade" ? "is-selected" : ""} onClick={() => selectWorkshopMode("upgrade")}>Upgrade</FocusButton><FocusButton id="workshop-infusion" focusedId={controller.focusedId} focus={controller.focus} className={workshopMode === "infusion" ? "is-selected" : ""} onClick={() => selectWorkshopMode("infusion")}>Infusion</FocusButton></div><FocusButton id="back" focusedId={controller.focusedId} focus={controller.focus} className="header-back" onClick={() => navigate("home")}>B · Back</FocusButton></header>
|
||||
<header className="front-screen-header"><BrandMark compact /><div><span>Group drop workshop</span><h1>Gear & Infusions</h1></div><div className="gear-mode-tabs"><FocusButton id="workshop-upgrade" focusedId={controller.focusedId} focus={controller.focus} className={workshopMode === "upgrade" ? "is-selected" : ""} onClick={() => selectWorkshopMode("upgrade")}>Upgrade</FocusButton><FocusButton id="workshop-infusion" focusedId={controller.focusedId} focus={controller.focus} className={workshopMode === "infusion" ? "is-selected" : ""} onClick={() => selectWorkshopMode("infusion")}>Infusion</FocusButton></div><FocusButton id="back" focusedId={controller.focusedId} focus={controller.focus} className="header-back" onClick={() => navigate("home")}>B · Back</FocusButton></header>
|
||||
<div className="gear-workshop-layout">
|
||||
<section className="gear-owner-list" aria-label="Party gear owners">
|
||||
{GEAR_OWNER_ORDER.map((ownerId) => {
|
||||
@@ -576,7 +576,7 @@ function GearScreen() {
|
||||
{workshopMode === "upgrade" ? <article className="gear-preview">
|
||||
<span>Selected upgrade</span>
|
||||
<h2>{GEAR_OWNER_LABELS[selectedOwnerId]} · {GEAR_SLOT_LABELS[selectedSlotId]} +{slot.level}</h2>
|
||||
<p>{GEAR_STAT_LABELS[recipe.statId]} from {BOSS_DEFINITIONS[recipe.primaryBossId].name} and {BOSS_DEFINITIONS[recipe.secondaryBossId].name} coins.</p>
|
||||
<p>{GEAR_STAT_LABELS[recipe.statId]} from Group {BOSS_GROUP_BY_ID[recipe.primaryGroupId].letter} and Group {BOSS_GROUP_BY_ID[recipe.secondaryGroupId].letter} drops.</p>
|
||||
<div className="gear-stat-comparison"><span><small>Current</small><strong>{currentBonus}</strong></span><i>→</i><span><small>{slot.level >= MAX_GEAR_LEVEL ? "Maximum" : `Rank +${slot.level + 1}`}</small><strong>{nextBonus}</strong></span></div>
|
||||
</article> : <article className="gear-preview gear-infusion-preview">
|
||||
<span>Active infusion · unlock +{ACTIVE_INFUSION_MIN_GEAR_LEVEL}</span>
|
||||
@@ -593,7 +593,7 @@ function GearScreen() {
|
||||
}
|
||||
bottom={
|
||||
<FrontSurface className="gear-context" bottom ariaLabel="Gear recipe and material inventory">
|
||||
<header className="context-header"><span>{workshopMode === "upgrade" ? `${GEAR_SLOT_LABELS[selectedSlotId]} recipe` : `${selectedInfusion.name} infusion`}</span><b>{hunter.materials.reduce((sum, item) => sum + item.quantity, 0)} COINS</b></header>
|
||||
<header className="context-header"><span>{workshopMode === "upgrade" ? `${GEAR_SLOT_LABELS[selectedSlotId]} recipe` : `${selectedInfusion.name} infusion`}</span><b>{hunter.materials.reduce((sum, item) => sum + item.quantity, 0)} DROPS</b></header>
|
||||
<div className="gear-costs">
|
||||
<span>{workshopMode === "upgrade" ? "Upgrade requirements" : `Infusion requirements · ${GEAR_SLOT_LABELS[selectedSlotId]} +${slot.level} anchor`}</span>
|
||||
{(workshopMode === "upgrade" ? costs : selectedInfusionCosts).length ? (workshopMode === "upgrade" ? costs : selectedInfusionCosts).map((cost) => {
|
||||
@@ -601,7 +601,7 @@ function GearScreen() {
|
||||
return <article className={owned >= cost.quantity ? "is-met" : "is-missing"} key={cost.itemId}><i>{owned >= cost.quantity ? "✓" : "×"}</i><span><strong>{cost.itemName}</strong><small>{owned} owned · {cost.quantity} needed</small></span><b>{owned}/{cost.quantity}</b></article>;
|
||||
}) : <article className="is-met"><i>✓</i><span><strong>Maximum rank reached</strong><small>No more materials required.</small></span><b>+{MAX_GEAR_LEVEL}</b></article>}
|
||||
</div>
|
||||
{workshopMode === "upgrade" ? <FocusButton id="upgrade" focusedId={controller.focusedId} focus={controller.focus} className="gear-upgrade-action" disabled={!canUpgrade} onClick={upgrade}><span>{slot.level >= MAX_GEAR_LEVEL ? "Maximum rank" : `Upgrade to +${slot.level + 1}`}</span><small>{canUpgrade ? "Spend coins · autosave" : "Collect required boss coins"}</small></FocusButton> : <FocusButton id="install-infusion" focusedId={controller.focusedId} focus={controller.focus} className="gear-upgrade-action" disabled={!canInstallInfusion} onClick={installInfusion}><span>{infusionEquipped ? "Infusion equipped" : `Install ${selectedInfusion.name}`}</span><small>{!activeUnlocked ? `Raise any ${GEAR_OWNER_LABELS[selectedOwnerId]} slot to +${ACTIVE_INFUSION_MIN_GEAR_LEVEL}` : !anchorUnlocked ? `Select a +${ACTIVE_INFUSION_MIN_GEAR_LEVEL} anchor slot` : canInstallInfusion ? "Spend coins · autosave" : infusionEquipped ? "Applies next encounter" : "Collect required boss coins"}</small></FocusButton>}
|
||||
{workshopMode === "upgrade" ? <FocusButton id="upgrade" focusedId={controller.focusedId} focus={controller.focus} className="gear-upgrade-action" disabled={!canUpgrade} onClick={upgrade}><span>{slot.level >= MAX_GEAR_LEVEL ? "Maximum rank" : `Upgrade to +${slot.level + 1}`}</span><small>{canUpgrade ? "Spend group drops · autosave" : "Collect required group drops"}</small></FocusButton> : <FocusButton id="install-infusion" focusedId={controller.focusedId} focus={controller.focus} className="gear-upgrade-action" disabled={!canInstallInfusion} onClick={installInfusion}><span>{infusionEquipped ? "Infusion equipped" : `Install ${selectedInfusion.name}`}</span><small>{!activeUnlocked ? `Raise any ${GEAR_OWNER_LABELS[selectedOwnerId]} slot to +${ACTIVE_INFUSION_MIN_GEAR_LEVEL}` : !anchorUnlocked ? `Select a +${ACTIVE_INFUSION_MIN_GEAR_LEVEL} anchor slot` : canInstallInfusion ? "Spend group drops · autosave" : infusionEquipped ? "Applies next encounter" : "Collect required group drops"}</small></FocusButton>}
|
||||
<div className="front-notice is-lower">{notice || "Gear changes save locally and apply when next encounter starts."}</div>
|
||||
</FrontSurface>
|
||||
}
|
||||
@@ -664,6 +664,8 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
|
||||
const selectDifficulty = useFrontendStore((state) => state.selectDifficulty);
|
||||
const navigate = useFrontendStore((state) => state.navigate);
|
||||
const [message, setMessage] = useState("");
|
||||
const bossPageSize = 12;
|
||||
const [bossPage, setBossPage] = useState(() => Math.max(0, Math.floor(AVAILABLE_BOSS_IDS.indexOf(selectedBossId) / bossPageSize)));
|
||||
const mode = MODE_COPY[modeId];
|
||||
const healer = hunter ? HEALER_CLASSES[hunter.activeClassId] : HEALER_CLASSES.priest;
|
||||
const progress = hunter?.healers[hunter.activeClassId];
|
||||
@@ -671,36 +673,46 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
|
||||
const selectedDifficulty = DIFFICULTY_BY_SLUG[selectedDifficultySlug];
|
||||
const isPve = modeId === "roguelike-pve";
|
||||
const isDungeon = modeId === "dungeons";
|
||||
const bossGridRows = Math.min(8, Math.ceil(BOSS_ORDER.length / 3));
|
||||
const bossPageCount = Math.ceil(AVAILABLE_BOSS_IDS.length / bossPageSize);
|
||||
const visibleBossIds = AVAILABLE_BOSS_IDS.slice(bossPage * bossPageSize, (bossPage + 1) * bossPageSize);
|
||||
const bossGridRows = Math.ceil(visibleBossIds.length / 3);
|
||||
const bossGridColumns = Math.ceil(visibleBossIds.length / bossGridRows);
|
||||
const changeBossPage = (nextPage: number) => {
|
||||
const page = Math.max(0, Math.min(bossPageCount - 1, nextPage));
|
||||
setBossPage(page);
|
||||
selectBoss(AVAILABLE_BOSS_IDS[page * bossPageSize]);
|
||||
};
|
||||
const launch = () => {
|
||||
if (isPve) return onLaunch(selectRandomBossPair(), "initiate");
|
||||
if (isDungeon) return onLaunch([selectedBossId], selectedDifficultySlug);
|
||||
setMessage("Online matchmaking connects here when game server is configured.");
|
||||
};
|
||||
const actions = useMemo<MenuAction[]>(() => [
|
||||
...(isDungeon ? BOSS_ORDER.map((bossId, index) => {
|
||||
...(isDungeon ? visibleBossIds.map((bossId, index) => {
|
||||
const column = Math.floor(index / bossGridRows);
|
||||
const row = index % bossGridRows;
|
||||
const neighborInColumn = (targetColumn: number) => {
|
||||
const columnStart = targetColumn * bossGridRows;
|
||||
if (columnStart >= BOSS_ORDER.length || targetColumn < 0) return undefined;
|
||||
const columnEnd = Math.min(columnStart + bossGridRows, BOSS_ORDER.length) - 1;
|
||||
return `boss-${BOSS_ORDER[Math.min(columnStart + row, columnEnd)]}`;
|
||||
if (columnStart >= visibleBossIds.length || targetColumn < 0) return undefined;
|
||||
const columnEnd = Math.min(columnStart + bossGridRows, visibleBossIds.length) - 1;
|
||||
return `boss-${visibleBossIds[Math.min(columnStart + row, columnEnd)]}`;
|
||||
};
|
||||
|
||||
return {
|
||||
id: `boss-${bossId}`,
|
||||
run: () => selectBoss(bossId),
|
||||
neighbors: {
|
||||
up: row > 0 ? `boss-${BOSS_ORDER[index - 1]}` : "back",
|
||||
down: index + 1 < Math.min((column + 1) * bossGridRows, BOSS_ORDER.length)
|
||||
? `boss-${BOSS_ORDER[index + 1]}`
|
||||
up: row > 0 ? `boss-${visibleBossIds[index - 1]}` : "back",
|
||||
down: index + 1 < Math.min((column + 1) * bossGridRows, visibleBossIds.length)
|
||||
? `boss-${visibleBossIds[index + 1]}`
|
||||
: `difficulty-${DIFFICULTIES[0].slug}`,
|
||||
left: neighborInColumn(column - 1),
|
||||
right: neighborInColumn(column + 1),
|
||||
left: neighborInColumn(column - 1) ?? (bossPage > 0 ? "boss-page-prev" : undefined),
|
||||
right: neighborInColumn(column + 1) ?? (bossPage < bossPageCount - 1 ? "boss-page-next" : undefined),
|
||||
},
|
||||
};
|
||||
}) : []),
|
||||
...(isDungeon && bossPage > 0 ? [{ id: "boss-page-prev", run: () => changeBossPage(bossPage - 1), neighbors: { right: `boss-${visibleBossIds[0]}`, down: `boss-${visibleBossIds[0]}`, up: "back" } }] : []),
|
||||
...(isDungeon && bossPage < bossPageCount - 1 ? [{ id: "boss-page-next", run: () => changeBossPage(bossPage + 1), neighbors: { left: `boss-${visibleBossIds[visibleBossIds.length - 1]}`, down: `boss-${visibleBossIds[0]}`, up: "back" } }] : []),
|
||||
...(isDungeon ? DIFFICULTIES.map((difficulty, index) => ({
|
||||
id: `difficulty-${difficulty.slug}`,
|
||||
run: () => selectDifficulty(difficulty.slug),
|
||||
@@ -712,8 +724,8 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
|
||||
},
|
||||
})) : []),
|
||||
{ id: "launch", run: launch, neighbors: isDungeon ? { up: `difficulty-${DIFFICULTIES[DIFFICULTIES.length - 1].slug}` } : { up: "back" } },
|
||||
{ id: "back", run: () => navigate("home"), neighbors: isDungeon ? { down: `boss-${BOSS_ORDER[0]}` } : { down: "launch" } },
|
||||
], [isDungeon, isPve, modeId, navigate, onLaunch, selectBoss, selectDifficulty, selectedBossId, selectedDifficultySlug]);
|
||||
{ id: "back", run: () => navigate("home"), neighbors: isDungeon ? { down: `boss-${AVAILABLE_BOSS_IDS[0]}` } : { down: "launch" } },
|
||||
], [bossGridRows, bossPage, bossPageCount, isDungeon, isPve, modeId, navigate, onLaunch, selectBoss, selectDifficulty, selectedBossId, selectedDifficultySlug, visibleBossIds]);
|
||||
const controller = useMenuController(actions, { onBack: () => navigate("home") });
|
||||
const launchLabel = isPve ? "Begin randomized run" : isDungeon ? `Challenge ${selectedBoss.name}` : "Enter matchmaking";
|
||||
const contextRules = isDungeon
|
||||
@@ -741,10 +753,17 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
|
||||
{!isDungeon && <div className="mode-hero"><span>{mode.eyebrow}</span><h2>{mode.title}</h2><p>{mode.description}</p><b>{mode.detail}</b></div>}
|
||||
{isDungeon && (
|
||||
<div className="boss-picker" aria-label="Choose boss encounter">
|
||||
<span>Choose encounter</span>
|
||||
<div className="boss-choice-grid" style={{ "--boss-grid-rows": bossGridRows } as React.CSSProperties}>
|
||||
{BOSS_ORDER.map((bossId) => {
|
||||
<div className="boss-picker-heading">
|
||||
<span>Choose encounter · Page {bossPage + 1}/{bossPageCount}</span>
|
||||
<div>
|
||||
<FocusButton id="boss-page-prev" focusedId={controller.focusedId} focus={controller.focus} disabled={bossPage === 0} onClick={() => changeBossPage(bossPage - 1)}>◀ Previous</FocusButton>
|
||||
<FocusButton id="boss-page-next" focusedId={controller.focusedId} focus={controller.focus} disabled={bossPage === bossPageCount - 1} onClick={() => changeBossPage(bossPage + 1)}>Next ▶</FocusButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="boss-choice-grid" style={{ "--boss-grid-rows": bossGridRows, "--boss-grid-columns": bossGridColumns } as React.CSSProperties}>
|
||||
{visibleBossIds.map((bossId) => {
|
||||
const boss = BOSS_DEFINITIONS[bossId];
|
||||
const group = BOSS_GROUP_BY_ID[boss.groupId];
|
||||
return (
|
||||
<FocusButton
|
||||
key={bossId}
|
||||
@@ -756,7 +775,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
|
||||
aria-pressed={selectedBossId === bossId}
|
||||
onClick={() => selectBoss(bossId)}
|
||||
>
|
||||
<i>{boss.icon}</i><span><strong>{boss.name}</strong><small>{boss.mechanics.join(" · ")}</small></span><b>{selectedBossId === bossId ? "✓" : ""}</b>
|
||||
<i>{boss.icon}</i><span><strong>{boss.name}</strong><small>Group {group.letter} · {group.name} · {boss.mechanics[0]}</small></span><b>{selectedBossId === bossId ? "✓" : ""}</b>
|
||||
</FocusButton>
|
||||
);
|
||||
})}
|
||||
@@ -778,7 +797,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
|
||||
<header className="context-header"><span>Run preparation</span><b>{isDungeon ? selectedDifficulty.name.toUpperCase() : mode.status.toUpperCase()}</b></header>
|
||||
{contextRules.map(([title, copy], index) => <div className="mode-rule" key={title}><i>0{index + 1}</i><span><strong>{title}</strong><small>{copy}</small></span></div>)}
|
||||
<div className="mode-loadout"><span>Equipped role</span><b>{healer.specialization} · Level {progress?.level ?? 1}</b><small>6 abilities · {progress?.inventory.length ?? 0} class items · Controller ready</small></div>
|
||||
{isDungeon && <div className="mode-loot-preview"><span>Guaranteed reward</span><b>{bossCoinDrop(selectedBossId, selectedDifficultySlug).name}</b><small>1–3 coins · {selectedDifficulty.rarity} · Pet chance 1 in 500</small></div>}
|
||||
{isDungeon && <div className="mode-loot-preview"><span>Guaranteed reward</span><b>{bossGroupDrop(selectedBossId, selectedDifficultySlug).name}</b><small>1–3 group drops · {selectedDifficulty.rarity} · Pet chance 1 in 500</small></div>}
|
||||
</FrontSurface>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user