Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
53b26e0
Show player levels and the XP each game earns
iiamlewis Sep 30, 2026
6ff9abf
Strip Lit's comment markers in the badge tests as nodes, not by regex
iiamlewis Sep 30, 2026
c91fed6
Merge branch 'main' into player-levels-ui
iiamlewis Oct 1, 2026
e7ef291
Add profile cards and the prestige flow
iiamlewis Oct 1, 2026
12d5fb6
Harden the prestige hold and recover from a lost answer
iiamlewis Oct 1, 2026
0ef94ba
Harden player levels UI from PR review
iiamlewis Oct 1, 2026
9e6c781
Merge remote-tracking branch 'origin/player-levels-ui' into profile-card
iiamlewis Oct 1, 2026
2406bc6
Look for the profile card in the profile-level tests
iiamlewis Oct 1, 2026
23be312
Merge branch 'main' into player-levels-ui
iiamlewis Oct 2, 2026
1ab88c7
Address review on the prestige flow
iiamlewis Oct 2, 2026
b899cb0
Address review on player levels: unverified games, off-screen reveals…
iiamlewis Oct 2, 2026
2aed23e
Merge remote-tracking branch 'origin/player-levels-ui' into profile-card
iiamlewis Oct 2, 2026
216851d
Rename the first-win XP line to first game of the day
iiamlewis Oct 2, 2026
2ae124e
Merge remote-tracking branch 'origin/player-levels-ui' into profile-card
iiamlewis Oct 2, 2026
a9c27cb
Recognise the new subscriber XP boosts: +25% / +50% / +100%
iiamlewis Oct 2, 2026
1197e98
Merge remote-tracking branch 'origin/player-levels-ui' into profile-card
iiamlewis Oct 2, 2026
a4f8edc
Add a Progression tab to the player profile
iiamlewis Oct 3, 2026
1b85229
Keep popup tabs on one scrollable row on narrow screens
iiamlewis Oct 3, 2026
bc2d1cb
Open the player profile from /player/<id> links
iiamlewis Oct 3, 2026
f6c2dcf
Put the not-found buttons side by side where there's room
iiamlewis Oct 3, 2026
9df3cab
Leave the profile link when a hash-opened modal takes over
iiamlewis Oct 3, 2026
cb1188c
Let profile-share open its actions as a menu, with a line of its own
iiamlewis Oct 3, 2026
19ccf82
Share a milestone or Legend from the end-of-game popup
iiamlewis Oct 3, 2026
b67eb66
Share the new rank from the prestige ceremony
iiamlewis Oct 3, 2026
5f83874
Stack the XP reveal's counter and level progress on a narrow bar
iiamlewis Oct 3, 2026
6a758f1
Show the XP a past game earned under its summary card
iiamlewis Oct 3, 2026
a18d74e
Drop the "New to OpenFront?" banner from the profile page
iiamlewis Oct 4, 2026
96b683e
Merge remote-tracking branch 'origin/player-levels-ui' into profile-card
iiamlewis Oct 4, 2026
9b4481d
Flash a ring around a share button on hover or keyboard focus
iiamlewis Oct 4, 2026
2b35fdb
Read the prestige rewards and track flares from the progression config
iiamlewis Oct 4, 2026
864f17c
Prestige: show the real rewards, and charge the emblem into the ceremony
iiamlewis Oct 4, 2026
94ba934
Open the full profile card with a honeycomb pulse; dress a Legend's i…
iiamlewis Oct 4, 2026
9399846
Give becoming a Legend a full-screen ceremony
iiamlewis Oct 4, 2026
ebce76c
Draw a colour and outline for every prestige rank
iiamlewis Oct 4, 2026
c184385
Light the XP bar's leading edge while it fills
iiamlewis Oct 4, 2026
782aa96
Read a game's level rewards and unlocks from the XP result
iiamlewis Oct 4, 2026
c1695b0
Show and claim a game's level rewards in the end-of-game reveal
iiamlewis Oct 4, 2026
169fc46
Let the nav's XP bar catch up to new progress
iiamlewis Oct 4, 2026
78db817
Draw the profile card's hex patterns as SVG, and start the pulse at t…
iiamlewis Oct 4, 2026
fd85cb8
Let the confirmation's emblem grow with the hold
iiamlewis Oct 4, 2026
47e7900
Keep the nav's XP bar clear of the avatar
iiamlewis Oct 4, 2026
db7c93f
Prestige: charge the player's own badge, and reveal the new rank's em…
iiamlewis Oct 4, 2026
b7cd806
Keep the prestige titles white for every rank
iiamlewis Oct 4, 2026
e4f9db4
Merge remote-tracking branch 'origin/profile-card' into profile-progr…
iiamlewis Oct 4, 2026
971b0f0
Read a level reward's level and prestige run from /users/@me
iiamlewis Oct 4, 2026
26ac084
Let a flare's cosmetic be shown before the catalog answers
iiamlewis Oct 4, 2026
b5f7a38
Add the reward track to the profile's Progression tab
iiamlewis Oct 4, 2026
c6383ae
Merge profile-progression: real prestige rewards, ceremony polish, Le…
iiamlewis Oct 4, 2026
17a3aa5
Merge profile-progression into profile-page
iiamlewis Oct 4, 2026
fbccf9d
Add a share row to the Legend ceremony
iiamlewis Oct 4, 2026
a78bcfb
Merge profile-page into levels-polish
iiamlewis Oct 4, 2026
3dc7c5d
Import the flare cosmetic's catalog and preview statically
iiamlewis Oct 5, 2026
f3c0b73
Load the prestige flow, the Legend ceremony and the XP cards on demand
iiamlewis Oct 5, 2026
ca04632
Merge profile-card: lazy-loaded levels screens
iiamlewis Oct 5, 2026
417e891
Fetch the reward track when the Progression tab first opens
iiamlewis Oct 5, 2026
f09b2aa
Draw each level badge look once and clone it per badge
iiamlewis Oct 5, 2026
3a4a50e
Keep everything the page needs at startup in the entry chunk
iiamlewis Oct 5, 2026
78794ab
Merge profile-card: keep startup modules in the entry chunk
iiamlewis Oct 5, 2026
720dc89
Merge profile-progression: lazy-loaded levels screens
iiamlewis Oct 5, 2026
87cacb8
Merge profile-page (with profile-progression's lazy loading) into lev…
iiamlewis Oct 5, 2026
c93e4b0
Merge main: src/core split into engine, engine-api and shared packages
iiamlewis Oct 6, 2026
ff32b62
Merge player-levels-ui: src/core split into packages
iiamlewis Oct 6, 2026
41e71fd
Merge profile-card: src/core split into packages
iiamlewis Oct 6, 2026
d1f20b9
Merge profile-progression: src/core split into packages
iiamlewis Oct 6, 2026
bf3e8d0
Merge profile-page: src/core split into packages
iiamlewis Oct 6, 2026
c1f80da
Merge remote-tracking branch 'origin/main' into player-levels-ui
iiamlewis Oct 6, 2026
d1ae378
Point the XP popup test at EventBus's new home in @openfront/shared
iiamlewis Oct 6, 2026
92b2c02
Merge player-levels-ui: bring the branch up to date with main
iiamlewis Oct 6, 2026
4c22604
Merge remote-tracking branch 'origin/profile-card' into profile-progr…
iiamlewis Oct 6, 2026
e274881
Merge remote-tracking branch 'origin/profile-progression' into profil…
iiamlewis Oct 6, 2026
0349cde
Merge remote-tracking branch 'origin/profile-page' into levels-polish
iiamlewis Oct 6, 2026
1271d1f
Merge main: lazy-loaded homepage modals
iiamlewis Oct 6, 2026
5d76db1
Merge player-levels-ui: main's lazy-loaded homepage modals
iiamlewis Oct 6, 2026
6a9a966
Reward track: read the owner's account afresh for level statuses
iiamlewis Oct 8, 2026
426de1c
Merge remote-tracking branch 'origin/profile-progression' into profil…
iiamlewis Oct 8, 2026
e8e6126
Merge remote-tracking branch 'origin/profile-page' into levels-polish
iiamlewis Oct 8, 2026
2b637e9
Refuse share moments the card route wouldn't draw, and move the URL o…
iiamlewis Oct 8, 2026
812cc15
Address review on the player levels UI
iiamlewis Oct 8, 2026
8df9cba
Prestige flow: follow the config's ladder, report late prestiges
iiamlewis Oct 8, 2026
d84816a
Reward track: follow the config's level cap; review fixes
iiamlewis Oct 8, 2026
088af00
Merge player-levels-ui (review fixes)
iiamlewis Oct 8, 2026
9e68afe
Merge profile-card (review fixes)
iiamlewis Oct 8, 2026
5a83a15
Merge profile-progression (review fixes)
iiamlewis Oct 8, 2026
ce7814a
Merge profile-page (review fixes)
iiamlewis Oct 8, 2026
4f5f21c
Merge main (#5726 and #5778 landed)
iiamlewis Oct 9, 2026
9d38e37
Merge profile-card (main sync)
iiamlewis Oct 9, 2026
c1f4998
Merge profile-progression (main sync)
iiamlewis Oct 9, 2026
c12cadd
Merge profile-page (main sync)
iiamlewis Oct 9, 2026
3dfaa9e
Show prestige ranks past 10, clamp the XP bar, escape the markup sent…
iiamlewis Oct 9, 2026
15a5a13
Merge branch 'profile-card' into profile-progression
iiamlewis Oct 9, 2026
cc14732
Merge branch 'profile-progression' into profile-page
iiamlewis Oct 9, 2026
0babc7a
Merge profile-page: show prestige ranks past 10, clamp the XP bar
iiamlewis Oct 9, 2026
54f8261
Rewrap the xpBar doc comment to 80 columns
iiamlewis Oct 9, 2026
a53acd7
Merge branch 'profile-card' into profile-progression
iiamlewis Oct 9, 2026
259f18c
Merge branch 'profile-progression' into profile-page
iiamlewis Oct 9, 2026
13072e6
Merge branch 'profile-page' into levels-polish
iiamlewis Oct 9, 2026
5860edd
Merge origin/main into profile-progression
iiamlewis Oct 9, 2026
d178f56
Merge branch 'profile-progression' into profile-page
iiamlewis Oct 9, 2026
7026f5e
Let an explicit tab request decide the profile's opening tab
iiamlewis Oct 9, 2026
57d1959
Merge branch 'profile-progression' into profile-page
iiamlewis Oct 9, 2026
cc5b916
Merge branch 'profile-page' into levels-polish
iiamlewis Oct 9, 2026
4e20a70
Profile: don't pick the Progression tab for a closed profile
iiamlewis Oct 9, 2026
58e24eb
Merge profile-progression (closed-profile tab fix)
iiamlewis Oct 9, 2026
70bc26e
Merge profile-page (closed-profile tab fix)
iiamlewis Oct 9, 2026
d8b1144
Merge origin/main into profile-progression
iiamlewis Oct 11, 2026
68b20ee
Merge profile-progression into profile-page
iiamlewis Oct 11, 2026
51c5cbc
Merge profile-page into levels-polish
iiamlewis Oct 11, 2026
67b202d
Drop the Heraldry prestige emblems (superseded by the unit-icon design)
iiamlewis Oct 12, 2026
2d2f79b
Profile: drop stale comments, old-API fallbacks and duplicates
iiamlewis Oct 12, 2026
2419c2b
Reward track: keep the guards for an API that hasn't deployed the lev…
iiamlewis Oct 12, 2026
caff98c
Merge origin/main into profile-progression
iiamlewis Oct 12, 2026
50a7e71
Rewards: skip held rewards in the reward track and the end-of-game row
iiamlewis Oct 12, 2026
fbbe348
Merge origin/main into profile-progression
iiamlewis Oct 12, 2026
412dae8
Rewards: type the held Claim all test's claim mock
iiamlewis Oct 12, 2026
7c4523c
Merge origin/main into profile-progression
iiamlewis Oct 12, 2026
dff0ea6
Rewards: a claim refused as held moves on to the rest
iiamlewis Oct 12, 2026
a80c2aa
Merge main: provisional XP at death (#5759)
iiamlewis Oct 12, 2026
cbad5d3
Profile: open the linked tab, and agree with the claim bar on fresh l…
iiamlewis Oct 12, 2026
1c92332
Merge branch 'main' into profile-progression
iiamlewis Oct 12, 2026
f89228f
Merge branch 'main' into profile-progression
iiamlewis Oct 12, 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
56 changes: 56 additions & 0 deletions packages/shared/src/ApiSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ export const RewardSchema = z.object({
amount: z.string(),
reason: z.string(),
note: z.string().nullable(),
// On a level reward (`level_up` / `level_milestone`): the level it was
// paid for, and the prestige run it was reached in. Absent on every other
// reward, and on an API that predates them.
level: z.number().optional(),
prestige: z.number().optional(),
// Why the reward can't be claimed yet; absent when it can. "trust": level
// Plutonium, kept until the account is trusted. Any other non-empty value is
// a hold this client doesn't know yet, and still reads as held: an unknown
Expand Down Expand Up @@ -207,6 +212,34 @@ export const XpBreakdownSchema = z.object({
});
export type XpBreakdown = z.infer<typeof XpBreakdownSchema>;

// A level reward one game earned (GET /users/@me/xp/:gameId `rewards`).
export const GameXpRewardSchema = RewardSchema.extend({
prestige: z.number(),
level: z.number(),
claimed: z.boolean(),
});
export type GameXpReward = z.infer<typeof GameXpRewardSchema>;

// A flare one game unlocked from the level track: a level's (`prestige` and
// `level` are the run and level crossed) or Legend's (both null). `kind`
// stays a string so a new kind never fails the parse.
export const GameXpFlareSchema = z.object({
kind: z.string(),
prestige: z.number().nullable(),
level: z.number().nullable(),
flareId: z.string(),
flareName: z.string(),
// The cosmetic behind the flare, when it is one.
cosmetic: z
.object({
type: z.string(),
name: z.string(),
url: z.string().nullable(),
})
.nullable(),
});
export type GameXpFlare = z.infer<typeof GameXpFlareSchema>;

// GET /users/@me/xp/:gameId — the XP a finished game awarded the caller. 404
// until the game has been processed. `reason` is open-ended server-side, so it
// stays a string: an unknown reason shows a generic line, never a parse error.
Expand All @@ -221,6 +254,14 @@ export const GameXpEligibleSchema = z.object({
.array(z.object({ prestige: z.number(), level: z.number() }))
.optional()
.default([]),
// What the levels this game crossed paid: one entry per grant, in level
// order, Caps before Plutonium. `id` is the reward to claim while
// `claimed` is false. Optional (an API without it still parses), and a
// malformed list reads as absent rather than failing the result.
rewards: z.array(GameXpRewardSchema).optional().catch(undefined),
// The cosmetics this game unlocked from the level track (already granted:
// nothing to claim). Same leniency as `rewards`.
flares: z.array(GameXpFlareSchema).optional().catch(undefined),
});
export const GameXpIneligibleSchema = z.object({
gameId: z.string(),
Expand All @@ -246,6 +287,21 @@ export const PublicProgressSchema = z.object({
// is left out.
xpInLevel: z.number().optional(),
xpForNext: z.number().optional(),
// For the profile's Progression tab. Optional, and a malformed list reads
// as absent rather than failing the whole response (the level still shows).
// When each prestige rank was entered (ISO dates), ordered by rank.
prestigeHistory: z
.array(z.object({ rank: z.number(), at: z.string() }))
.optional()
.catch(undefined),
// The first time each milestone level (10/25/50/75/100) was reached in each
// prestige run (ISO dates), ordered by run then level.
milestones: z
.array(
z.object({ prestige: z.number(), level: z.number(), at: z.string() }),
)
.optional()
.catch(undefined),
});
export type PublicProgress = z.infer<typeof PublicProgressSchema>;

Expand Down
64 changes: 62 additions & 2 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
"tab_clans": "Clans",
"tab_friends": "Friends",
"tab_games": "Games",
"tab_progression": "Progression",
"tab_stats": "Stats",
"title": "Account",
"unclaimed_rewards": "Unclaimed Rewards",
Expand Down Expand Up @@ -1741,12 +1742,28 @@
"troops": "Troops"
},
"player_profile": {
"current_run": "Current run",
"current_run_prestige": "Current run · Prestige {prestige}",
"first_run": "First run",
"legend_run": "Legend · Prestige {prestige}",
"loading": "Loading profile...",
"milestone_not_yet": "Not yet",
"milestones": "Milestones",
"no_clans": "This player isn't in any clans.",
"not_found": "Player profile not found",
"no_prestige": "Hasn't prestiged yet",
"not_found_body": "We couldn't find a player at this link. It may be mistyped, or the account may have been deleted.",
"not_found_title": "Player not found",
"play": "Play OpenFront",
"prestige_history": "Prestige history",
"share": "Share Profile",
"share_copy_link": "Copy link",
"share_discord": "Copy for Discord",
"share_native": "Share",
"share_text": "Check out {name}'s profile on OpenFront",
"share_x": "Share on X",
"title": "Player Profile",
"view": "View Profile"
"view": "View Profile",
"view_leaderboard": "View leaderboard"
},
"player_stats_table": {
"arrived": "Arrived",
Expand Down Expand Up @@ -1849,6 +1866,7 @@
"keep_holding": "Keep holding…",
"load_failed": "Couldn't open Prestige. Check your connection and try again.",
"ready": "Level 100! Until you prestige, XP only adds to your lifetime total.",
"share_text": "I just reached Prestige {rank} on OpenFront!",
"stale": "Your level changed since this page loaded, so your account has been refreshed.",
"stale_failed": "Your level changed since this page loaded. Reload the page to see where you are.",
"submitting": "Prestiging…",
Expand Down Expand Up @@ -1935,9 +1953,24 @@
"reconcile_other": "The final figure differs from the provisional one.",
"reconcile_reconnect": "An opponent who had disconnected came back, so they don't count as outlasted.",
"reconcile_team_win": "Your team won: the win bonus was added.",
"rewards_balance": "Balance {caps} Caps · {plutonium} Plutonium",
"rewards_balance_caps": "Balance {caps} Caps",
"rewards_claimed": "Claimed",
"rewards_claiming": "Claiming",
"rewards_each": "({count} × {amount})",
"rewards_level_many": "Level {level} rewards",
"rewards_level_one": "Level {level} reward",
"rewards_levels": "Levels {from}–{to}",
"share_moment_legend": "Share Legend",
"share_moment_level": "Share Level {level}",
"share_text_legend": "I just became a Legend on OpenFront!",
"share_text_level": "I just reached Level {level} on OpenFront!",
"sign_in_to_earn": "Sign in to earn XP and level up.",
"skip_reveal": "Skip",
"tap_to_skip": "Tap to skip",
"unlock_in_locker": "{type} · in your Locker",
"unlocked": "Unlocked",
"unlocked_cosmetic": "Cosmetic",
"xp_bar_label": "Level progress",
"xp_before_levels": "Played before levels existed, so there's no XP for this game.",
"xp_confirmed": "XP confirmed",
Expand Down Expand Up @@ -2042,6 +2075,33 @@
"unavailable_unreachable": "The game could not be downloaded. Check your connection and try again.",
"watch_classic": "Watch the old replay instead"
},
"reward_track": {
"after_level": "After level {level}",
"caps_amount": "{amount} Caps",
"claim_caps": "+{amount} Caps",
"claim_count": "{count, plural, one {# level reward to claim} other {# level rewards to claim}}",
"claim_plutonium": "+{amount} Plutonium",
"earlier": "Earlier levels",
"key_claimed": "Claimed",
"key_locked": "Locked",
"key_ready": "Ready to claim",
"later": "Later levels",
"legend_reward": "Legend reward",
"legend_sub": "The top of the track",
"levels_label": "Levels and their rewards",
"plus": "+{amount}",
"prestige_reward": "Prestige {rank} reward",
"strip_end_legend": "{level} → Legend",
"strip_end_prestige": "{level} → P{rank}",
"strip_label": "Levels 1 to {max}. Choose a level to show it on the track.",
"strip_level": "Level {level}",
"strip_start": "Lv 1",
"strip_value": "Showing levels {lo} to {hi}",
"strip_you": "You · {level}",
"title": "Reward track · Prestige {prestige}",
"title_first_run": "Reward track · First run",
"you": "You"
},
"rewards_modal": {
"title": "Rewards"
},
Expand Down
1 change: 1 addition & 0 deletions src/client/AccountModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export class AccountModal extends BaseModal {
<div class="p-6">${this.renderTab(tab)}</div>
</div>
<prestige-flow
.publicId=${this.userMeResponse?.player?.publicId ?? ""}
@prestiged=${this.handlePrestiged}
@prestige-stale=${this.handlePrestigeStale}
></prestige-flow>
Expand Down
Loading
Loading