v0.6: 深空巡航 Canvas 2D 实时玩法 + UI 偏移重叠修复

## 玩法拓展(回应用户: 玩法太单调/全是点点点)
- 新增「深空巡航」Canvas 2D 全屏实时玩法
  - 飞船操控(WASD/方向键/移动端虚拟摇杆)
  - 实时躲避陨石/虚空风暴,收集晶体/洞见/信标
  - 到达星门通关,护盾归零失败
  - 3层视差星空 + 粒子系统(尾焰/收集/碰撞/烟花) + 辉光 + 屏幕震动
  - 奖励同步主游戏(grantCruiseReward)
  - 独立 localStorage 记录最高分/通关数

## UI 修复(回应用户: 解析球左上偏移/成就小屏重叠)
- DecodeArray 节点: 移除 Tailwind translate 与 inline transform 冲突
- AchievementsPanel: 移除固定 max-h, 改 flex-1 min-h-0 自适应
- page.tsx 布局: 小屏减小 min-h, 避免挤压重叠

## 集成
- gameStore 新增 grantCruiseReward action
- header 新增 amber 主题「巡航」按钮 + 全屏 CruiseMode 渲染

## 文件
- src/lib/game/cruise.ts (新增, ~1008行)
- src/components/game/CruiseMode.tsx (新增, ~1350行)
- src/app/page.tsx (集成巡航入口)
- src/store/gameStore.ts (grantCruiseReward action)
- src/components/game/DecodeArray.tsx (节点偏移修复)
- src/components/game/AchievementsPanel.tsx (小屏重叠修复)

