Skip to main content

Frontend

The web app is Next.js (App Router) + React + TypeScript with an embedded Phaser game. Supporting libraries: TanStack Query (server state), Zustand (client/game state), Zod (validation), Solana wallet adapter.

Folder structure

alpun-app/src/
├── app/
│ ├── (marketing)/ Homepage, tokenomics, roadmap, universe
│ ├── play/ Phaser game container
│ ├── dashboard/ Staking, inventory, missions, leaderboard
│ └── layout.tsx
├── components/
│ ├── game/ PhaserGame, GameOverlay, LoadingScreen
│ ├── wallet/ Connect button, wallet modal
│ ├── staking/ Positions, deposit/withdraw flows
│ └── ui/
├── game/
│ ├── config.ts Phaser.Game configuration
│ ├── events.ts Typed EventBus bridging React ↔ Phaser
│ ├── objects/ Alpaca, Resource, Building
│ ├── scenes/ Boot, Preload, Farm, UI
│ └── systems/ Movement, Mission, Network
├── stores/ game-store, player-store, wallet-store
├── lib/ api-client, websocket-client, solana-client
└── types/

Responsibilities

LayerOwns
Next.jsHomepage, SEO pages, wallet connection, dashboard, staking UI, leaderboards, game container
PhaserRendering, movement, animations, farm interactions, collisions, sound, particles, local prediction
TanStack QueryPlayer profile, missions, staking positions, inventory and leaderboard caching/refresh
ZustandCurrent session, selected alpaca, audio settings, scene, wallet state shared with Phaser
ZodValidating API responses and game commands

React ↔ Phaser bridge

React state never lives inside Phaser. A typed event bus connects the two:

export type GameEvents = {
"player:ready": { playerId: string };
"resource:collected": { resourceId: string; quantity: number };
"mission:started": { missionId: string };
"hud:update": { energy: number; wool: number };
};

Direction of traffic:

React → Phaser
Start mission, change selected alpaca, change map,
update wallet-derived information

Phaser → React
Open staking modal, update HUD, show mission result,
display transaction request

Scenes

Phaser scenes are separated by responsibility — BootScene (config), PreloadScene (asset loading with progress), FarmScene (the world), UIScene (HUD overlay running in parallel).

Rendering rule

The client is allowed to animate optimistically — collecting a resource plays immediately — but every credited change comes back from the backend via the command response or a WebSocket state delta. If the server rejects a command, the UI reconciles.