Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
dd60e13
Build scenario analytics dashboard
Emissaryx Jul 29, 2026
dbdc4d8
Improve scenario rankings and match details
Emissaryx Jul 29, 2026
e37963e
Support direct Cloudflare page links
Emissaryx Jul 29, 2026
84f99a3
Simplify scenario filters and expand leaderboard controls
Emissaryx Jul 29, 2026
0d800a3
Unify scenario filters and add time ranges
Emissaryx Jul 29, 2026
c20be00
Stop time filters from refetching continuously
Emissaryx Jul 30, 2026
9e38719
Load useful batches for scenario time ranges
Emissaryx Jul 30, 2026
d32f30d
Calculate leaderboards from complete time windows
Emissaryx Jul 30, 2026
1f253de
Respect scenario API page-size limit
Emissaryx Jul 30, 2026
384a969
Show realm win rates above balance bar
Emissaryx Jul 30, 2026
8b0f954
Place win rates at realm balance split
Emissaryx Jul 30, 2026
ba64082
Remove win-rate label backgrounds
Emissaryx Jul 30, 2026
f6b0770
Restore complete stylesheet
Emissaryx Jul 30, 2026
0ac2b30
Improve scenario filter performance and clarity
Emissaryx Jul 30, 2026
ee49e66
Use the last hour as the default scenario window
Emissaryx Jul 30, 2026
61b8cb5
Add scenario activity breakdown
Emissaryx Jul 30, 2026
6577dfd
Fit standout tables without desktop scrolling
Emissaryx Jul 30, 2026
2e041e1
Add sortable scenario balance analysis
Emissaryx Jul 30, 2026
bbbab1f
Keep profile scenario history outside dashboard default
Emissaryx Jul 30, 2026
0f4f8d9
Add totals average and median standout views
Emissaryx Jul 30, 2026
b277440
Add character scenario relationships
Emissaryx Jul 30, 2026
982874b
Clarify selected and active scenario dates
Emissaryx Jul 30, 2026
109c56a
Scenario page: fix share-link drift, align column order, sort by real…
Emissaryx Aug 1, 2026
2a98495
Scenario page styling: balance/score colors, Bulma select widths
Emissaryx Aug 1, 2026
0335191
Standouts: default Average/Median to a 10+ scenario minimum
Emissaryx Aug 1, 2026
68b5acb
Fix misaligned dropdown arrow on compact Bulma selects
Emissaryx Aug 1, 2026
0826f6f
Scenario scoreboard: group by role, show % of team per stat
Emissaryx Aug 1, 2026
7f4c37c
Add scoreboard team/role section styles
Emissaryx Aug 1, 2026
f677c2b
Remove useless default prop value (lint fix)
Emissaryx Aug 1, 2026
e1da98f
Remove useless default prop value (lint fix)
Emissaryx Aug 1, 2026
ca2eb20
Remove useless default prop value (lint fix)
Emissaryx Aug 1, 2026
e60aba8
Remove dead commented-out GameObject search row
Emissaryx Aug 1, 2026
a572d9d
Cache scenario window results to avoid redownloading on revisit
Emissaryx Aug 2, 2026
9196265
Bucket the relative time window to 5 minutes so cache-first actually …
Emissaryx Aug 2, 2026
07a802c
Add navigation links for items, quests, creatures, instances, and sto…
Emissaryx Aug 2, 2026
d7a7a74
Reorder main nav links alphabetically
Emissaryx Aug 2, 2026
c20f975
Wrap select elements in divs for styling
Emissaryx Aug 2, 2026
e37c175
Refactor career selection to use a div wrapper
Emissaryx Aug 2, 2026
448615c
Refactor select elements for item filters
Emissaryx Aug 2, 2026
454e4d2
Add MainNav component for navigation links
Emissaryx Aug 2, 2026
874de3c
Add MainNav component to App routing
Emissaryx Aug 2, 2026
b24d13b
Clean up Home component by removing imports and links
Emissaryx Aug 2, 2026
7aa4aa9
Add generic filter-grid and filter-summary-bar styles
Emissaryx Aug 2, 2026
490c83e
Fix Instances breadcrumb and switch filters to filter-grid
Emissaryx Aug 2, 2026
74000be
Switch Creatures filter to filter-grid
Emissaryx Aug 2, 2026
ca0fe15
Refactor search box layout in Quests component
Emissaryx Aug 2, 2026
e778912
Refactor Items page layout and search filters
Emissaryx Aug 2, 2026
b8468ac
Refactor SkirmishFilters layout to use filter-grid
Emissaryx Aug 2, 2026
013f470
Refactor career filter layout and improve accessibility
Emissaryx Aug 2, 2026
b89988b
Change container class for Home component
Emissaryx Aug 2, 2026
921fd18
Update MainNav container class for responsiveness
Emissaryx Aug 2, 2026
2920069
Change container class to is-max-widescreen
Emissaryx Aug 2, 2026
bde20fb
Update container class to is-max-widescreen
Emissaryx Aug 2, 2026
9225ada
Change container class to is-max-widescreen
Emissaryx Aug 2, 2026
9099c74
Update container class for SearchGuild component
Emissaryx Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/deploy-cloudflare-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Deploy Cloudflare Pages

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Build site
run: npm run build

