@@ -37,6 +37,7 @@ import {
import { requestDisplaySurface } from "../platform/displayRouting" ;
import { onlineRepository , type LeaderboardResult } from "../frontend/onlineRepository" ;
import { DualDisplayFrame } from "./DualDisplayFrame" ;
import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs" ;
const BossTrophyPortrait = lazy ( ( ) = > import ( "./BossTrophyPortrait" ) . then ( ( module ) = > ( { default : module . BossTrophyPortrait } ) ) ) ;
@@ -76,7 +77,7 @@ function BrandMark({ compact = false }: { compact?: boolean }) {
}
function ControllerLegend ( { back = false } : { back? : boolean } ) {
return < div className = "controller-legend" > < span > < b > A < / b > Select < / span > { back && < span > < b > B < / b > Back < / span > } < span > < b > + < / b > Navigate < / span > < / div > ;
return < div className = "controller-legend" > < span > < b > { DEFAULT_CONTROLLER_GLYPHS . confirm } < / b > Select < / span > { back && < span > < b > { DEFAULT_CONTROLLER_GLYPHS . back } < / b > Back < / span > } < span > < b > + < / b > Navigate < / span > < / div > ;
}
function LoginScreen() {
@@ -335,7 +336,7 @@ function SaveScreen() {
{ selected . online && < div className = "online-record" > < span > < b > ONLINE < / b > { selected . online . hunterName } < / span > < time > { formatSaveTimestamp ( selected . online . updatedAt ) } < / time > < / div > }
< div className = "save-actions" >
< FocusButton id = { hasLocal ? "play" : "create" } focusedId = { controller . focusedId } focus = { controller . focus } className = "front-primary" onClick = { ( ) = > hasLocal ? playSlot ( selectedSlotId ) : openCreation ( ) } >
{ hasLocal ? "Continue offline save" : "Create new hunter" } < small > A < / small >
{ hasLocal ? "Continue offline save" : "Create new hunter" } < small > { DEFAULT_CONTROLLER_GLYPHS . confirm } < / small >
< / FocusButton >
< div className = "sync-actions" >
< FocusButton id = "upload" focusedId = { controller . focusedId } focus = { controller . focus } disabled = { ! hasLocal || ! accountId } onClick = { ( ) = > uploadSlot ( selectedSlotId ) } > ↑ Sync offline to server < / FocusButton >
@@ -399,7 +400,6 @@ function HomeScreen() {
top = {
< FrontSurface className = "home-surface" ariaLabel = "Main menu" >
< header className = "home-header" > < BrandMark compact / > < span > Welcome back , < b > { hunter . hunterName } < / b > < / span > < i > { accountId ? "● SYNC READY" : "○ OFFLINE" } < / i > < / header >
< div className = "home-title" > < span > Choose your hunt < / span > < h1 > Where are you needed ? < / h1 > < / div >
< div className = "mode-grid" >
{ HOME_MODES . map ( ( mode ) = > (
< FocusButton key = { mode . id } id = { mode . id } focusedId = { controller . focusedId } focus = { controller . focus } className = "mode-card" onClick = { ( ) = > selectMode ( mode . id ) } >
@@ -441,6 +441,8 @@ function HomeScreen() {
) ;
}
type ProfileStatId = BossId | "roguelike" | "rogue-trials-endless" ;
function ProfileScreen() {
const hunter = useActiveHunter ( ) ;
const accountId = useFrontendStore ( ( state ) = > state . accountId ) ;
@@ -449,11 +451,11 @@ function ProfileScreen() {
const [ groupId , setGroupId ] = useState ( collections [ 0 ] ? . groupId ? ? "" ) ;
const [ collectionView , setCollectionView ] = useState < "loot" | "trophies" | "stats" > ( "trophies" ) ;
const collection = collections . find ( ( group ) = > group . groupId === groupId ) ? ? collections [ 0 ] ;
const [ selectedStat , setSelectedStat ] = useState < BossId | "roguelike" > ( "roguelike" ) ;
const [ selectedStat , setSelectedStat ] = useState < ProfileStatId > ( "roguelike" ) ;
const [ leaderboard , setLeaderboard ] = useState < LeaderboardResult | null > ( null ) ;
const [ leaderboardStatus , setLeaderboardStatus ] = useState ( "" ) ;
useEffect ( ( ) = > {
if ( selectedStat === "roguelike" || collection ? . bosses . some ( ( boss ) = > boss . bossId === selectedStat ) ) return ;
if ( selectedStat === "roguelike" || selectedStat === "rogue-trials-endless" || collection ? . bosses . some ( ( boss ) = > boss . bossId === selectedStat ) ) return ;
setSelectedStat ( collection ? . bosses [ 0 ] ? . bossId ? ? "roguelike" ) ;
} , [ collection , selectedStat ] ) ;
useEffect ( ( ) = > {
@@ -467,7 +469,9 @@ function ProfileScreen() {
setLeaderboardStatus ( "Loading overall rankings…" ) ;
const request = selectedStat === "roguelike"
? onlineRepository . roguelikeLeaderboard ( hunter . slotId )
: onlineRepository . bossLeaderboard ( selectedStat , hunter . slotId ) ;
: selectedStat === "rogue-trials-endless"
? onlineRepository . rogueTrialsEndlessLeaderboard ( hunter . slotId )
: onlineRepository . bossLeaderboard ( selectedStat , hunter . slotId ) ;
void request . then ( ( result ) = > {
if ( cancelled ) return ;
setLeaderboard ( result ) ;
@@ -484,12 +488,13 @@ function ProfileScreen() {
{ id : "view-stats" , run : ( ) = > setCollectionView ( "stats" ) , neighbors : { left : "view-trophies" , right : "view-loot" , down : collectionView === "stats" ? "stat-roguelike" : undefined } } ,
{ id : "view-loot" , run : ( ) = > setCollectionView ( "loot" ) , neighbors : { left : "view-stats" } } ,
. . . ( collectionView === "stats" ? [
{ id : "stat-roguelike" , run : ( ) = > setSelectedStat ( "roguelike" ) , neighbors : { up : "view-stats" , down : ` stat- ${ collection . bosses [ 0 ] . bossId } ` } } ,
{ id : "stat-roguelike" , run : ( ) = > setSelectedStat ( "roguelike" ) , neighbors : { up : "view-stats" , down : "stat-rogue-trials-endless" } } ,
{ id : "stat-rogue-trials-endless" , run : ( ) = > setSelectedStat ( "rogue-trials-endless" ) , neighbors : { up : "stat-roguelike" , down : ` stat- ${ collection . bosses [ 0 ] . bossId } ` } } ,
. . . collection . bosses . map ( ( boss , index ) = > ( {
id : ` stat- ${ boss . bossId } ` ,
run : ( ) = > setSelectedStat ( boss . bossId ) ,
neighbors : {
up : index === 0 ? "stat-roguelike " : ` stat- ${ collection . bosses [ index - 1 ] . bossId } ` ,
up : index === 0 ? "stat-rogue-trials-endless " : ` stat- ${ collection . bosses [ index - 1 ] . bossId } ` ,
down : index === collection . bosses . length - 1 ? ` group- ${ collection . groupId } ` : ` stat- ${ collection . bosses [ index + 1 ] . bossId } ` ,
} ,
} ) ) ,
@@ -503,6 +508,16 @@ function ProfileScreen() {
const activeProgress = hunter . healers [ hunter . activeClassId ] ;
const earned = collection . drops . filter ( ( drop ) = > drop . count > 0 ) . length ;
const trophiesEarned = collection . bosses . filter ( ( boss ) = > boss . pet . count > 0 ) . length ;
const selectedStatValue = selectedStat === "roguelike"
? hunter.stats.highestRoguelikeRound
: selectedStat === "rogue-trials-endless"
? hunter.stats.highestRogueTrialsEndlessKills
: hunter.stats.bossKills [ selectedStat ] ? ? 0 ;
const selectedStatLabel = selectedStat === "roguelike"
? "Roguelike rounds"
: selectedStat === "rogue-trials-endless"
? "Rogue Trials endless kills"
: BOSS_DEFINITIONS [ selectedStat ] . name ;
return (
< DualDisplayFrame
@@ -512,7 +527,7 @@ function ProfileScreen() {
< FocusButton id = "view-trophies" focusedId = { controller . focusedId } focus = { controller . focus } className = { collectionView === "trophies" ? "is-selected" : "" } role = "tab" aria-selected = { collectionView === "trophies" } onClick = { ( ) = > setCollectionView ( "trophies" ) } > Trophy Case < / FocusButton >
< FocusButton id = "view-stats" focusedId = { controller . focusedId } focus = { controller . focus } className = { collectionView === "stats" ? "is-selected" : "" } role = "tab" aria-selected = { collectionView === "stats" } onClick = { ( ) = > setCollectionView ( "stats" ) } > Boss Stats < / FocusButton >
< FocusButton id = "view-loot" focusedId = { controller . focusedId } focus = { controller . focus } className = { collectionView === "loot" ? "is-selected" : "" } role = "tab" aria-selected = { collectionView === "loot" } onClick = { ( ) = > setCollectionView ( "loot" ) } > Group Loot < / FocusButton >
< / div > < FocusButton id = "back" focusedId = { controller . focusedId } focus = { controller . focus } className = "header-back" onClick = { ( ) = > navigate ( "home" ) } > B · Back < / FocusButton > < / header >
< / div > < FocusButton id = "back" focusedId = { controller . focusedId } focus = { controller . focus } className = "header-back" onClick = { ( ) = > navigate ( "home" ) } > { DEFAULT_CONTROLLER_GLYPHS . back } · Back < / FocusButton > < / header >
{ collectionView === "loot" ? < >
< 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" >
@@ -540,18 +555,19 @@ function ProfileScreen() {
< / div >
< div className = "collection-note trophy-note" > < i > ♛ < / i > < span > < strong > Each guardian keeps its own trophy . < / strong > < small > Defeat that boss for a 1 in 500 pet roll . < / small > < / span > < / div >
< / > : < >
< div className = "collection-heading boss-stats-heading" > < span > < small > Lifetime records · Overall leaderboards < / small > < h2 > Boss Stats < / h2 > < / span > < b > Highest roguelike round { hunter . stats . highestRoguelikeRound } < / b > < / div >
< div className = "collection-heading boss-stats-heading" > < span > < small > Lifetime records · Overall leaderboards < / small > < h2 > Boss Stats < / h2 > < / span > < b > Endless best { hunter . stats . highestRogueTrialsEndlessKills } kills < / b > < / div >
< div className = "boss-stats-layout" >
< section className = "boss-stat-selector" aria-label = "Boss statistic selection" >
< FocusButton id = "stat-roguelike" focusedId = { controller . focusedId } focus = { controller . focus } className = { selectedStat === "roguelike" ? "is-selected" : "" } onClick = { ( ) = > setSelectedStat ( "roguelike" ) } > < i > ∞ < / i > < span > < strong > Roguelike < / strong > < small > Highest round before defeat < / small > < / span > < b > { hunter . stats . highestRoguelikeRound } < / b > < / FocusButton >
< FocusButton id = "stat-rogue-trials-endless" focusedId = { controller . focusedId } focus = { controller . focus } className = { selectedStat === "rogue-trials-endless" ? "is-selected" : "" } onClick = { ( ) = > setSelectedStat ( "rogue-trials-endless" ) } > < i > Ⅲ < / i > < span > < strong > Trials Endless < / strong > < small > Most bosses in one run < / small > < / span > < b > { hunter . stats . highestRogueTrialsEndlessKills } < / b > < / FocusButton >
{ collection . bosses . map ( ( boss ) = > < FocusButton key = { boss . bossId } id = { ` stat- ${ boss . bossId } ` } focusedId = { controller . focusedId } focus = { controller . focus } className = { selectedStat === boss . bossId ? "is-selected" : "" } onClick = { ( ) = > setSelectedStat ( boss . bossId ) } > < i > { BOSS_DEFINITIONS [ boss . bossId ] . icon } < / i > < span > < strong > { boss . bossName } < / strong > < small > Lifetime boss kills < / small > < / span > < b > { boss . kills } < / b > < / FocusButton > ) }
< / section >
< section className = "leaderboard-panel" aria-label = "Overall leaderboard" >
< header > < span > < small > Overall Top 5 < / small > < strong > { selectedStat === "roguelike" ? "Roguelike rounds" : BOSS_DEFINITIONS [ selectedStat ] . name } < / strong > < / span > < b > { selectedStat === "roguelike" ? ` ${ hunter . stats . highestRoguelikeRound } best ` : ` ${ hunter . stats . bossKills [ selectedStat ] ? ? 0 } kills` } < / b > < / header >
< header > < span > < small > Overall Top 5 < / small > < strong > { selectedStatLabel } < / strong > < / span > < b > { selectedStatValue } { selectedStat === "roguelike" ? "round" : " kills" } < / b > < / header >
{ leaderboardStatus ? < div className = "leaderboard-status" > { leaderboardStatus } < / div > : < div className = "leaderboard-rows" >
{ leaderboard ? . top . length ? leaderboard . top . map ( ( entry ) = > < div key = { ` ${ entry . username } - ${ entry . slotId } ` } className = { entry . username === accountId && entry . slotId === hunter . slotId ? "is-you" : "" } > < b > # { entry . rank } < / b > < span > < strong > { entry . hunterName } < / strong > < small > { entry . username } < / small > < / span > < em > { entry . value } < / em > < / div > ) : < div className = "leaderboard-empty" > No ranked hunters yet . < / div > }
< / div > }
< div className = "leaderboard-self" > < b > { leaderboard ? . current ? ` # ${ leaderboard . current . rank } ` : "—" } < / b > < span > < strong > Your rank · { hunter . hunterName } < / strong > < small > { accountId ? ? "Offline hunter" } < / small > < / span > < em > { selectedStat === "roguelike" ? hunter.stats.highestRoguelikeRound : hunter.stats.bossKills [ selectedStat ] ? ? 0 } < / em > < / div >
< div className = "leaderboard-self" > < b > { leaderboard ? . current ? ` # ${ leaderboard . current . rank } ` : "—" } < / b > < span > < strong > Your rank · { hunter . hunterName } < / strong > < small > { accountId ? ? "Offline hunter" } < / small > < / span > < em > { selectedStatValue } < / em > < / div >
< / section >
< / div >
< div className = "collection-note trophy-note" > < i > ◆ < / i > < span > < strong > Rankings update with server saves . < / strong > < small > Top five always shown ; your row stays visible at any rank . < / small > < / span > < / div >
@@ -567,6 +583,7 @@ 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 >
< span > < small > Highest roguelike round < / small > < strong > { hunter . stats . highestRoguelikeRound } < / strong > < / span >
< span > < small > Endless best < / small > < strong > { hunter . stats . highestRogueTrialsEndlessKills } < / strong > < / span >
< / div >
< 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 ) } >
@@ -701,7 +718,7 @@ function GearScreen() {
< DualDisplayFrame
top = {
< FrontSurface className = "gear-surface" ariaLabel = "Gear upgrade workshop" >
< 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 >
< 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" ) } > { DEFAULT_CONTROLLER_GLYPHS . back } · Back < / FocusButton > < / header >
< div className = "gear-workshop-layout" >
< section className = "gear-owner-list" aria-label = "Party gear owners" >
{ GEAR_OWNER_ORDER . map ( ( ownerId ) = > {
@@ -764,7 +781,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 group drops · autosave" : "Collect required group drops" } < / small > < / FocusButton > : passiveContext ? < div className = "gear-passive-context-action" > < span > { hunter . gearProgress [ selectedOwnerId ] . passiveInfusionId === selectedPassive . id ? "Passive equipped" : "A · Equip selected passive" } < / span > < small > Applies at rank 1 next encounter . < / small > < / div > : < 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 > }
{ 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 > : passiveContext ? < div className = "gear-passive-context-action" > < span > { hunter . gearProgress [ selectedOwnerId ] . passiveInfusionId === selectedPassive . id ? "Passive equipped" : ` ${ DEFAULT_CONTROLLER_GLYPHS . confirm } · Equip selected passive ` } < / span > < small > Applies at rank 1 next encounter . < / small > < / div > : < 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 >
}
@@ -795,7 +812,7 @@ function SettingsScreen() {
< DualDisplayFrame
top = {
< FrontSurface className = "settings-surface" ariaLabel = "Settings" >
< header className = "front-screen-header" > < BrandMark compact / > < div > < span > Field configuration < / span > < h1 > Settings < / h1 > < / 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 > Field configuration < / span > < h1 > Settings < / h1 > < / div > < FocusButton id = "back" focusedId = { controller . focusedId } focus = { controller . focus } className = "header-back" onClick = { ( ) = > navigate ( "home" ) } > { DEFAULT_CONTROLLER_GLYPHS . back } · Back < / FocusButton > < / header >
< div className = "settings-layout" >
< section > < span className = "settings-section-title" > Audio < / span > < div className = "volume-setting" > < span > < strong > Master volume < / strong > < small > All music , effects , and voice < / small > < / span > < div > < FocusButton id = "volume-down" focusedId = { controller . focusedId } focus = { controller . focus } onClick = { ( ) = > updateSetting ( "masterVolume" , Math . max ( 0 , settings . masterVolume - 10 ) ) } > − < / FocusButton > < b > { settings . masterVolume } % < / b > < FocusButton id = "volume-up" focusedId = { controller . focusedId } focus = { controller . focus } onClick = { ( ) = > updateSetting ( "masterVolume" , Math . min ( 100 , settings . masterVolume + 10 ) ) } > + < / FocusButton > < / div > < i > < em style = { { width : ` ${ settings . masterVolume } % ` } } / > < / i > < / div > < / section >
< section > < span className = "settings-section-title" > Display & accessibility < / span > < SettingToggle id = "motion" label = "Reduced motion" copy = "Limit non-essential UI movement" value = { settings . reducedMotion } focusedId = { controller . focusedId } focus = { controller . focus } onClick = { ( ) = > updateSetting ( "reducedMotion" , ! settings . reducedMotion ) } / > < SettingToggle id = "numbers" label = "Damage numbers" copy = "Show combat values over units" value = { settings . damageNumbers } focusedId = { controller . focusedId } focus = { controller . focus } onClick = { ( ) = > updateSetting ( "damageNumbers" , ! settings . damageNumbers ) } / > < SettingToggle id = "text" label = "Large interface text" copy = "Increase menu and tactical labels" value = { settings . largeText } focusedId = { controller . focusedId } focus = { controller . focus } onClick = { ( ) = > updateSetting ( "largeText" , ! settings . largeText ) } / > < / section >
@@ -808,9 +825,9 @@ function SettingsScreen() {
< header className = "context-header" > < span > Controller < / span > < b > BUILT - IN THOR PAD < / b > < / header >
< div className = "controller-map" >
< div className = "pad-diagram" > < i > ↑ < / i > < span > ← < b > ● < / b > → < / span > < i > ↓ < / i > < / div >
< div className = "face-diagram" > < i className = "y" > Y < / i > < span > < i className = "x" > X < / i > < b > ● < / b > < i className = "b" > B < / i > < / span > < i className = "a " > A < / i > < / div >
< div className = "face-diagram" > < i className = "triangle" > { DEFAULT_CONTROLLER_GLYPHS . faceTop } < / i > < span > < i className = "square" > { DEFAULT_CONTROLLER_GLYPHS . faceLeft } < / i > < b > ● < / b > < i className = "circle" > { DEFAULT_CONTROLLER_GLYPHS . faceRight } < / i > < / span > < i className = "cross " > { DEFAULT_CONTROLLER_GLYPHS . faceBottom } < / i > < / div >
< / div >
< div className = "mapping-list" > < span > < b > A < / b > Confirm / cast Purify < / span > < span > < b > B < / b > Back / cast Shield < / span > < span > < b > D - Pad < / b > Navigate / target party < / span > < span > < b > Right stick < / b > Rotate camera < / span > < span > < b > Start < / b > Pause / menu < / span > < / div >
< div className = "mapping-list" > < span > < b > { DEFAULT_CONTROLLER_GLYPHS . confirm } < / b > Confirm / cast Purify < / span > < span > < b > { DEFAULT_CONTROLLER_GLYPHS . back } < / b > Back / cast Shield < / span > < span > < b > D - Pad < / b > Navigate / target party < / span > < span > < b > Right stick < / b > Rotate camera < / span > < span > < b > { DEFAULT_CONTROLLER_GLYPHS . start } < / b > Pause / menu < / span > < / div >
< div className = "control-assurance" > < i > ✓ < / i > < span > < strong > No click - to - focus required < / strong > < small > Controller input routes through app - level actions . < / small > < / span > < / div >
< / FrontSurface >
}
@@ -908,7 +925,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
? [
[ "Four dual rounds" , "Clear four randomized pairs while drafting one stacking buff after each win." ] ,
[ "Unseen trio finale" , "Round 5 selects three bosses that have not appeared earlier in that run." ] ,
[ "Trial victory" , "Defeat all three final bosses together to complete Rogue Trials ."] ,
[ "Endless choice" , "After the trio falls, quit with the clear or continue while every dead boss is replaced ."] ,
]
: isPve
? [
@@ -925,7 +942,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
< DualDisplayFrame
top = {
< FrontSurface className = { ` mode-surface mode- ${ modeId } ` } ariaLabel = { ` ${ mode . title } details ` } >
< header className = "front-screen-header" > < BrandMark compact / > < div > < span > Game mode < / span > < h1 > { mode . title } < / h1 > < / 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 > Game mode < / span > < h1 > { mode . title } < / h1 > < / div > < FocusButton id = "back" focusedId = { controller . focusedId } focus = { controller . focus } className = "header-back" onClick = { ( ) = > navigate ( "home" ) } > { DEFAULT_CONTROLLER_GLYPHS . back } · Back < / FocusButton > < / header >
{ ! 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" >
@@ -973,7 +990,7 @@ function ModeScreen({ onLaunch }: { onLaunch: (bossIds: readonly BossId[], diffi
{ DIFFICULTIES . map ( ( difficulty ) = > < FocusButton key = { difficulty . slug } id = { ` difficulty- ${ difficulty . slug } ` } focusedId = { controller . focusedId } focus = { controller . focus } className = { difficulty . slug === selectedDifficultySlug ? "is-selected" : "" } onClick = { ( ) = > selectDifficulty ( difficulty . slug ) } > < strong > { difficulty . name } < / strong > < small > iLvl { difficulty . itemLevel } < / small > < / FocusButton > ) }
< / div >
) }
< FocusButton id = "launch" focusedId = { controller . focusedId } focus = { controller . focus } className = "mode-launch" onClick = { launch } > < span > { launchLabel } < / span > < small > { mode . status } · A < / small > < / FocusButton >
< FocusButton id = "launch" focusedId = { controller . focusedId } focus = { controller . focus } className = "mode-launch" onClick = { launch } > < span > { launchLabel } < / span > < small > { mode . status } · { DEFAULT_CONTROLLER_GLYPHS . confirm } < / small > < / FocusButton >
{ message && < div className = "front-notice" > { message } < / div > }
< / FrontSurface >
}