QA: lint 零错误; agent-browser+VLM 验证主界面无偏移+巡航玩法正常运行
This commit is contained in:
2026-06-23 19:33:51 +00:00
parent fcf94b6c3f
commit fafa2ee20a
9 changed files with 3382 additions and 8 deletions
+19 -4
View File
@@ -18,6 +18,7 @@ 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 { CruiseMode } from "@/components/game/CruiseMode";
import {
StarTideNotifier,
StarTideIndicator,
@@ -40,6 +41,7 @@ import {
Trophy,
Star,
Radio,
Navigation,
} from "lucide-react";
import { TECH_TREE, FRAGMENTS, formatNum } from "@/lib/game/config";
import { ACHIEVEMENTS } from "@/lib/game/achievements";
@@ -54,6 +56,7 @@ export default function Page() {
const [settingsOpen, setSettingsOpen] = useState(false);
const [constellationOpen, setConstellationOpen] = useState(false);
const [chronicleOpen, setChronicleOpen] = useState(false);
const [cruiseOpen, setCruiseOpen] = useState(false);
const [mounted, setMounted] = useState(false);
const contact = useGameStore((s) => s.contact);
@@ -172,6 +175,17 @@ export default function Page() {
</div>
<div className="ml-auto flex items-center gap-1.5">
<StarTideIndicator />
<Button
size="sm"
variant="outline"
onClick={() => setCruiseOpen(true)}
className="border-amber-400/50 text-amber-200 hover:bg-amber-500/10 h-8 px-2.5"
aria-label="深空巡航"
title="深空巡航 · 实时玩法"
>
<Navigation className="h-3.5 w-3.5 mr-1" />
</Button>
<Button
size="icon"
variant="ghost"
@@ -224,11 +238,11 @@ export default function Page() {
<ResourceBar onPrestige={() => setPrestigeOpen(true)} />
</header>
{/* 主体 */}
{/* 主体 — 小屏允许自然滚动,避免 min-h 总和超过视口导致挤压重叠 */}
<main className="flex-1 px-3 sm:px-5 pb-3 min-h-0">
<div className="grid grid-cols-1 lg:grid-cols-[1fr_minmax(360px,420px)] gap-3 h-full">
{/* 左侧:脉冲晶体 */}
<section className="glass rounded-2xl p-4 sm:p-6 flex flex-col items-center justify-center min-h-[420px] relative overflow-hidden">
<section className="glass rounded-2xl p-4 sm:p-6 flex flex-col items-center justify-center min-h-[340px] sm:min-h-[420px] relative overflow-hidden">
{/* 装饰光圈 */}
<div className="pointer-events-none absolute -top-20 -left-20 h-60 w-60 rounded-full bg-emerald-500/10 blur-3xl" />
<div className="pointer-events-none absolute -bottom-20 -right-20 h-60 w-60 rounded-full bg-fuchsia-500/10 blur-3xl" />
@@ -240,11 +254,11 @@ export default function Page() {
{/* 右侧:解码 + 标签面板 */}
<section className="flex flex-col gap-3 min-h-0">
{/* 解码面板 */}
<div data-tut="decode-panel" className="glass rounded-2xl p-4 flex-1 min-h-[360px] max-h-[560px]">
<div data-tut="decode-panel" className="glass rounded-2xl p-4 flex-1 min-h-[300px] sm:min-h-[360px] max-h-[560px]">
<DecodePanel />
</div>
{/* 标签面板:探险 / 技术 / 星图 / 图谱 / 成就 / 信标 / 统计 */}
<div className="glass rounded-2xl p-3 min-h-[320px] max-h-[440px]">
<div className="glass rounded-2xl p-3 min-h-[280px] sm:min-h-[320px] max-h-[440px] flex flex-col">
<Tabs defaultValue={hasActiveExpedition ? "expedition" : hasPendingPerk ? "constellation" : "tech"} className="h-full flex flex-col">
<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">
@@ -373,6 +387,7 @@ export default function Page() {
<StarTideNotifier />
<TutorialOverlay />
<OfflineReportDialog />
{cruiseOpen && <CruiseMode onClose={() => setCruiseOpen(false)} />}
</div>
);
}
+2 -2
View File
@@ -37,8 +37,8 @@ export function AchievementsPanel() {
)}
</div>
{/* 成就网格 */}
<div className="flex-1 grid grid-cols-1 sm:grid-cols-2 gap-1.5 content-start overflow-y-auto echo-scroll pr-1 max-h-[300px]">
{/* 成就网格 — 移除固定 max-h,用 flex-1 min-h-0 自适应父容器,避免小屏挤压重叠 */}
<div className="flex-1 min-h-0 grid grid-cols-1 sm:grid-cols-2 gap-1.5 content-start overflow-y-auto echo-scroll pr-1">
{ACHIEVEMENTS.map((a) => {
const unlocked = !!achievements[a.id];
return (
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -192,11 +192,11 @@ export function DecodeArray() {
onMouseEnter={() => setHoverId(node.id)}
onMouseLeave={() => setHoverId(null)}
disabled={used}
className="absolute -translate-x-1/2 -translate-y-1/2 rounded-full transition-all duration-200 focus:outline-none disabled:cursor-not-allowed"
className="absolute rounded-full transition-[box-shadow,background,border,opacity] duration-200 focus:outline-none disabled:cursor-not-allowed"
style={{
left: `${cx}%`,
top: `${cy}%`,
width: `min(${100 / cols * 0.62}%, 56px)`,
width: `min(${(100 / cols) * 0.62}%, 56px)`,
aspectRatio: "1",
background: used
? "rgba(255,255,255,0.04)"
@@ -214,6 +214,7 @@ export function DecodeArray() {
? `1.5px solid ${v.hex}aa`
: "1px solid rgba(255,255,255,0.12)",
opacity: used ? 0.35 : isDeadStart ? 0.5 : 1,
// 统一用 inline transform 定位居中 + 缩放,避免与 Tailwind translate 冲突导致左上偏移
transform: `translate(-50%, -50%) scale(${
isHover && !used ? 1.12 : isFlash ? (flash?.ok ? 1.25 : 0.8) : 1
})`,
File diff suppressed because it is too large Load Diff
+16
View File
@@ -118,6 +118,9 @@ interface GameActions {
// 深空信标奖励发放(v0.5
grantBeaconReward: (insights: number, contact: number) => void;
// 深空巡航奖励发放(v0.6 — 实时 Canvas 玩法)
grantCruiseReward: (rewards: { crystals?: number; insights?: number; contact?: number }) => void;
// 派生
canPrestige: () => boolean;
}
@@ -796,6 +799,19 @@ export const useGameStore = create<Store>()(
contact: Math.min(100, s.contact + contact),
});
},
// 深空巡航:发放实时玩法奖励(v0.6)
grantCruiseReward: (rewards) => {
const s = get();
const addCrystals = rewards.crystals ?? 0;
const addInsights = rewards.insights ?? 0;
const addContact = rewards.contact ?? 0;
set({
crystals: Math.min(s.crystalCap, s.crystals + addCrystals),
insights: s.insights + Math.round(addInsights),
contact: Math.min(100, s.contact + addContact),
});
},
}),
{
name: "echo-nexus-save-v1",