- name: Deploy to Cloudflare Pages
run: npx wrangler@4 pages deploy build --project-name=ror-killboard-preview
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"vite": "^8.0.14"
},
"scripts": {
"dev": "vite",
"start": "vite",
"build": "vite build",
"test": "tsc -p . && vite build && oxlint --type-aware src",
Expand Down
1 change: 1 addition & 0 deletions public/_redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* /index.html 200
213 changes: 116 additions & 97 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import './i18n/config';
import React from 'react';
import { Character } from '@/pages/Character';
import { Home } from '@/pages/Home';
import { MainNav } from '@/components/global/MainNav';
import { Kill } from '@/pages/Kill';
import { Guild } from '@/pages/Guild';
import { Search } from '@/pages/Search';
Expand Down Expand Up @@ -54,103 +55,121 @@ const App = () => {
usePageViews();

return (
<Routes>
<Route path="/" element={<Home tab="players" />} />
<Route path="/guilds" element={<Home tab="guilds" />} />
<Route path="/scenarios" element={<Home tab="scenarios" />} />
<Route path="/kill/:id" element={<Kill />} />
<Route path="/kills" element={<Kills />} />
<Route path="/character/:id" element={<Character tab="kills" />} />
<Route
path="/character/:id/scenarios"
element={<Character tab="scenarios" />}
/>
<Route
path="/character/:id/skirmishes"
element={<Character tab="skirmishes" />}
/>
<Route
path="/character/:id/armory"
element={<Character tab="armory" />}
/>
<Route
path="/character/:playerId1/feud/:playerId2"
element={<PlayerFeudPage />}
/>

<Route path="/creatures" element={<Creatures />} />
<Route path="/creature/:id" element={<Creature />} />
<Route path="/creature/:id/quests" element={<Creature tab="quests" />} />
<Route
path="/creature/:id/vendorItems"
element={<Creature tab="vendorItems" />}
/>
<Route path="/creature/:id/zone/:zoneId" element={<Creature />} />

<Route path="/gameobject/:id" element={<GameObject />} />
<Route path="/creature/:id/quests" element={<GameObject />} />
<Route path="/creature/:id/zone/:zoneId" element={<GameObject />} />

<Route path="/guild/:id" element={<Guild tab="kills" />} />
<Route path="/guild/:id/members" element={<Guild tab="members" />} />
<Route path="/guild/:id/scenarios" element={<Guild tab="scenarios" />} />
<Route
path="/guild/:id/skirmishes"
element={<Guild tab="skirmishes" />}
/>
<Route
path="/guild/:guildId1/feud/:guildId2"
element={<GuildFeudPage />}
/>

<Route path="/quests" element={<Quests />} />
<Route path="/quest/:id" element={<Quest />} />

<Route path="/search/:query" element={<Search />} />
<Route path="/search/guild/:query" element={<SearchGuild />} />

<Route path="/scenario/:id" element={<Scenario tab="scoreboard" />} />
<Route path="/scenario/:id/kills" element={<Scenario tab="kills" />} />
<Route
path="/scenario/:id/skirmishes"
element={<Scenario tab="skirmishes" />}
/>
<Route path="/scenario/:id/map" element={<Scenario tab="map" />} />
<Route path="/skirmishes" element={<Home tab="skirmishes" />} />
<Route path="/skirmish/:id" element={<Skirmish tab="scoreboard" />} />
<Route path="/skirmish/:id/kills" element={<Skirmish tab="kills" />} />
<Route
path="/skirmish/:id/damage/:characterId"
element={<Skirmish tab="characterDamage" />}
/>
<Route path="/skirmish/:id/damage" element={<Skirmish tab="damage" />} />
<Route path="/zone_heatmap/:id" element={<ZoneMap />} />

<Route path="/items" element={<Items />} />
<Route path="/item/:id" element={<Item tab="vendors" />} />
<Route path="/item/:id/purchase" element={<Item tab="purchase" />} />
<Route path="/item/:id/quests" element={<Item tab="quests" />} />

<Route path="/instance-runs" element={<InstanceRuns />} />
<Route path="/instance-run/:id" element={<InstanceRun />} />
<Route
path="/instance-run/:instanceRunId/:id"
element={<InstanceEncounterRun />}
/>

<Route path="/instances" element={<Instances />} />
<Route path="/instance-statistics/:id" element={<InstanceStatistics />} />

<Route path="/storylines" element={<Storylines />} />
<Route path="/storylines/:id" element={<Storyline />} />
<Route path="/storylines/:storylineId/:id" element={<StorylineEntry />} />
<Route
path="/storylines/:storylineId/:storylineEntryId/:id"
element={<StorylineActivity />}
/>

<Route path="/ranked-leaderboard" element={<RankedLeaderboard />} />
</Routes>
<>
<MainNav />
<Routes>
<Route path="/" element={<Home tab="players" />} />
<Route path="/guilds" element={<Home tab="guilds" />} />
<Route path="/scenarios" element={<Home tab="scenarios" />} />
<Route path="/kill/:id" element={<Kill />} />
<Route path="/kills" element={<Kills />} />
<Route path="/character/:id" element={<Character tab="kills" />} />
<Route
path="/character/:id/scenarios"
element={<Character tab="scenarios" />}
/>
<Route
path="/character/:id/skirmishes"
element={<Character tab="skirmishes" />}
/>
<Route
path="/character/:id/armory"
element={<Character tab="armory" />}
/>
<Route
path="/character/:playerId1/feud/:playerId2"
element={<PlayerFeudPage />}
/>

<Route path="/creatures" element={<Creatures />} />
<Route path="/creature/:id" element={<Creature />} />
<Route
path="/creature/:id/quests"
element={<Creature tab="quests" />}
/>
<Route
path="/creature/:id/vendorItems"
element={<Creature tab="vendorItems" />}
/>
<Route path="/creature/:id/zone/:zoneId" element={<Creature />} />

<Route path="/gameobject/:id" element={<GameObject />} />
<Route path="/creature/:id/quests" element={<GameObject />} />
<Route path="/creature/:id/zone/:zoneId" element={<GameObject />} />

<Route path="/guild/:id" element={<Guild tab="kills" />} />
<Route path="/guild/:id/members" element={<Guild tab="members" />} />
<Route
path="/guild/:id/scenarios"
element={<Guild tab="scenarios" />}
/>
<Route
path="/guild/:id/skirmishes"
element={<Guild tab="skirmishes" />}
/>
<Route
path="/guild/:guildId1/feud/:guildId2"
element={<GuildFeudPage />}
/>

<Route path="/quests" element={<Quests />} />
<Route path="/quest/:id" element={<Quest />} />

<Route path="/search/:query" element={<Search />} />
<Route path="/search/guild/:query" element={<SearchGuild />} />

<Route path="/scenario/:id" element={<Scenario tab="scoreboard" />} />
<Route path="/scenario/:id/kills" element={<Scenario tab="kills" />} />
<Route
path="/scenario/:id/skirmishes"
element={<Scenario tab="skirmishes" />}
/>
<Route path="/scenario/:id/map" element={<Scenario tab="map" />} />
<Route path="/skirmishes" element={<Home tab="skirmishes" />} />
<Route path="/skirmish/:id" element={<Skirmish tab="scoreboard" />} />
<Route path="/skirmish/:id/kills" element={<Skirmish tab="kills" />} />
<Route
path="/skirmish/:id/damage/:characterId"
element={<Skirmish tab="characterDamage" />}
/>
<Route
path="/skirmish/:id/damage"
element={<Skirmish tab="damage" />}
/>
<Route path="/zone_heatmap/:id" element={<ZoneMap />} />

<Route path="/items" element={<Items />} />
<Route path="/item/:id" element={<Item tab="vendors" />} />
<Route path="/item/:id/purchase" element={<Item tab="purchase" />} />
<Route path="/item/:id/quests" element={<Item tab="quests" />} />

<Route path="/instance-runs" element={<InstanceRuns />} />
<Route path="/instance-run/:id" element={<InstanceRun />} />
<Route
path="/instance-run/:instanceRunId/:id"
element={<InstanceEncounterRun />}
/>

<Route path="/instances" element={<Instances />} />
<Route
path="/instance-statistics/:id"
element={<InstanceStatistics />}
/>

<Route path="/storylines" element={<Storylines />} />
<Route path="/storylines/:id" element={<Storyline />} />
<Route
path="/storylines/:storylineId/:id"
element={<StorylineEntry />}
/>
<Route
path="/storylines/:storylineId/:storylineEntryId/:id"
element={<StorylineActivity />}
/>

<Route path="/ranked-leaderboard" element={<RankedLeaderboard />} />
</Routes>
</>
);
};

Expand Down
13 changes: 7 additions & 6 deletions src/components/WarIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ReactElement } from 'react';
import { assetUrl } from '@/utils';

