v0.5.2: offline report + tab UI + persist race fix

- New: Offline earnings report dialog (welcome back popup with gain/duration/rate/progress)
  - src/lib/game/offlineReport.ts: module-level store + custom event
  - src/components/game/OfflineReportDialog.tsx: Radix Dialog UI
  - gameStore.init: setPendingOfflineReport before set()
- Fix: useGameLoop waits for persist.hasHydrated() before init
  - Root cause: Zustand persist rehydrate is async (Promise.resolve wrapper)
  - Before fix: init read INITIAL_STATE (lastTick=undefined), skipped offline branch
  - After fix: init waits for rehydrate, correctly reads saved lastTick
- UI: Tab bar redesign with per-tab accent colors + flex-col layout
- Version: v0.5 -> v0.5.2
This commit is contained in:
2026-06-23 17:04:28 +00:00
parent bd28219b65
commit 296c609479
5 changed files with 273 additions and 33 deletions
+30 -28
View File
@@ -17,6 +17,7 @@ import { ConstellationDialog } from "@/components/game/ConstellationDialog";
import { ChronicleDialog } from "@/components/game/ChronicleDialog";
import { BeaconPanel } from "@/components/game/BeaconPanel";
import { TutorialOverlay } from "@/components/game/TutorialOverlay";
import { OfflineReportDialog } from "@/components/game/OfflineReportDialog";
import {
StarTideNotifier,
StarTideIndicator,
@@ -165,7 +166,7 @@ export default function Page() {
<div className="leading-tight">
<h1 className="text-base sm:text-lg font-bold text-gradient"></h1>
<p className="text-[9px] sm:text-[10px] text-muted-foreground/70 -mt-0.5 tracking-wider">
ECHO NEXUS · v0.5
ECHO NEXUS · v0.5.2
</p>
</div>
</div>
@@ -245,49 +246,49 @@ export default function Page() {
{/* 标签面板:探险 / 技术 / 星图 / 图谱 / 成就 / 信标 / 统计 */}
<div className="glass rounded-2xl p-3 min-h-[320px] max-h-[440px]">
<Tabs defaultValue={hasActiveExpedition ? "expedition" : hasPendingPerk ? "constellation" : "tech"} className="h-full flex flex-col">
<TabsList className="grid grid-cols-7 h-8 bg-black/30">
<TabsTrigger value="expedition" data-tut="tab-expedition" className="text-[11px] gap-0.5 relative px-0.5">
<Rocket className="h-3 w-3" />
<TabsList className="grid grid-cols-7 h-9 bg-black/30 gap-0.5 p-1">
<TabsTrigger value="expedition" data-tut="tab-expedition" className="text-[11px] gap-1 relative px-0.5 flex-col h-7 data-[state=active]:bg-amber-500/15 data-[state=active]:shadow-[0_0_12px_rgba(251,191,36,0.3)] transition-all">
<Rocket className="h-3.5 w-3.5" />
<span className="leading-none"></span>
{energy >= 1 && !hasActiveExpedition && (
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse" />
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-amber-400 animate-pulse ring-1 ring-black/50" />
)}
{hasActiveExpedition && (
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-rose-400 animate-pulse" />
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-rose-400 animate-pulse ring-1 ring-black/50" />
)}
</TabsTrigger>
<TabsTrigger value="tech" data-tut="tab-tech" className="text-[11px] gap-0.5 px-0.5">
<Cpu className="h-3 w-3" />
<TabsTrigger value="tech" data-tut="tab-tech" className="text-[11px] gap-1 px-0.5 flex-col h-7 data-[state=active]:bg-emerald-500/15 data-[state=active]:shadow-[0_0_12px_rgba(52,211,153,0.3)] transition-all">
<Cpu className="h-3.5 w-3.5" />
<span className="leading-none"></span>
</TabsTrigger>
<TabsTrigger value="constellation" className="text-[11px] gap-0.5 relative px-0.5">
<Star className="h-3 w-3" />
<TabsTrigger value="constellation" className="text-[11px] gap-1 relative px-0.5 flex-col h-7 data-[state=active]:bg-fuchsia-500/15 data-[state=active]:shadow-[0_0_12px_rgba(232,121,249,0.3)] transition-all">
<Star className="h-3.5 w-3.5" />
<span className="leading-none"></span>
{hasPendingPerk && (
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-fuchsia-400 animate-pulse" />
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-fuchsia-400 animate-pulse ring-1 ring-black/50" />
)}
</TabsTrigger>
<TabsTrigger value="codex" className="text-[11px] gap-0.5 px-0.5">
<BookOpen className="h-3 w-3" />
<TabsTrigger value="codex" className="text-[11px] gap-1 px-0.5 flex-col h-7 data-[state=active]:bg-cyan-500/15 data-[state=active]:shadow-[0_0_12px_rgba(34,211,238,0.3)] transition-all">
<BookOpen className="h-3.5 w-3.5" />
<span className="leading-none"></span>
</TabsTrigger>
<TabsTrigger value="ach" className="text-[11px] gap-0.5 relative px-0.5">
<Trophy className="h-3 w-3" />
<TabsTrigger value="ach" className="text-[11px] gap-1 relative px-0.5 flex-col h-7 data-[state=active]:bg-amber-500/15 data-[state=active]:shadow-[0_0_12px_rgba(251,191,36,0.3)] transition-all">
<Trophy className="h-3.5 w-3.5" />
<span className="leading-none"></span>
{ownedAchCount < ACHIEVEMENTS.length && (
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse" />
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-amber-400 animate-pulse ring-1 ring-black/50" />
)}
</TabsTrigger>
<TabsTrigger value="beacon" className="text-[11px] gap-0.5 relative px-0.5">
<Radio className="h-3 w-3" />
<TabsTrigger value="beacon" className="text-[11px] gap-1 relative px-0.5 flex-col h-7 data-[state=active]:bg-emerald-500/15 data-[state=active]:shadow-[0_0_12px_rgba(52,211,153,0.3)] transition-all">
<Radio className="h-3.5 w-3.5" />
<span className="leading-none"></span>
{beaconClaimable && (
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-emerald-400 animate-pulse ring-1 ring-black/50" />
)}
</TabsTrigger>
<TabsTrigger value="stats" className="text-[11px] gap-0.5 px-0.5">
<BarChart3 className="h-3 w-3" />
<TabsTrigger value="stats" className="text-[11px] gap-1 px-0.5 flex-col h-7 data-[state=active]:bg-slate-500/15 data-[state=active]:shadow-[0_0_12px_rgba(148,163,184,0.3)] transition-all">
<BarChart3 className="h-3.5 w-3.5" />
<span className="leading-none"></span>
</TabsTrigger>
</TabsList>
<TabsContent value="expedition" className="flex-1 mt-2 min-h-0 data-[state=active]:flex data-[state=active]:flex-col">
@@ -371,6 +372,7 @@ export default function Page() {
<AchievementNotifier />
<StarTideNotifier />
<TutorialOverlay />
<OfflineReportDialog />
</div>
);
}