export const WarIcon = ({
icon,
Expand All @@ -19,18 +20,18 @@ export const WarIcon = ({
switch (frameType) {
case 'circle': {
return selected
? '/images/icons/round_frame_press.png'
: '/images/icons/round_frame.png';
? assetUrl('/images/icons/round_frame_press.png')
: assetUrl('/images/icons/round_frame.png');
}
case 'hex': {
return selected
? '/images/icons/hex_frame_press.png'
: '/images/icons/hex_frame.png';
? assetUrl('/images/icons/hex_frame_press.png')
: assetUrl('/images/icons/hex_frame.png');
}
default: {
return selected
? '/images/icons/square_frame_press.png'
: '/images/icons/square_frame.png';
? assetUrl('/images/icons/square_frame_press.png')
: assetUrl('/images/icons/square_frame.png');
}
}
})();
Expand Down
2 changes: 1 addition & 1 deletion src/components/ZoneHeatmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const ZoneHeatmap = ({
zoneId,
max,
data,
size = 640,
size,
}: {
zoneId: string;
max: number;
Expand Down
49 changes: 49 additions & 0 deletions src/components/global/MainNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
import { Link, useLocation } from 'react-router';
import type { ReactElement } from 'react';

// Persistent top nav, rendered on every page (see App.tsx). Nav links are
// alphabetical by label. Items, Quests, Creatures, Instances, and
// Storylines each have their own route and full page layout; the
// ranked leaderboard page intentionally isn't added here.
export const MainNav = (): ReactElement => {
const { t } = useTranslation();
const { pathname } = useLocation();

return (
<div className="container is-max-widescreen mt-2">
<div className="tabs is-fullwidth">
<li className={clsx({ 'is-active': pathname.startsWith('/creature') })}>
<Link to="/creatures">{t('pages:home.showCreatures')}</Link>
</li>
<li className={clsx({ 'is-active': pathname === '/guilds' })}>
<Link to="/guilds">{t('pages:home.showGuildLeaderboard')}</Link>
</li>
<li className={clsx({ 'is-active': pathname.startsWith('/instance') })}>
<Link to="/instances">{t('pages:home.showInstances')}</Link>
</li>
<li className={clsx({ 'is-active': pathname.startsWith('/item') })}>
<Link to="/items">{t('pages:home.showItems')}</Link>
</li>
<li className={clsx({ 'is-active': pathname === '/' })}>
<Link to="/">{t('pages:home.showPlayerLeaderboard')}</Link>
</li>
<li className={clsx({ 'is-active': pathname.startsWith('/quest') })}>
<Link to="/quests">{t('pages:home.showQuests')}</Link>
</li>
<li className={clsx({ 'is-active': pathname === '/scenarios' })}>
<Link to="/scenarios">{t('pages:home.showScenarios')}</Link>
</li>
<li className={clsx({ 'is-active': pathname === '/skirmishes' })}>
<Link to="/skirmishes">{t('pages:home.showSkirmishes')}</Link>
</li>
<li
className={clsx({ 'is-active': pathname.startsWith('/storylines') })}
>
<Link to="/storylines">{t('pages:home.showStorylines')}</Link>
</li>
</div>
</div>
);
};
Loading