deploy: v0.11 解码卡片hover增强 + StatsPanel性能优化

This commit is contained in:
2026-06-24 02:12:57 +00:00
parent 43c203ebd2
commit 2440d1df33
293 changed files with 410 additions and 41691 deletions
-1
View File
@@ -1 +0,0 @@
DATABASE_URL=file:/home/z/my-project/db/custom.db
-150
View File
@@ -1,150 +0,0 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Echo Nexus 本地文件
local-*
.claude
.z-ai-config
dev.log
dev.out.log
*.tsbuildinfo
next-env.d.ts
.vercel
skills/
qa-*.png
+145
View File
@@ -0,0 +1,145 @@
// Type definitions for Next.js cacheLife configs
declare module 'next/cache' {
export { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache'
export {
updateTag,
revalidateTag,
revalidatePath,
refresh,
} from 'next/dist/server/web/spec-extension/revalidate'
export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store'
/**
* Cache this `"use cache"` for a timespan defined by the `"default"` profile.
* ```
* stale: 300 seconds (5 minutes)
* revalidate: 900 seconds (15 minutes)
* expire: never
* ```
*
* This cache may be stale on clients for 5 minutes before checking with the server.
* If the server receives a new request after 15 minutes, start revalidating new values in the background.
* It lives for the maximum age of the server cache. If this entry has no traffic for a while, it may serve an old value the next request.
*/
export function cacheLife(profile: "default"): void
/**
* Cache this `"use cache"` for a timespan defined by the `"seconds"` profile.
* ```
* stale: 30 seconds
* revalidate: 1 seconds
* expire: 60 seconds (1 minute)
* ```
*
* This cache may be stale on clients for 30 seconds before checking with the server.
* If the server receives a new request after 1 seconds, start revalidating new values in the background.
* If this entry has no traffic for 1 minute it will expire. The next request will recompute it.
*/
export function cacheLife(profile: "seconds"): void
/**
* Cache this `"use cache"` for a timespan defined by the `"minutes"` profile.
* ```
* stale: 300 seconds (5 minutes)
* revalidate: 60 seconds (1 minute)
* expire: 3600 seconds (1 hour)
* ```
*
* This cache may be stale on clients for 5 minutes before checking with the server.
* If the server receives a new request after 1 minute, start revalidating new values in the background.
* If this entry has no traffic for 1 hour it will expire. The next request will recompute it.
*/
export function cacheLife(profile: "minutes"): void
/**
* Cache this `"use cache"` for a timespan defined by the `"hours"` profile.
* ```
* stale: 300 seconds (5 minutes)
* revalidate: 3600 seconds (1 hour)
* expire: 86400 seconds (1 day)
* ```
*
* This cache may be stale on clients for 5 minutes before checking with the server.
* If the server receives a new request after 1 hour, start revalidating new values in the background.
* If this entry has no traffic for 1 day it will expire. The next request will recompute it.
*/
export function cacheLife(profile: "hours"): void
/**
* Cache this `"use cache"` for a timespan defined by the `"days"` profile.
* ```
* stale: 300 seconds (5 minutes)
* revalidate: 86400 seconds (1 day)
* expire: 604800 seconds (1 week)
* ```
*
* This cache may be stale on clients for 5 minutes before checking with the server.
* If the server receives a new request after 1 day, start revalidating new values in the background.
* If this entry has no traffic for 1 week it will expire. The next request will recompute it.
*/
export function cacheLife(profile: "days"): void
/**
* Cache this `"use cache"` for a timespan defined by the `"weeks"` profile.
* ```
* stale: 300 seconds (5 minutes)
* revalidate: 604800 seconds (1 week)
* expire: 2592000 seconds (1 month)
* ```
*
* This cache may be stale on clients for 5 minutes before checking with the server.
* If the server receives a new request after 1 week, start revalidating new values in the background.
* If this entry has no traffic for 1 month it will expire. The next request will recompute it.
*/
export function cacheLife(profile: "weeks"): void
/**
* Cache this `"use cache"` for a timespan defined by the `"max"` profile.
* ```
* stale: 300 seconds (5 minutes)
* revalidate: 2592000 seconds (1 month)
* expire: 31536000 seconds (365 days)
* ```
*
* This cache may be stale on clients for 5 minutes before checking with the server.
* If the server receives a new request after 1 month, start revalidating new values in the background.
* If this entry has no traffic for 365 days it will expire. The next request will recompute it.
*/
export function cacheLife(profile: "max"): void
/**
* Cache this `"use cache"` using a custom timespan.
* ```
* stale: ... // seconds
* revalidate: ... // seconds
* expire: ... // seconds
* ```
*
* This is similar to Cache-Control: max-age=`stale`,s-max-age=`revalidate`,stale-while-revalidate=`expire-revalidate`
*
* If a value is left out, the lowest of other cacheLife() calls or the default, is used instead.
*/
export function cacheLife(profile: {
/**
* This cache may be stale on clients for ... seconds before checking with the server.
*/
stale?: number,
/**
* If the server receives a new request after ... seconds, start revalidating new values in the background.
*/
revalidate?: number,
/**
* If this entry has no traffic for ... seconds it will expire. The next request will recompute it.
*/
expire?: number
}): void
import { cacheTag } from 'next/dist/server/use-cache/cache-tag'
export { cacheTag }
export const unstable_cacheTag: typeof cacheTag
export const unstable_cacheLife: typeof cacheLife
}
+55
View File
@@ -0,0 +1,55 @@
// This file is generated automatically by Next.js
// Do not edit this file manually
type AppRoutes = never
type PageRoutes = never
type LayoutRoutes = never
type RedirectRoutes = never
type RewriteRoutes = never
type Routes = AppRoutes | PageRoutes | LayoutRoutes | RedirectRoutes | RewriteRoutes
interface ParamMap {
}
export type ParamsOf<Route extends Routes> = ParamMap[Route]
interface LayoutSlotMap {
}
export type { AppRoutes, PageRoutes, LayoutRoutes, RedirectRoutes, RewriteRoutes, ParamMap }
declare global {
/**
* Props for Next.js App Router page components
* @example
* ```tsx
* export default function Page(props: PageProps<'/blog/[slug]'>) {
* const { slug } = await props.params
* return <div>Blog post: {slug}</div>
* }
* ```
*/
interface PageProps<AppRoute extends AppRoutes> {
params: Promise<ParamMap[AppRoute]>
searchParams: Promise<Record<string, string | string[] | undefined>>
}
/**
* Props for Next.js App Router layout components
* @example
* ```tsx
* export default function Layout(props: LayoutProps<'/dashboard'>) {
* return <div>{props.children}</div>
* }
* ```
*/
type LayoutProps<LayoutRoute extends LayoutRoutes> = {
params: Promise<ParamMap[LayoutRoute]>
children: React.ReactNode
} & {
[K in LayoutSlotMap[LayoutRoute]]: React.ReactNode
}
}
+16
View File
@@ -0,0 +1,16 @@
// This file is generated automatically by Next.js
// Do not edit this file manually
// This file validates that all pages and layouts export the correct types
+1
View File
@@ -0,0 +1 @@
echo-nexus
-122
View File
@@ -1,122 +0,0 @@
#!/bin/bash
# 将 stderr 重定向到 stdout,避免 execute_command 因为 stderr 输出而报错
exec 2>&1
set -e
# 获取脚本所在目录(.zscripts 目录,即 workspace-agent/.zscripts
# 使用 $0 获取脚本路径(兼容 sh 和 bash)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Next.js 项目路径
NEXTJS_PROJECT_DIR="/home/z/my-project"
# 检查 Next.js 项目目录是否存在
if [ ! -d "$NEXTJS_PROJECT_DIR" ]; then
echo "❌ 错误: Next.js 项目目录不存在: $NEXTJS_PROJECT_DIR"
exit 1
fi
echo "🚀 开始构建 Next.js 应用和 mini-services..."
echo "📁 Next.js 项目路径: $NEXTJS_PROJECT_DIR"
# 切换到 Next.js 项目目录
cd "$NEXTJS_PROJECT_DIR" || exit 1
# 设置环境变量
export NEXT_TELEMETRY_DISABLED=1
BUILD_DIR="/tmp/build_fullstack_$BUILD_ID"
echo "📁 清理并创建构建目录: $BUILD_DIR"
mkdir -p "$BUILD_DIR"
# 安装依赖
echo "📦 安装依赖..."
bun install
# 构建 Next.js 应用
echo "🔨 构建 Next.js 应用..."
bun run build
# 构建 mini-services
# 检查 Next.js 项目目录下是否有 mini-services 目录
if [ -d "$NEXTJS_PROJECT_DIR/mini-services" ]; then
echo "🔨 构建 mini-services..."
# 使用 workspace-agent 目录下的 mini-services 脚本
sh "$SCRIPT_DIR/mini-services-install.sh"
sh "$SCRIPT_DIR/mini-services-build.sh"
# 复制 mini-services-start.sh 到 mini-services-dist 目录
echo " - 复制 mini-services-start.sh 到 $BUILD_DIR"
cp "$SCRIPT_DIR/mini-services-start.sh" "$BUILD_DIR/mini-services-start.sh"
chmod +x "$BUILD_DIR/mini-services-start.sh"
else
echo "️ mini-services 目录不存在,跳过"
fi
# 将所有构建产物复制到临时构建目录
echo "📦 收集构建产物到 $BUILD_DIR..."
# 复制 Next.js standalone 构建输出
if [ -d ".next/standalone" ]; then
echo " - 复制 .next/standalone"
cp -r .next/standalone "$BUILD_DIR/next-service-dist/"
fi
# 复制 Next.js 静态文件
if [ -d ".next/static" ]; then
echo " - 复制 .next/static"
mkdir -p "$BUILD_DIR/next-service-dist/.next"
cp -r .next/static "$BUILD_DIR/next-service-dist/.next/"
fi
# 复制 public 目录
if [ -d "public" ]; then
echo " - 复制 public"
cp -r public "$BUILD_DIR/next-service-dist/"
fi
# 将测试环境数据库复制到构建产物中,生产环境直接使用这份数据库
if [ -f "./db/custom.db" ]; then
echo "🗄️ 复制测试环境数据库到构建产物..."
mkdir -p "$BUILD_DIR/db"
cp -r ./db/. "$BUILD_DIR/db/"
echo "🗄️ 同步构建产物中的数据库结构..."
DATABASE_URL="file:$BUILD_DIR/db/custom.db" bun run db:push
echo "✅ 构建产物数据库已准备完成"
ls -lah "$BUILD_DIR/db"
else
echo "❌ 未找到测试环境数据库文件 ./db/custom.db,无法继续构建生产包"
exit 1
fi
# 复制 Caddyfile(如果存在)
if [ -f "Caddyfile" ]; then
echo " - 复制 Caddyfile"
cp Caddyfile "$BUILD_DIR/"
else
echo "️ Caddyfile 不存在,跳过"
fi
# 复制 start.sh 脚本
echo " - 复制 start.sh 到 $BUILD_DIR"
cp "$SCRIPT_DIR/start.sh" "$BUILD_DIR/start.sh"
chmod +x "$BUILD_DIR/start.sh"
# 打包到 $BUILD_DIR.tar.gz
PACKAGE_FILE="${BUILD_DIR}.tar.gz"
echo ""
echo "📦 打包构建产物到 $PACKAGE_FILE..."
cd "$BUILD_DIR" || exit 1
tar -czf "$PACKAGE_FILE" .
cd - > /dev/null || exit 1
# # 清理临时目录
# rm -rf "$BUILD_DIR"
echo ""
echo "✅ 构建完成!所有产物已打包到 $PACKAGE_FILE"
echo "📊 打包文件大小:"
ls -lh "$PACKAGE_FILE"
-1
View File
@@ -1 +0,0 @@
12512
-154
View File
@@ -1,154 +0,0 @@
#!/bin/bash
set -euo pipefail
# 获取脚本所在目录(.zscripts)
# 使用 $0 获取脚本路径(与 build.sh 保持一致)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
log_step_start() {
local step_name="$1"
echo "=========================================="
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting: $step_name"
echo "=========================================="
export STEP_START_TIME
STEP_START_TIME=$(date +%s)
}
log_step_end() {
local step_name="${1:-Unknown step}"
local end_time
end_time=$(date +%s)
local duration=$((end_time - STEP_START_TIME))
echo "=========================================="
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Completed: $step_name"
echo "[LOG] Step: $step_name | Duration: ${duration}s"
echo "=========================================="
echo ""
}
start_mini_services() {
local mini_services_dir="$PROJECT_DIR/mini-services"
local started_count=0
log_step_start "Starting mini-services"
if [ ! -d "$mini_services_dir" ]; then
echo "Mini-services directory not found, skipping..."
log_step_end "Starting mini-services"
return 0
fi
echo "Found mini-services directory, scanning for sub-services..."
for service_dir in "$mini_services_dir"/*; do
if [ ! -d "$service_dir" ]; then
continue
fi
local service_name
service_name=$(basename "$service_dir")
echo "Checking service: $service_name"
if [ ! -f "$service_dir/package.json" ]; then
echo "[$service_name] No package.json found, skipping..."
continue
fi
if ! grep -q '"dev"' "$service_dir/package.json"; then
echo "[$service_name] No dev script found, skipping..."
continue
fi
echo "Starting $service_name in background..."
(
cd "$service_dir"
echo "[$service_name] Installing dependencies..."
bun install
echo "[$service_name] Running bun run dev..."
exec bun run dev
) >"$PROJECT_DIR/.zscripts/mini-service-${service_name}.log" 2>&1 &
local service_pid=$!
echo "[$service_name] Started in background (PID: $service_pid)"
echo "[$service_name] Log: $PROJECT_DIR/.zscripts/mini-service-${service_name}.log"
disown "$service_pid" 2>/dev/null || true
started_count=$((started_count + 1))
done
echo "Mini-services startup completed. Started $started_count service(s)."
log_step_end "Starting mini-services"
}
wait_for_service() {
local host="$1"
local port="$2"
local service_name="$3"
local max_attempts="${4:-60}"
local attempt=1
echo "Waiting for $service_name to be ready on $host:$port..."
while [ "$attempt" -le "$max_attempts" ]; do
if curl -s --connect-timeout 2 --max-time 5 "http://$host:$port" >/dev/null 2>&1; then
echo "$service_name is ready!"
return 0
fi
echo "Attempt $attempt/$max_attempts: $service_name not ready yet, waiting..."
sleep 1
attempt=$((attempt + 1))
done
echo "ERROR: $service_name failed to start within $max_attempts seconds"
return 1
}
cleanup() {
if [ -n "${DEV_PID:-}" ] && kill -0 "$DEV_PID" >/dev/null 2>&1; then
echo "Stopping Next.js dev server (PID: $DEV_PID)..."
kill "$DEV_PID" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT INT TERM
cd "$PROJECT_DIR"
if ! command -v bun >/dev/null 2>&1; then
echo "ERROR: bun is not installed or not in PATH"
exit 1
fi
log_step_start "bun install"
echo "[BUN] Installing dependencies..."
bun install
log_step_end "bun install"
log_step_start "bun run db:push"
echo "[BUN] Setting up database..."
bun run db:push
log_step_end "bun run db:push"
log_step_start "Starting Next.js dev server"
echo "[BUN] Starting development server..."
bun run dev &
DEV_PID=$!
log_step_end "Starting Next.js dev server"
log_step_start "Waiting for Next.js dev server"
wait_for_service "localhost" "3000" "Next.js dev server"
log_step_end "Waiting for Next.js dev server"
log_step_start "Health check"
echo "[BUN] Performing health check..."
curl -fsS localhost:3000 >/dev/null
echo "[BUN] Health check passed"
log_step_end "Health check"
start_mini_services
echo "Next.js dev server is running in background (PID: $DEV_PID)."
echo "Use 'kill $DEV_PID' to stop it."
disown "$DEV_PID" 2>/dev/null || true
unset DEV_PID
-78
View File
@@ -1,78 +0,0 @@
#!/bin/bash
# 配置项
ROOT_DIR="/home/z/my-project/mini-services"
DIST_DIR="/tmp/build_fullstack_$BUILD_ID/mini-services-dist"
main() {
echo "🚀 开始批量构建..."
# 检查 rootdir 是否存在
if [ ! -d "$ROOT_DIR" ]; then
echo "️ 目录 $ROOT_DIR 不存在,跳过构建"
return
fi
# 创建输出目录(如果不存在)
mkdir -p "$DIST_DIR"
# 统计变量
success_count=0
fail_count=0
# 遍历 mini-services 目录下的所有文件夹
for dir in "$ROOT_DIR"/*; do
# 检查是否是目录且包含 package.json
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
project_name=$(basename "$dir")
# 智能查找入口文件 (按优先级查找)
entry_path=""
for entry in "src/index.ts" "index.ts" "src/index.js" "index.js"; do
if [ -f "$dir/$entry" ]; then
entry_path="$dir/$entry"
break
fi
done
if [ -z "$entry_path" ]; then
echo "⚠️ 跳过 $project_name: 未找到入口文件 (index.ts/js)"
continue
fi
echo ""
echo "📦 正在构建: $project_name..."
# 使用 bun build CLI 构建
output_file="$DIST_DIR/mini-service-$project_name.js"
if bun build "$entry_path" \
--outfile "$output_file" \
--target bun \
--minify; then
echo "$project_name 构建成功 -> $output_file"
success_count=$((success_count + 1))
else
echo "$project_name 构建失败"
fail_count=$((fail_count + 1))
fi
fi
done
if [ -f ./.zscripts/mini-services-start.sh ]; then
cp ./.zscripts/mini-services-start.sh "$DIST_DIR/mini-services-start.sh"
chmod +x "$DIST_DIR/mini-services-start.sh"
fi
echo ""
echo "🎉 所有任务完成!"
if [ $success_count -gt 0 ] || [ $fail_count -gt 0 ]; then
echo "✅ 成功: $success_count"
if [ $fail_count -gt 0 ]; then
echo "❌ 失败: $fail_count"
fi
fi
}
main
-65
View File
@@ -1,65 +0,0 @@
#!/bin/bash
# 配置项
ROOT_DIR="/home/z/my-project/mini-services"
main() {
echo "🚀 开始批量安装依赖..."
# 检查 rootdir 是否存在
if [ ! -d "$ROOT_DIR" ]; then
echo "️ 目录 $ROOT_DIR 不存在,跳过安装"
return
fi
# 统计变量
success_count=0
fail_count=0
failed_projects=""
# 遍历 mini-services 目录下的所有文件夹
for dir in "$ROOT_DIR"/*; do
# 检查是否是目录且包含 package.json
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
project_name=$(basename "$dir")
echo ""
echo "📦 正在安装依赖: $project_name..."
# 进入项目目录并执行 bun install
if (cd "$dir" && bun install); then
echo "$project_name 依赖安装成功"
success_count=$((success_count + 1))
else
echo "$project_name 依赖安装失败"
fail_count=$((fail_count + 1))
if [ -z "$failed_projects" ]; then
failed_projects="$project_name"
else
failed_projects="$failed_projects $project_name"
fi
fi
fi
done
# 汇总结果
echo ""
echo "=================================================="
if [ $success_count -gt 0 ] || [ $fail_count -gt 0 ]; then
echo "🎉 安装完成!"
echo "✅ 成功: $success_count"
if [ $fail_count -gt 0 ]; then
echo "❌ 失败: $fail_count"
echo ""
echo "失败的项目:"
for project in $failed_projects; do
echo " - $project"
done
fi
else
echo "️ 未找到任何包含 package.json 的项目"
fi
echo "=================================================="
}
main
-123
View File
@@ -1,123 +0,0 @@
#!/bin/sh
# 配置项
DIST_DIR="./mini-services-dist"
# 存储所有子进程的 PID
pids=""
# 清理函数:优雅关闭所有服务
cleanup() {
echo ""
echo "🛑 正在关闭所有服务..."
# 发送 SIGTERM 信号给所有子进程
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
service_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
echo " 关闭进程 $pid ($service_name)..."
kill -TERM "$pid" 2>/dev/null
fi
done
# 等待所有进程退出(最多等待 5 秒)
sleep 1
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
# 如果还在运行,等待最多 4 秒
timeout=4
while [ $timeout -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
sleep 1
timeout=$((timeout - 1))
done
# 如果仍然在运行,强制关闭
if kill -0 "$pid" 2>/dev/null; then
echo " 强制关闭进程 $pid..."
kill -KILL "$pid" 2>/dev/null
fi
fi
done
echo "✅ 所有服务已关闭"
}
main() {
echo "🚀 开始启动所有 mini services..."
# 检查 dist 目录是否存在
if [ ! -d "$DIST_DIR" ]; then
echo "️ 目录 $DIST_DIR 不存在"
return
fi
# 查找所有 mini-service-*.js 文件
service_files=""
for file in "$DIST_DIR"/mini-service-*.js; do
if [ -f "$file" ]; then
if [ -z "$service_files" ]; then
service_files="$file"
else
service_files="$service_files $file"
fi
fi
done
# 计算服务文件数量
service_count=0
for file in $service_files; do
service_count=$((service_count + 1))
done
if [ $service_count -eq 0 ]; then
echo "️ 未找到任何 mini service 文件"
return
fi
echo "📦 找到 $service_count 个服务,开始启动..."
echo ""
# 启动每个服务
for file in $service_files; do
service_name=$(basename "$file" .js | sed 's/mini-service-//')
echo "▶️ 启动服务: $service_name..."
# 使用 bun 运行服务(后台运行)
bun "$file" &
pid=$!
if [ -z "$pids" ]; then
pids="$pid"
else
pids="$pids $pid"
fi
# 等待一小段时间检查进程是否成功启动
sleep 0.5
if ! kill -0 "$pid" 2>/dev/null; then
echo "$service_name 启动失败"
# 从字符串中移除失败的 PID
pids=$(echo "$pids" | sed "s/\b$pid\b//" | sed 's/ */ /g' | sed 's/^ *//' | sed 's/ *$//')
else
echo "$service_name 已启动 (PID: $pid)"
fi
done
# 计算运行中的服务数量
running_count=0
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
running_count=$((running_count + 1))
fi
done
echo ""
echo "🎉 所有服务已启动!共 $running_count 个服务正在运行"
echo ""
echo "💡 按 Ctrl+C 停止所有服务"
echo ""
# 等待所有后台进程
wait
}
main
-135
View File
@@ -1,135 +0,0 @@
#!/bin/sh
set -e
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BUILD_DIR="$SCRIPT_DIR"
# 存储所有子进程的 PID
pids=""
# 清理函数:优雅关闭所有服务
cleanup() {
echo ""
echo "🛑 正在关闭所有服务..."
# 发送 SIGTERM 信号给所有子进程
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
service_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
echo " 关闭进程 $pid ($service_name)..."
kill -TERM "$pid" 2>/dev/null
fi
done
# 等待所有进程退出(最多等待 5 秒)
sleep 1
for pid in $pids; do
if kill -0 "$pid" 2>/dev/null; then
# 如果还在运行,等待最多 4 秒
timeout=4
while [ $timeout -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
sleep 1
timeout=$((timeout - 1))
done
# 如果仍然在运行,强制关闭
if kill -0 "$pid" 2>/dev/null; then
echo " 强制关闭进程 $pid..."
kill -KILL "$pid" 2>/dev/null
fi
fi
done
echo "✅ 所有服务已关闭"
exit 0
}
echo "🚀 开始启动所有服务..."
echo ""
# 切换到构建目录
cd "$BUILD_DIR" || exit 1
ls -lah
DEFAULT_PACKAGED_DB_PATH="/app/db/custom.db"
DEFAULT_PACKAGED_DATABASE_URL="file:$DEFAULT_PACKAGED_DB_PATH"
# 启动 Next.js 服务器
if [ -f "./next-service-dist/server.js" ]; then
echo "🚀 启动 Next.js 服务器..."
cd next-service-dist/ || exit 1
# 设置环境变量
export NODE_ENV=production
export PORT="${PORT:-3000}"
export HOSTNAME="${HOSTNAME:-0.0.0.0}"
export DATABASE_URL="${DATABASE_URL:-$DEFAULT_PACKAGED_DATABASE_URL}"
if [ "$DATABASE_URL" = "$DEFAULT_PACKAGED_DATABASE_URL" ]; then
if [ ! -f "$DEFAULT_PACKAGED_DB_PATH" ]; then
echo "❌ 未找到打包后的数据库文件 $DEFAULT_PACKAGED_DB_PATH"
echo " 为避免生产环境启动到空数据库,启动已终止"
exit 1
fi
echo "🗄️ 当前使用打包数据库: $DEFAULT_PACKAGED_DB_PATH"
else
echo "🗄️ 当前使用外部指定数据库: $DATABASE_URL"
fi
# 后台启动 Next.js
bun server.js &
NEXT_PID=$!
pids="$NEXT_PID"
# 等待一小段时间检查进程是否成功启动
sleep 1
if ! kill -0 "$NEXT_PID" 2>/dev/null; then
echo "❌ Next.js 服务器启动失败"
exit 1
else
echo "✅ Next.js 服务器已启动 (PID: $NEXT_PID, Port: $PORT)"
fi
cd ../
else
echo "⚠️ 未找到 Next.js 服务器文件: ./next-service-dist/server.js"
fi
# 启动 mini-services
if [ -f "./mini-services-start.sh" ]; then
echo "🚀 启动 mini-services..."
# 运行启动脚本(从根目录运行,脚本内部会处理 mini-services-dist 目录)
sh ./mini-services-start.sh &
MINI_PID=$!
pids="$pids $MINI_PID"
# 等待一小段时间检查进程是否成功启动
sleep 1
if ! kill -0 "$MINI_PID" 2>/dev/null; then
echo "⚠️ mini-services 可能启动失败,但继续运行..."
else
echo "✅ mini-services 已启动 (PID: $MINI_PID)"
fi
elif [ -d "./mini-services-dist" ]; then
echo "⚠️ 未找到 mini-services 启动脚本,但目录存在"
else
echo "️ mini-services 目录不存在,跳过"
fi
# 启动 Caddy(如果存在 Caddyfile
echo "🚀 启动 Caddy..."
# Caddy 作为前台进程运行(主进程)
echo "✅ Caddy 已启动(前台运行)"
echo ""
echo "🎉 所有服务已启动!"
echo ""
echo "💡 按 Ctrl+C 停止所有服务"
echo ""
# Caddy 作为主进程运行
exec caddy run --config Caddyfile --adapter caddyfile
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
-23
View File
@@ -1,23 +0,0 @@
:81 {
@transform_port_query {
query XTransformPort=*
}
handle @transform_port_query {
reverse_proxy localhost:{query.XTransformPort} {
header_up Host {host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Real-IP {remote_host}
}
}
handle {
reverse_proxy localhost:3000 {
header_up Host {host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Real-IP {remote_host}
}
}
}
-130
View File
@@ -1,130 +0,0 @@
# 回响星核 / Echo Nexus
> 原创深空考古放置策略游戏 · Original deep-space archaeology idle strategy game
你是一名深空考古指挥官,驾驶自治勘探舰回收已「飞升」的以太文明遗留的记忆晶体,解码碎片拼凑出他们的故事,并最终跨越维度发起「接触」。
---
## 🎮 在线游玩
**👉 [https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/](https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/)**
无需安装,打开即玩。支持桌面(鼠标 + 键盘)与移动端(触控)。
---
## 项目状态
- **版本****v0.7** — CrystalOrb Canvas 粒子系统 + 四维角色属性
- **栈**Next.js 16 (App Router) · TypeScript · Tailwind CSS 4 · shadcn/ui · Canvas 2D · Zustand · Web Audio API
- **仓库**[https://git.atdunbg.xyz/Super_Z/echo-nexus](https://git.atdunbg.xyz/Super_Z/echo-nexus)
- **在线版**[https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/](https://gitea-pages.atdunbg.xyz/Super_Z/echo-nexus/)
- **部署**Gitea Pages(静态导出,`BUILD_EXPORT=true next build``out/` 推送 `gh-pages` 分支)
---
## 核心特色
- **解码共振谜题**:把放置游戏的「等待」变成有节奏的主动小谜题(路径构造法生成器,100% 可解)
- **碎片涌现叙事**:故事从解码中自然浮现,5 纪元循环命名 + 模板化 lore,不打断玩法
- **深空全息美学**:晶体 + Canvas 粒子 + 共振波纹的差异化视觉(严格 emerald/rose/amber/fuchsia 四色全息色谱,零蓝色/靛色)
- **飞升多周目**:终局「接触」后保留蓝图开启新周目,星图天文台 18 个天赋跨周目永久生效
- **实时巡航玩法**Canvas 2D 飞船操控 + 8 种实体 + 3 层视差星空,打破纯放置节奏
- **角色 RPG 深度**:四维属性(探索力/智慧/勇气/灵感)影响所有游戏系统
---
## 游戏系统(10 大系统)
| # | 系统 | 简介 |
|---|------|------|
| 1 | 放置采矿 | 中央晶体球 Canvas 粒子系统 + 主动脉冲 + 连击加成 |
| 2 | 谐振解码 | 原创路径谜题,100% 可解率 |
| 3 | 技术树 | 三层科技解锁,提升产能/解码/探险 |
| 4 | 遗迹探险 | 肉鸽系统,6 种节点 + 程序化路径 + BOSS |
| 5 | 星潮事件 | 6 种动态事件,全屏叠层 + 程序化音效 |
| 6 | 星图天文台 | 飞升后 3 选 1 天赋 draft,18 个跨周目天赋 |
| 7 | 回响编年史 | 跨周目叙事时间轴,5 纪元循环命名 |
| 8 | 深空信标 | 每日挑战 + 本地排行榜 Top20 |
| 9 | 深空巡航 | Canvas 2D 实时玩法,飞船操控/陨石躲避/星门通关 |
| 10 | 角色属性 | 四维属性(探索力/智慧/勇气/灵感),飞升获得属性点 |
---
## 开发路线
| 版本 | 核心内容 | 状态 |
|------|---------|------|
| v0.1 | 放置 + 解码 + 技术树 + 图谱 + 飞升 | ✅ |
| v0.1.1 | 解码可解性修复(路径构造法) | ✅ |
| v0.2 | 遗迹探险肉鸽系统 | ✅ |
| v0.2.1 | 程序化音频 + 14 项成就 | ✅ |
| v0.3 | 星潮事件系统 | ✅ |
| v0.3.1 | 星图天文台元进程 | ✅ |
| v0.4 | 回响编年史系统 | ✅ |
| v0.5 | 深空信标系统 | ✅ |
| v0.5.1 | Gitea Pages 静态部署 + 新手教程 | ✅ |
| v0.5.2 | 离线收益报告 + 标签页 UI 重设计 | ✅ |
| v0.6 | 深空巡航 Canvas 2D 实时玩法 | ✅ |
| **v0.7** | **CrystalOrb Canvas 粒子系统 + 四维角色属性** | ✅ |
| v0.8 | 巡航玩法增强 / BOSS 关卡 / 道具掉落 | 🔜 |
| v0.9 | 云存档 + 云排行榜 | 🔜 |
| v1.0 | 全 5 纪元手写叙事 + 多人同步星潮 | 🔜 |
---
## 文档
- [`docs/01-调研报告.md`](docs/01-调研报告.md) — 游戏市场调研
- [`docs/02-游戏设计文档.md`](docs/02-游戏设计文档.md) — GDD 策划设计
- [`docs/03-技术架构.md`](docs/03-技术架构.md) — 技术架构
- [`docs/04-解码系统修复-v0.1.1.md`](docs/04-解码系统修复-v0.1.1.md) — v0.1.1 解码修复
- [`docs/05-遗迹探险系统-v0.2.md`](docs/05-遗迹探险系统-v0.2.md) — v0.2 探险系统
- [`docs/06-音频与成就系统-v0.2.1.md`](docs/06-音频与成就系统-v0.2.1.md) — v0.2.1 音频与成就
- [`docs/07-星潮事件系统-v0.3.md`](docs/07-星潮事件系统-v0.3.md) — v0.3 星潮
- [`docs/08-星图天文台系统-v0.3.1.md`](docs/08-星图天文台系统-v0.3.1.md) — v0.3.1 天文台
- [`docs/09-回响编年史系统-v0.4.md`](docs/09-回响编年史系统-v0.4.md) — v0.4 编年史
- [`docs/10-深空信标系统-v0.5.md`](docs/10-深空信标系统-v0.5.md) — v0.5 信标
---
## 本地开发
```bash
# 安装依赖
bun install
# 启动 dev 服务器(http://localhost:3000
bun run dev
# 静态导出(用于 Gitea Pages 部署)
BUILD_EXPORT=true bun run build:static
# 产物在 out/ 目录,basePath 为 /Super_Z/echo-nexus
# 代码检查
bun run lint
```
---
## 部署到 Gitea Pages
```bash
# 1. 静态导出
BUILD_EXPORT=true bun run build:static
# 2. 推送到 gh-pages 分支
cd out
git init && git checkout -b gh-pages
git add -A
git commit -m "deploy: v0.7"
git remote add origin https://git.atdunbg.xyz/Super_Z/echo-nexus.git
git push -f origin gh-pages
```
Gitea Pages 会自动从 `gh-pages` 分支提供静态网站服务。
---
© Super_Z · 持续开发中
+9
View File
@@ -0,0 +1,9 @@
1:"$Sreact.fragment"
2:I[47257,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ClientPageRoot"]
3:I[52683,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","/Super_Z/echo-nexus/_next/static/chunks/2b70cc96271374ab.js","/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js"],"default"]
6:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
7:"$Sreact.suspense"
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","async":true}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/2b70cc96271374ab.js","async":true}],["$","script","script-3",{"src":"/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"loading":null,"isPartial":false}
4:{}
5:"$0:rsc:props:children:0:props:serverProvidedParams:params"
8:null
+23
View File
@@ -0,0 +1,23 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
5:I[47257,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ClientPageRoot"]
6:I[52683,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","/Super_Z/echo-nexus/_next/static/chunks/2b70cc96271374ab.js","/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js"],"default"]
9:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"OutletBoundary"]
a:"$Sreact.suspense"
c:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
e:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
10:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/media/0acc7fdf55eb3220-s.p.532ccaa1.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["/Super_Z/echo-nexus/_next/static/media/797e433ab948586e-s.p.29207c2f.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["/Super_Z/echo-nexus/_next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
0:{"P":null,"b":"Hwdx7zmlsf4GYBBRkD4mx","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L5",null,{"Component":"$6","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@7","$@8"]}}],[["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/38c9b86c7986e4d2.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/dad63bc31a306c06.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/2b70cc96271374ab.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/Super_Z/echo-nexus/_next/static/chunks/421819b51db6b69f.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lc",null,{"children":"$Ld"}],["$","div",null,{"hidden":true,"children":["$","$Le",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$Lf"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$10",[]],"S":true}
7:{}
8:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
d:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
11:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
b:null
f:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L11","12",{}]]
+6
View File
@@ -0,0 +1,6 @@
1:"$Sreact.fragment"
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"ViewportBoundary"]
3:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"IconMark"]
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L5","12",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
+7
View File
@@ -0,0 +1,7 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","style"]
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}],["$","$L4",null,{}]]}]}]]}],"loading":null,"isPartial":false}
+6
View File
@@ -0,0 +1,6 @@
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/media/0acc7fdf55eb3220-s.p.532ccaa1.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["/Super_Z/echo-nexus/_next/static/media/797e433ab948586e-s.p.29207c2f.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
:HL["/Super_Z/echo-nexus/_next/static/media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
@@ -0,0 +1,11 @@
self.__BUILD_MANIFEST = {
"__rewrites": {
"afterFiles": [],
"beforeFiles": [],
"fallback": []
},
"sortedPages": [
"/_app",
"/_error"
]
};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()
@@ -0,0 +1 @@
[]
@@ -0,0 +1 @@
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/fef07dbb0973bf53-s.518e079e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/8a480f0b521d4e75-s.ea323500.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/53b9e256198e5412-s.853d50a3.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7178b3e590c64307-s.55554cd0.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/caa3a2e1cccd8315-s.p.3b6cae6d.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Fallback;src:local(Arial);ascent-override:95.94%;descent-override:28.16%;line-gap-override:0.0%;size-adjust:104.76%}.geist_a71539c9-module__T19VSG__className{font-family:Geist,Geist Fallback;font-style:normal}.geist_a71539c9-module__T19VSG__variable{--font-geist-sans:"Geist","Geist Fallback"}
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/5ce348bf30bf5439-s.56c1f21e.woff2)format("woff2");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/4fa387ec64143e14-s.3b336396.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/6306c77e7c8268e4-s.e3369375.woff2)format("woff2");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/7d817b4c03b0c5f1-s.a40b9a8b.woff2)format("woff2");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/bbc41e54d2fcbd21-s.fe42ddf4.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/797e433ab948586e-s.p.29207c2f.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Fallback;src:local(Arial);ascent-override:74.67%;descent-override:21.92%;line-gap-override:0.0%;size-adjust:134.59%}.geist_mono_8d43a2aa-module__8Li5zG__className{font-family:Geist Mono,Geist Mono Fallback;font-style:normal}.geist_mono_8d43a2aa-module__8Li5zG__variable{--font-geist-mono:"Geist Mono","Geist Mono Fallback"}
@font-face{font-family:Orbitron;font-style:normal;font-weight:400;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:500;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:600;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:700;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron;font-style:normal;font-weight:900;font-display:swap;src:url(../media/0acc7fdf55eb3220-s.p.532ccaa1.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Orbitron Fallback;src:local(Arial);ascent-override:81.5%;descent-override:19.59%;line-gap-override:0.0%;size-adjust:124.05%}.orbitron_da1c3699-module__wSKb_W__className{font-family:Orbitron,Orbitron Fallback;font-style:normal}.orbitron_da1c3699-module__wSKb_W__variable{--font-display:"Orbitron","Orbitron Fallback"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,95203,e=>{"use strict";var s=e.i(43476),t=e.i(71645),l=e.i(76639),a=e.i(19455),o=e.i(18533),r=e.i(43625),n=e.i(31334),i=e.i(83086),d=e.i(16715);function c({open:e,onOpenChange:c}){let x=(0,o.useGameStore)(e=>e.pendingPerkChoices),h=(0,o.useGameStore)(e=>e.constellation??[]),m=(0,o.useGameStore)(e=>e.chooseConstellationPerk),u=(0,o.useGameStore)(e=>e.rerollPerkChoices),[f,g]=(0,t.useState)(null);if((0,t.useEffect)(()=>{e||g(null)},[e]),(0,t.useEffect)(()=>{x&&x.length>0&&!e&&c(!0),(!x||0===x.length)&&e&&c(!1)},[x,e,c]),!x||0===x.length)return null;let p=(0,r.constellationProgress)(h);return(0,s.jsx)(l.Dialog,{open:e,onOpenChange:c,children:(0,s.jsxs)(l.DialogContent,{className:"max-w-md border-fuchsia-400/30 bg-gradient-to-br from-[#0a0820]/95 to-[#1a0f30]/95 backdrop-blur-md",children:[(0,s.jsxs)(l.DialogHeader,{children:[(0,s.jsxs)(l.DialogTitle,{className:"flex items-center gap-2 text-fuchsia-200",children:[(0,s.jsx)(i.Sparkles,{className:"h-4 w-4"}),"星图觉醒"]}),(0,s.jsxs)(l.DialogDescription,{className:"text-muted-foreground text-xs",children:["飞升带来的回响在星图上凝聚。从 3 道星辉中选择 1 道,永久获得其加成。",(0,s.jsx)("br",{}),(0,s.jsxs)("span",{className:"text-fuchsia-300/80",children:["当前已觉醒 ",p.unlocked,"/",p.total]})]})]}),(0,s.jsx)("div",{className:"grid grid-cols-1 gap-2 mt-1",children:x.map(e=>{let t=(0,r.getPerk)(e);if(!t)return null;let l=r.CONSTELLATION_CATEGORY_META[t.category],a=f===e;return(0,s.jsxs)("button",{onClick:()=>{g(e),m(e)&&((0,n.sfx)("constellation"),setTimeout(()=>c(!1),500))},disabled:!!f,className:`group relative w-full text-left rounded-lg border px-3 py-2.5 transition-all overflow-hidden ${a?"border-fuchsia-400/80 bg-fuchsia-500/10 scale-[1.02]":f?"border-white/5 opacity-40":"border-white/10 hover:border-white/30 hover:bg-white/5"}`,style:{boxShadow:a?`0 0 20px ${l.glow}`:void 0},children:[(0,s.jsx)("div",{className:"absolute left-0 top-0 bottom-0 w-1",style:{background:l.hex,boxShadow:`0 0 8px ${l.hex}`}}),(0,s.jsxs)("div",{className:"pl-2 flex items-start gap-3",children:[(0,s.jsx)("div",{className:"flex-shrink-0 mt-0.5 h-8 w-8 rounded-full flex items-center justify-center text-sm font-bold",style:{background:`${l.hex}20`,color:l.hex,border:`1px solid ${l.hex}40`},children:l.name.charAt(0)}),(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-0.5",children:[(0,s.jsx)("span",{className:"text-sm font-bold text-foreground",children:t.name}),(0,s.jsx)("span",{className:"text-[9px] px-1 py-0.5 rounded font-mono",style:{background:`${l.hex}20`,color:l.hex},children:l.name})]}),(0,s.jsx)("div",{className:"text-[11px] text-muted-foreground",children:t.desc})]})]}),a&&(0,s.jsx)("div",{className:"absolute inset-0 pointer-events-none",children:[...Array(8)].map((e,t)=>(0,s.jsx)("span",{className:"absolute h-1 w-1 rounded-full animate-ping",style:{background:l.hex,left:`${20+8*t}%`,top:`${30+t%3*20}%`,animationDelay:`${50*t}ms`,animationDuration:"800ms"}},t))})]},e)})}),(0,s.jsxs)("div",{className:"flex items-center justify-between mt-3 pt-2 border-t border-white/5",children:[(0,s.jsxs)(a.Button,{variant:"ghost",size:"sm",onClick:()=>{u(),(0,n.sfx)("uiClick")},disabled:!!f,className:"text-muted-foreground hover:text-foreground h-7 text-[11px]",children:[(0,s.jsx)(d.RefreshCw,{className:"h-3 w-3 mr-1"}),"重新抽取"]}),(0,s.jsx)("span",{className:"text-[10px] text-muted-foreground/60",children:"✦ 选择后永久保留,跨周目生效"})]})]})})}e.s(["ConstellationDialog",()=>c])},76103,e=>{e.n(e.i(95203))}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"OutletBoundary"]
6:"$Sreact.suspense"
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"ViewportBoundary"]
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"MetadataBoundary"]
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","style"]
0:{"P":null,"b":"Hwdx7zmlsf4GYBBRkD4mx","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
d:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"IconMark"]
7:null
b:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$Ld","12",{}]]
+6
View File
@@ -0,0 +1,6 @@
1:"$Sreact.fragment"
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"ViewportBoundary"]
3:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"MetadataBoundary"]
4:"$Sreact.suspense"
5:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"IconMark"]
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","rsc":["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$L5","12",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"loading":null,"isPartial":false}
+7
View File
@@ -0,0 +1,7 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","style"]
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","precedence":"next"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","async":true}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}],["$","$L4",null,{}]]}]}]]}],"loading":null,"isPartial":false}
@@ -0,0 +1,5 @@
1:"$Sreact.fragment"
2:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"OutletBoundary"]
3:"$Sreact.suspense"
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","rsc":["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],null,["$","$L2",null,{"children":["$","$3",null,{"name":"Next.MetadataOutlet","children":"$@4"}]}]]}],"loading":null,"isPartial":false}
4:null
+4
View File
@@ -0,0 +1,4 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","rsc":["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","template":["$","$L3",null,{}]}]]}],"loading":null,"isPartial":false}
+3
View File
@@ -0,0 +1,3 @@
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","style"]
0:{"buildId":"Hwdx7zmlsf4GYBBRkD4mx","tree":{"name":"","paramType":null,"paramKey":"","hasRuntimePrefetch":false,"slots":{"children":{"name":"/_not-found","paramType":null,"paramKey":"/_not-found","hasRuntimePrefetch":false,"slots":{"children":{"name":"__PAGE__","paramType":null,"paramKey":"__PAGE__","hasRuntimePrefetch":false,"slots":null,"isRootLayout":false}},"isRootLayout":false}},"isRootLayout":true},"staleTime":300}
File diff suppressed because one or more lines are too long
+16
View File
@@ -0,0 +1,16 @@
1:"$Sreact.fragment"
2:I[39756,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
3:I[37457,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
4:I[77855,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"Toaster"]
5:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"OutletBoundary"]
6:"$Sreact.suspense"
8:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"ViewportBoundary"]
a:I[97367,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"MetadataBoundary"]
c:I[68027,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"default"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","style"]
:HL["/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","style"]
0:{"P":null,"b":"Hwdx7zmlsf4GYBBRkD4mx","c":["","_not-found",""],"q":"","i":false,"f":[[["",{"children":["/_not-found",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/59368da722d2753f.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/Super_Z/echo-nexus/_next/static/chunks/5c53b7dbef70e8a3.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"zh-CN","suppressHydrationWarning":true,"className":"dark","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable orbitron_da1c3699-module__wSKb_W__variable antialiased bg-background text-foreground","children":[["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}],["$","$L4",null,{}]]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:0:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],null,["$","$L5",null,{"children":["$","$6",null,{"name":"Next.MetadataOutlet","children":"$@7"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],["$","$1","h",{"children":[["$","meta",null,{"name":"robots","content":"noindex"}],["$","$L8",null,{"children":"$L9"}],["$","div",null,{"hidden":true,"children":["$","$La",null,{"children":["$","$6",null,{"name":"Next.Metadata","children":"$Lb"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$c","$undefined"],"S":true}
9:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
d:I[27201,["/Super_Z/echo-nexus/_next/static/chunks/3059a9b053b6a3de.js","/Super_Z/echo-nexus/_next/static/chunks/33d3f2c0cda968c7.js","/Super_Z/echo-nexus/_next/static/chunks/4d383bb4be112933.js"],"IconMark"]
7:null
b:[["$","title","0",{"children":"回响星核 · Echo Nexus"}],["$","meta","1",{"name":"description","content":"原创深空考古放置策略游戏 — 解码记忆晶体,拼凑以太文明的回响,跨越维度发起接触。"}],["$","meta","2",{"name":"author","content":"Super_Z"}],["$","meta","3",{"name":"keywords","content":"Echo Nexus,回响星核,idle game,browser game,放置游戏,解谜,深空考古"}],["$","meta","4",{"property":"og:title","content":"回响星核 · Echo Nexus"}],["$","meta","5",{"property":"og:description","content":"原创深空考古放置策略游戏"}],["$","meta","6",{"property":"og:site_name","content":"Echo Nexus"}],["$","meta","7",{"property":"og:type","content":"website"}],["$","meta","8",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","9",{"name":"twitter:title","content":"回响星核 · Echo Nexus"}],["$","meta","10",{"name":"twitter:description","content":"原创深空考古放置策略游戏"}],["$","link","11",{"rel":"icon","href":"https://z-cdn.chatglm.cn/z-ai/static/logo.svg"}],["$","$Ld","12",{}]]
-91
View File
@@ -1,91 +0,0 @@
# Task ID: 10-a — 限时挑战 + 星潮类型深化
**Agent**: full-stack-developer
**Task**: 工单 #5 限时挑战 + 工单 #3 星潮类型深化
## Work Log
### 前置准备
- 阅读 `worklog.md` 了解 v0.8.1 项目状态(10 大系统,四色全息 emerald/rose/amber/fuchsia
- 精读 `beacon.ts`(873 行,日挑战+周挑战+信标链)、`BeaconPanel.tsx`753 行)、`starTide.ts`209 行,6 种星潮)、`gameStore.ts` trackBeacon~170 行)+ tickTide~440 行)
- 确认现有 UI 均动态读取 `TIDE_EVENTS[type]`,新增类型自动生效
### 工单 #5:限时挑战(Timed Challenge
**beacon.ts 扩展**873 → 1162 行,+289 行):
- `BeaconScoreEntry` 新增 `isTimed?: boolean` 字段
- `BeaconTimedChallenge` 接口:slotKey / type / difficulty(固定 routine) / goal / rewardInsight / rewardContact / seed / title / desc / expiresAt
- `BeaconTimedProgress` 接口:slotKey / progress / startedAt / completedAt / claimed / durationSec
- `TIMED_SLOT_MS = 4h``BEACON_TIMED_KEY = "echo-nexus-beacon-timed-v1"`
- `getTimedSlotKey(now)`UTC 0/4/8/12/16/20 点切分,格式 "timed_YYYY-MM-DD_HH"
- `timedSlotKeyToSeed`FNV-1a 哈希
- `generateTimedChallenge(now)`:确定性生成,难度 routinegoal = 日基准 ×0.3-0.5,5 种类型各有公式
- `msUntilNextTimedSlot(now)`:距离下个 4 小时时段的毫秒数
- `loadTimedProgress` / `saveTimedProgress` / `addTimedProgress` / `claimTimedReward`(推送排行榜,entry 加 isTimed: true
**gameStore.ts 集成**
- import 扩展:generateTimedChallenge / loadTimedProgress / addTimedProgress / claimTimedReward + 类型
- `trackBeacon` 返回值新增 `timedJustCompleted`:日/限时/周三类进度同时更新
- 新增 `claimTimedBeacon` actionGameActions 接口同步)
- 新增 `timedJustCompleted` 到 trackBeacon 返回类型
**BeaconPanel.tsx UI**753 → 944 行):
- import 扩展:generateTimedChallenge / loadTimedProgress / getTimedSlotKey / msUntilNextTimedSlot / Hourglass 图标
- 新增 timedChallenge/timedProgress/timedCountdown state
- 每秒刷新 timedProgress + timedCountdown
- 新增 handleClaimTimed 回调
- **限时挑战区块**(amber 主题,插入日挑战与周挑战之间):
- Hourglass 图标 + "限时挑战 · TIMED" 标题 + slotKey + 倒计时
- 难度/类型标签 + 标题 + 描述 + 进度条 + 奖励 + 领取按钮(emerald 主题)
- **紧急状态**:距时段结束 <30min 时切换 rose 脉冲动画 + "即将结束"徽章
- 2 个新动画:timed-glowamber 呼吸)/ timed-urgentrose 急促脉冲)
- 排行榜 entry 区分 TIMEDamber 徽章)/ WEEKfuchsia 徽章)
- 空状态提示更新为"完成限时、每日或本周信标即可登榜"
### 工单 #3:星潮类型深化
**starTide.ts 扩展**209 → 289 行):
- `TideType` 新增 3 种:surge / eclipse / prism
- `TideModifiers` 新增 3 个可选字段:targetLenBonus / bossWinRateBonus / autoDecodeIntervalMult
- `TIDE_EVENTS` 新增 3 种元信息(name/desc/color/glow/icon/negative):
- surge(涌动星潮,emerald #34d399,⇈):产能 ×2.5 但解码目标 +2
- eclipse(蚀相星潮,rose #fb7185,◐):BOSS 胜率 +20% 但产能 -30%
- prism(棱镜星潮,fuchsia #e879f9,◬):洞见 ×2 + 自动解码周期 -30%
- `TIDE_WEIGHTS` 新增 surge/eclipse/prism 各 12(总权重 136
- `getTideModifiers` 新增 3 个 case + null 分支补全新字段
- 注释更新:"6 种类型" → "9 种类型"
**decode.ts 扩展**
- `generatePuzzle` 新增第 3 参数 `targetLenBonus`(默认 0
- targetLen = cfg.targetLen + targetLenBonusstepLimitBase 同步增加
- 路径构造法自动适配更长目标序列(300 次尝试 + 线性兜底)
**gameStore.ts 集成**3 处修饰器应用):
- `startDecode`surge 星潮时传入 `tideMod.targetLenBonus` → 谜题更长
- `autoDecodeTick`prism 星潮时 `interval *= tideMod.autoDecodeIntervalMult`(×0.7
- `resolveCurrentNode`eclipse 星潮时 `b = am.bossWinRateBonus + tideMod.bossWinRateBonus`+20%
**achievements.ts**`ach_tides_all` 阈值 6 → 9,描述"经历全部 9 种星潮事件"
**page.tsx**StatsPanel "星潮亲历" 显示 `/ 9`
### QA 验证
1. **lint**`bun run lint` 零错误(exit 0
2. **dev 服务器**HTTP 200,编译 < 30ms
3. **BeaconPanel VLM 评分**
- 首屏 8.5/10:限时挑战 TIMED 区块可见、四色一致、倒计时/进度/标题/奖励/按钮清晰
- 最终截图 9/10:4 个挑战区块齐全、限时区块带倒计时、无蓝色
4. **新星潮 VLM 评分**localStorage 注入 + reload 触发):
- surgeemerald 绿):8/10,横幅 + 全屏绿色微光
- prismfuchsia 紫):7/10,横幅 + 全屏紫色微光
- eclipserose 粉):8/10(单图),横幅 + 全屏粉色微光
5. **不破坏现有功能**:日挑战/周挑战/信标链/6 旧星潮全部保留,排行榜正常
## Stage Summary
- ✅ 工单 #5 限时挑战完成:beacon.ts +289 行 + gameStore claimTimedBeacon + BeaconPanel amber 区块
- ✅ 工单 #3 星潮深化完成:starTide.ts +80 行(3 新类型)+ decode.ts targetLenBonus + gameStore 3 处修饰器
- ✅ 确定性生成:限时挑战同时段同种子同结果(FNV-1a slotKey 哈希)
- ✅ 严格四色规范:限时 amber、surge emerald、eclipse rose、prism fuchsia,零蓝色/靛色
- ✅ 紧急状态设计:限时挑战 <30min 时 rose 脉冲 + "即将结束"徽章
- ✅ lint 零错误 + dev HTTP 200 + VLM ≥7/10(信标 9/10、surge 8/10、prism 7/10、eclipse 8/10
- ✅ 现有日/周/信标链/6 旧星潮全部保留,无回归
-207
View File
@@ -1,207 +0,0 @@
# Task 10-b · full-stack-developer · P2/#4 云排行榜 mini-service
> 本文件为本 agent 在 Task 10-b 的工作记录,供后续 agent 查阅。
## 任务概述
为「回响星核 / Echo Nexus」v0.8.2 实现 P2/#4 工单:把信标本机排行榜(纯 localStorage)升级为云端 Top100,让玩家间可以比较分数。
## 阅读的前置工作
- `/home/z/my-project/worklog.md`(v0.8.1 项目状态,10 大系统,四色全息规范)
- `/home/z/my-project/src/lib/game/beacon.ts`(实际 1161 行 — worklog 信息滞后,v0.8.2 已加限时挑战 `claimTimedReward`
- `/home/z/my-project/src/components/game/BeaconPanel.tsx`(实际 953 行 — 已含限时挑战 amber 主题)
- `/home/z/my-project/src/store/gameStore.ts`1241 行 — trackBeacon + 3 个 claim actiongrantBeaconReward/claimWeeklyBeacon/claimTimedBeacon/claimChainReward
- `/home/z/my-project/Caddyfile`:81 网关,按 ?XTransformPort=* 转发到对应端口)
- `/home/z/my-project/examples/websocket/server.ts` + `frontend.tsx`mini-service + 前端连接参考)
- `/home/z/my-project/.zscripts/dev.sh`mini-service 自动扫描启动逻辑,发现 mini-services 目录下子服务自动 `bun run dev`
- `/home/z/my-project/agent-ctx/9-b-full-stack-developer.md`(前一个 agent 的信标系统扩展记录)
- `/home/z/my-project/mini-services/star-tide-service/`(已存在的 P3 #10-c 服务,端口 3031
## 实现细节
### 1. mini-service`mini-services/leaderboard-service/`
**目录结构**
```
mini-services/leaderboard-service/
├── package.json # 独立 bun 项目,type=module
├── index.ts # Hono 入口(182 行)
├── bun.lock # 自动生成
├── node_modules/ # hono 独立安装
└── README.md # API 文档
```
**package.json**
- `"dev": "bun --hot index.ts"`(规范要求的热重载脚本)
- `"start": "bun index.ts"`(无热重载,用于稳定运行)
- 依赖:hono ^4.6.14
**index.ts 设计要点**
- Hono + `cors()` 中间件,origin: * 全开放(前端跨端口必须)
- 内存数组 `entries: BeaconScoreEntry[]`,最多 1000 条,按 score 降序
- 同分排序:先看用时短,再看提交早
- 防刷:`lastSubmitAt: Map<dateKey|challenge, ts>`,10 秒内只接受 1 次,返回 429 + retryAfterMs
- 4 个路由:
- `GET /` → 健康检查 `{service, version, ok, uptime}`
- `GET /api/leaderboard` → Top100 `{entries, total}`
- `POST /api/leaderboard` body `{entry}` → schema 校验 → 推入 → 排序 → `{entries, total, rank}`
- `GET /api/leaderboard/stats``{totalSubmissions, uniquePlayers, topScore}`
- OPTIONS 预检由 hono/cors 自动处理,返回 204 + 完整 CORS 头
- 端口固定 3030`export default { port, fetch }` 标准 bun 模式
**启动调试经验**
- `bun --hot` 在本沙盒环境下不稳定,多次启动后约 5-10s 内被 kill(即使 setsid + nohup
- 解决方案:用 `bun index.ts`(无 --hot+ 双 fork 模式:`( bun index.ts >/tmp/log 2>&1 & )`
- 双 fork 后 PPID=1reparent 到 init),进程脱离原 bash sessionuptime 4+ 分钟稳定
### 2. beacon.ts 扩展(1161 → 1290 行,+129 行)
**三个 claim 函数返回值扩展(非破坏性)**:
- `claimBeaconReward` 返回值新增 `entry: BeaconScoreEntry`
- `claimWeeklyReward` 返回值新增 `entry: BeaconScoreEntry`
- `claimTimedReward` 返回值新增 `entry: BeaconScoreEntry`
- 用途:让前端 / gameStore 能拿到完整 entry 提交到云端
**新增云排行榜模块**
- `BEACON_CLOUD_PORT = 3030` 常量
- `BEACON_CLOUD_LAST_SUBMIT_KEY = "echo-nexus-beacon-cloud-last-submit-v1"` localStorage key
- 3 个响应接口:`CloudLeaderboardResponse` / `CloudSubmitResponse` / `CloudStatsResponse`
- `loadLastCloudSubmitTimestamp()` / `saveLastCloudSubmitTimestamp(ts)`
- `fetchCloudLeaderboard(): Promise<BeaconScoreEntry[]>` — GET `/api/leaderboard?XTransformPort=3030`
- `submitCloudScore(entry): Promise<number>` — POST,成功返回 rank ≥ 1,失败返回 -1,自动记录 ts
- `fetchCloudStats(): Promise<CloudStatsResponse | null>`
- **所有 fetch 用相对路径 + ?XTransformPort=3030,禁止 localhost:3030**
- try-catch 包裹,失败时静默返回空/null,不抛异常
### 3. gameStore.ts 集成
- import 新增 `submitCloudScore`
- `claimWeeklyBeacon` action:领奖成功后 `void submitCloudScore(res.entry)`fire-and-forget,不 await,不阻塞)
- `claimTimedBeacon` action:同上
- `claimBeaconReward` (daily) 由 BeaconPanel 直接调用,不通过 gameStore
### 4. BeaconPanel.tsx 重写(953 → 1170 行,+217 行)
**新增 import**
- `fetchCloudLeaderboard` / `submitCloudScore` / `loadLastCloudSubmitTimestamp` from beacon
- 4 个 lucide 图标:`RefreshCw` / `Globe` / `WifiOff` / `Loader2`
- `useRef` from react
**新增状态**
- `lbTab: 'local' | 'global'`(默认 local
- `cloudEntries: BeaconScoreEntry[]`
- `cloudLoading: boolean`
- `cloudError: string | null`
- `cloudFetched: boolean`
- `mySubmitTs: number`
- `cloudFetchingRef` (防并发)
**新增逻辑**
- `refreshCloudLeaderboard` callback:拉取云端榜 + 防并发 + 空 entries 时显示"成为首位登顶者"
- useEffect:切到全球 tab 自动拉取(仅首次)+ 初始化读取 mySubmitTs
- `handleClaimDaily`:领奖后 `void submitCloudScore(res.entry).then(rank => ...)` — 若用户在全球 tab 自动刷新
- `handleClaimTimed` / `handleClaimWeekly`:领奖后更新 mySubmitTs + 若在全球 tab 自动刷新
**排行榜 UI 重写为双 tab**
- 顶部 tab 切换:「本地」(amber 主题) / 「全球」(emerald 主题) — 用 `<button aria-pressed>` 实现可访问性
- 全球 tab 专属刷新按钮(RefreshCwloading 时 animate-spin
- 子标题:本地"本机 Top N · 离线可用" / 全球"全球 Top N · 已同步 / 同步中…"
- 本地榜:保留原 v0.8.2 渲染(max-h-200px overflow-y-auto
- 全球榜:
- 加载中:Loader2 spinner + "正在拉取全球榜…"
- 空榜 + 错误:WifiOff 图标 + 错误文案 + 重试按钮
- 空榜 + 无错:Globe 图标 + "尚无全球记录。完成挑战并领取即可登顶全球榜!"
- 有数据:渲染前 100 条,每条带排名(1-3 名用 Crown/Medal/Award 图标)/ 难度色 / WEEK·TIMED 徽章 / 用时 / 分数
- **玩家自己的记录高亮**:通过 `entry.timestamp === mySubmitTs` 匹配
- emerald 边框 (`border-emerald-500/40`)
- emerald 阴影 (`shadow-[0_0_12px_rgba(52,211,153,0.25)]`)
- YOU 徽章 (`bg-emerald-500/25 text-emerald-200 border-emerald-500/50`)
- emerald 分数色 (`color: #34d399`)
- 加粗排名
### 5. eslint 配置
- `eslint.config.mjs` 新增 ignores `"mini-services/**"`
- 原因:mini-service 是独立 bun 项目,有自己的 TypeScript 配置和 lint 规范,不应参与主项目 lint
## QA 验证结果
### 1. lint
- `bun run lint` 零错误零警告
### 2. dev 服务器
- dev.log 全程无错误,所有编译 < 300ms
- 注:dev.log 中显示 `/api/leaderboard?XTransformPort=3030 404` 是预期行为 — 通过 :3000 直接访问时 Next.js 没有 /api/leaderboard 路由;通过 :81 网关访问时 Caddy 转发到 :3030 正常返回 200
### 3. mini-service curl 全通过
- `GET /` → 200 `{"service":"echo-nexus-leaderboard","version":"v0.8.2","ok":true,"uptime":N}`
- `GET /api/leaderboard` (空) → 200 `{"entries":[],"total":0}`
- `POST /api/leaderboard` → 200 `{"entries":[...],"total":1,"rank":1}`
- `GET /api/leaderboard` (有数据) → 200 `{"entries":[...],"total":N}`
- `GET /api/leaderboard/stats` → 200 `{"totalSubmissions":N,"uniquePlayers":M,"topScore":X}`
- `OPTIONS /api/leaderboard` 预检 → 204 + 完整 CORS 头(Allow-Origin: *
- 重复 POST(同 dateKey+challenge 10s 内)→ 429 `{"error":"Rate limited","retryAfterMs":N}`
### 4. 网关路由验证
- Caddy :81 → localhost:3030 通过 `?XTransformPort=3030` 正确转发,返回 JSON 200
### 5. agent-browser 集成测试
- 通过 :81 端口访问(绕过 Next.js :3000 的 404
- 信标 tab 选中(用 KeyboardEvent Enter 激活 Radix Tabs — 因 pointerdown 被覆盖层拦截)
- 切到「全球」tab → 自动拉取 → 显示"全球 Top 6 · 已同步" → 渲染 6 条记录
- 刷新按钮可点击,旋转动画正常
- POST 测试:浏览器 fetch 提交 score=9999 → 返回 rank=1 → 刷新后榜首显示 10.00K
- YOU 高亮:手动设置 localStorage[BEACON_CLOUD_LAST_SUBMIT_KEY] 后刷新 → 榜首显示 emerald 边框 + YOU 徽章
### 6. VLM 视觉评分(目标 ≥7/10)
- 全球榜含 YOU 高亮:**8/10**
- VLM 评语:"双tab切换清晰,全球榜记录完整,难度色与标签规范,视觉层次佳,但「YOU」徽章未突出显示"
- 本地榜:**8/10**
- VLM 评语:"双tab切换逻辑清晰,本地tab内容完整呈现;四色规范应用,视觉区分度高;信息层级合理"
## 修改的文件
1. `mini-services/leaderboard-service/package.json`(新建)
2. `mini-services/leaderboard-service/index.ts`(新建,182 行)
3. `mini-services/leaderboard-service/README.md`(新建)
4. `src/lib/game/beacon.ts` — 1161 → 1290 行(+129 行:3 个 claim 返回 entry + 云排行榜模块)
5. `src/store/gameStore.ts` — 1235 → 1252 行(+17 行:import + 2 个 claim action 加 submitCloudScore
6. `src/components/game/BeaconPanel.tsx` — 953 → 1170 行(+217 行:双 tab UI + 云端拉取 + 提交 + YOU 高亮)
7. `eslint.config.mjs` — ignores 新增 "mini-services/**"
## 不破坏的现有功能
- 本地排行榜(`loadLeaderboard` / `pushLeaderboardEntry`)完整保留作为离线 fallback
- 日挑战 / 周挑战 / 限时挑战 / 信标链流程全部保留
- 现有 localStorage keysecho-nexus-beacon-lb-v1 / -prog-v1 / -weekly-v1 / -chain-v1 / -timed-v1)全部保留
- 主项目 package.json 不变(hono 装在 mini-service 子项目内)
## 新增的 localStorage keys
- `echo-nexus-beacon-cloud-last-submit-v1`(上次成功提交到云端的 entry timestamp,用于全球榜高亮)
## 四色全息规范遵循
- 本地 tabamber (#fbbf24)
- 全球 tabemerald (#34d399) — 与"在线/同步"语义一致
- 玩家高亮:emerald 边框 + emerald YOU 徽章 + emerald 分数色
- 难度色保留:routine emerald / anomaly amber / singular rose
- 周挑战徽章:fuchsia
- 限时挑战徽章:amber
- 错误状态:rose (#fb7185)
- **零蓝色/靛色违规**
## 截图资产
- `/home/z/my-project/agent-ctx/beacon-global-leaderboard.png` — 全球榜初始视图(6 条记录)
- `/home/z/my-project/agent-ctx/beacon-global-with-you.png` — 全球榜含 YOU 高亮(7 条记录,榜首是玩家)
- `/home/z/my-project/agent-ctx/beacon-local-leaderboard.png` — 本地榜视图
## 重要注意事项(供后续 agent)
1. **前端访问端口**:用户通过 Preview Panel 看到的是 Caddy 网关 :81,不是 Next.js :3000。所以 `fetch('/api/leaderboard?XTransformPort=3030')` 在用户视角下能正常路由。如果直接访问 :3000 会 404(这是预期行为)。
2. **mini-service 启动方式**:用 `bun index.ts`(无 --hot+ 双 fork `( ... & )` 模式启动。`bun --hot` 在沙盒下不稳定。
3. **mini-service 已被 .zscripts/dev.sh 自动扫描**:下次容器重启会自动启动。但当前会话需手动启动一次。
4. **localStorage key 命名**`echo-nexus-beacon-cloud-last-submit-v1` 而非 `echo-nexus-beacon-cloud-lb-v1`,前者存的是单个 timestamp(用于高亮),后者若需要存全球榜快照可用后者(目前未实现,按需拉取)。
5. **star-tide-service 端口 3031**:另一个 P3 工单已存在的服务,本服务的端口 3030 与之互不冲突。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

-52
View File
@@ -1,52 +0,0 @@
# Task 6: 开发 Canvas 2D 深空巡航实时玩法
## Agent: full-stack-developer
## 完成状态: ✅ 全部完成
## 创建的文件
1. `/home/z/my-project/src/lib/game/cruise.ts` — 逻辑层(~520 行纯 TS
2. `/home/z/my-project/src/components/game/CruiseMode.tsx` — Canvas 2D 渲染层(~830 行)
## 修改的文件
1. `/home/z/my-project/src/app/page.tsx` — 新增巡航入口按钮 + CruiseMode 渲染
## 关键实现细节
### cruise.ts 逻辑层
- **类型系统**CruiseEntity 联合类型(8 种实体:ship/asteroid/storm/crystal/insight/beacon/stargate/particle),完整 CruiseState/Phase/RunResult/Stats/Input
- **关卡生成**mulberry32 + FNV-1a 种子化 RNG;陨石(8+level×2)、风暴(1+level/2)、晶体(5+level)、洞见(2+level/3)、信标(1+level/4)、星门;时长 60-90s
- **物理**:飞船 8 方向加速度+摩擦+限速+边界反弹;陨石碰撞扣盾+击退+600ms 无敌;风暴持续 DPS;收集物吸引半径 90px 自动吸入
- **奖励**crystals=碎片×(8+lvl×2)、insights=洞见×(2+lvl×0.5)、contact=信标×(1.5+lvl×0.3);通关×1.5、失败×0.5
- **localStorage**:独立 key `echo-nexus-cruise-v1`,记录最高分/通关数/最高关卡/累计奖励/最近 20 局
### CruiseMode.tsx 渲染层
- **全屏 Canvas**fixed inset-0 z-50DPR cap 2resize 监听
- **3 层视差星空**:远/中/近 100/60/30 星,独立漂移+飞船速度视差+闪烁
- **实体辉光绘制**:全部使用 ctx.shadowBlur;飞船三角形+尾焰渐变;陨石不规则多边形;风暴 5 层云团+闪电;晶体菱形+脉冲环;洞见球体+漩涡;信标光束+旋转环;星门 4 层旋涡+吸入粒子
- **粒子系统**:尾焰/收集/碰撞/烟花,上限 200
- **屏幕震动**:受击 280ms 衰减抖动
- **低护盾警告**shield<30% 屏幕边缘 rose 脉冲边框
- **HUD**HTML 叠层(非 canvas),glass+backdrop-blur;护盾/能量/分数/用时/收集计数;80ms 节流更新
- **控制**:桌面 WASD/方向键;移动端 floating 虚拟摇杆;P 暂停;Esc 退出;失焦自动暂停
- **奖励同步**:结算时 grantCruiseReward + recordRunrewardGrantedRef 防重
### page.tsx 集成
- header 按钮区新增「巡航」按钮(Navigation 图标,amber 主题)
- 新增 cruiseOpen state
- 组件末尾渲染 {cruiseOpen && <CruiseMode onClose={...} />}
- 未破坏现有 7 标签页和其他功能
## QA 验证结果
- ✅ lint 零错误
- ✅ 巡航按钮出现在顶部 header
- ✅ 点击进入全屏 Canvas,ready 界面完整
- ✅ WASD 控制飞船移动,按 W 直冲星门通关
- ✅ 收集 2 晶体 → 奖励 +30(2×10×1.5=30 计算正确)
- ✅ localStorage 正确记录 highScore/totalWins/bestLevel
- ✅ 奖励同步到 gameStore
- ✅ 退出返回主界面,7 标签页完好
- ✅ 移动端 viewport 测试通过
## 色彩遵循
emerald(#34d399) / rose(#fb7185) / amber(#fbbf24) / fuchsia(#e879f9) 四色全息色谱,零蓝色/靛色
-103
View File
@@ -1,103 +0,0 @@
# Task 7: 角色属性系统(探索力/智慧/勇气/灵感)
## Agent: full-stack-developer
## 完成状态: ✅ 全部完成
## 创建的文件
1. `/home/z/my-project/src/lib/game/attributes.ts` — 属性逻辑层(~330 行)
2. `/home/z/my-project/src/components/game/AttributesPanel.tsx` — 属性 UI 面板(~330 行)
## 修改的文件
1. `/home/z/my-project/src/lib/game/types.ts` — GameState 新增 attributes/attributeProgress/pendingAttrPoints
2. `/home/z/my-project/src/lib/game/config.ts` — INITIAL_STATE 补全新字段默认值
3. `/home/z/my-project/src/lib/game/engine.ts` — recomputeStats 聚合属性加成;performPrestige 发放属性点
4. `/home/z/my-project/src/store/gameStore.ts` — 新增 allocateAttribute/gainAttributeExp actionpulse/clickNode/autoDecodeTick/resolveCurrentNode/grantCruiseReward/tickTide/init 接入属性逻辑;旧存档兼容
5. `/home/z/my-project/src/lib/game/achievements.ts` — 新增 2 项属性成就
6. `/home/z/my-project/src/app/page.tsx` — 新增第 8 个「角色」标签页 + grid-cols-7→8 + 红点提示 + 统计面板新增属性行 + 版本号 v0.6→v0.7
## 关键实现细节
### attributes.ts 逻辑层
- **类型系统**AttributeKey4 个键)/ CharacterAttributes0-100 数值)/ AttributeProgressEntryexp + level
- **加成公式**
- 0-50 线性区:每点 +0.5% 加成(0..50 → 0..25%
- 50-100 递减区:每点 +0.2% 加成(50..100 → 25..35%
- 超过 100 仍按 100 计算加成(软上限)
- **getAttributeBonus(attr)** 单属性加成百分比
- **getAllBonuses(attrs)** 返回 12 个修饰器:探险力倍率/巡航速度/解码步数/洞见倍率/自动解码周期/探险生命/BOSS 胜率/巡航护盾/接触率/星潮触发/脉冲连击/产能加成
- **levelUpCheck(progress)** 自动跨多级升级,安全上限 200 次循环
- **expRequiredForLevel(level) = 10 × level**(最低 10
- **computePrestigeAttrPoints(ascensions) = ascensions × 2 + 1**(飞升前次数计算)
- **migrateAttributes(state)** 旧存档兼容:补全缺失字段、夹紧越界值、同步 level 与 attributes
### engine.ts 改造
- recomputeStats 末尾追加 `getAllBonuses(state.attributes ?? {})` 聚合:
- `crystalsPerSec *= am.crystalsPerSecMult`
- `insightMult += am.insightMultAdd`
- `contactRateMult *= am.contactRateMult`
- `decodeStepsBonus += am.decodeStepsBonus`
- performPrestige
- 保留 attributes 数值(跨周目永久)
- 清空 attributeProgress(新周目重新累积经验)
- pendingAttrPoints += computePrestigeAttrPoints(ascensions) = ascensions × 2 + 1
- createInitialState:每次返回全新 attributes/attributeProgress 对象,避免引用共享
### gameStore.ts 集成(核心)
- **pulse**:连击 ≥3 给灵感经验(expGain = 1 + floor(combo/2));脉冲威力乘以灵感连击加成
- **clickNode** 完成:给智慧经验(expGain = puzzle.tier × 2
- **autoDecodeTick**:自动解码也给智慧经验 +1;自动解码周期受智慧 am.autoDecodeIntervalMult 影响
- **startExpedition**:探险力乘 am.expeditionPowerMult;探险生命加 am.expeditionHpBonus
- **resolveCurrentNode**
- BOSS 节点用包装 RNG 提升 +am.bossWinRateBonus 胜率(单次 rng 调用,B% 概率返回 0,其余返回 r-B 保持均匀分布)
- 战斗胜利给勇气+探索力经验(中途战斗 +2/+1,BOSS 击破 +8/+6,探险胜利 +4
- **grantCruiseReward**:按总奖励量缩放给探索力+勇气经验(expBase = max(2, totalReward/30)
- **tickTide**:灵感 am.tideTriggerBonus 缩短星潮间隙(gap × (1 - bonus)),上限 30%
- **doPrestige**performPrestige 后用 next.attributes 重算 stats
- **allocateAttribute(attr, points=1)**:分配属性点,同步 attributeProgress[attr].level
- **gainAttributeExp(attr, amount)**:通用经验获取(自动升级)
- **init()**:调用 migrateAttributes 补全旧存档字段,并传 attributes 到 syncStats
- 所有 syncStats 调用点(10+ 处)都补充 `attributes: ...` 参数
### AttributesPanel.tsx UI
- 四维属性卡片网格(小屏 2×2,大屏 1×4):
- 图标(Compass/Brain/Swords/Sparkles+ 中文名 + 英文名 + Lv.{value} badge
- 数值 /100 + 加成百分比
- 经验进度条(gradient + glow+ "递减区"标记
- 加成影响列表(3 条)
- 「+分配」按钮(pendingAttrPoints > 0 时可点,hover scale 105
- 顶部:标题 + 待分配点数 badgependingAttrPoints > 0 时 echo-pending-pulse 动画)
- 底部:总等级 + 总加成概览 + 12 个修饰器明细行
- 配色:4 色全息(emerald/fuchsia/amber/rose),辉光边框 + 顶角光晕装饰
- 完全响应式(2 列 → 4 列),overflow-y-auto + 自定义 scrollbar
### page.tsx 集成
- 新增 `User` 图标导入
- 新增 `pendingAttrPoints` store selector
- TabsList: grid-cols-7 → grid-cols-8
- 新增第 8 个 TabsTrigger「角色」(value="attributes"),主题色用 emerald→fuchsia→rose 渐变
- pendingAttrPoints > 0 时显示 rose 红点
- 新增 TabsContent 渲染 AttributesPanel
- StatsPanel 新增 5 行属性行(探索力/智慧/勇气/灵感/待分配属性点)
- 版本号 v0.6 → v0.7
### achievements.ts 新增
- `ach_attr_total_50`(四维觉醒):四维属性总和 ≥ 50 → 产能+6%/洞见+6%
- `ach_attr_max_100`(维度精通):任一属性 ≥ 100 → 产能+12%/洞见+10%
## QA 验证结果
- ✅ `bun run lint` 零错误
- ✅ dev 服务器 HTTP 200
- ✅ 编译 < 250ms177ms 实测)
- ✅ 7 标签页 + 巡航按钮完好保留,新增第 8 个「角色」标签页
- ✅ data-tut 锚点(tab-expedition/tab-tech/prestige-btn/crystal-orb/decode-panel)保留
- ✅ 色彩规范:严格 emerald/fuchsia/amber/rose 四色全息,零蓝色/靛色
- ✅ 旧存档兼容:migrateAttributes 补全 attributes/attributeProgress/pendingAttrPoints 字段
- ✅ 飞升后 pendingAttrPoints = ascensions × 2 + 1(飞升前次数)
- ✅ pulse/clickNode/resolveCurrentNode/grantCruiseReward/autoDecodeTick 均接入属性经验获取
## 注意事项
- BOSS 胜率 RNG 包装:仅在 boss 节点生效,单次 rng 调用保持均匀分布;+am.bossWinRateBonus 上限 +30%
- 灵感星潮触发:通过缩短 gap 间接提升触发频率(上限 30%)
- 属性加成与现有所有系统(技术树/蓝图/成就/星图/星潮)叠加,不冲突
- migrateAttributes 同步 attributeProgress[attr].level 与 attributes[attr] 数值,避免漂移
-141
View File
@@ -1,141 +0,0 @@
# Task 9-b · full-stack-developer · 信标系统扩展(周挑战 + 信标链)
> 本文件为本 agent 在 Task 9-b 的工作记录,供后续 agent 查阅。
## 任务概述
为「回响星核 / Echo Nexus」v0.8 扩展深空信标系统,新增两大功能:
1. **周挑战(Weekly Challenge** — 每周一 UTC 0 点刷新,目标更大、奖励更好,与日挑战并行
2. **信标链(Beacon Chain** — 连续完成日挑战形成"链",达成里程碑(3/7/14/30 天)领取递增大奖,含 grace 续命机制
## 阅读的前置工作
- `/home/z/my-project/worklog.md`(v0.8 项目状态,10 大系统,四色全息规范)
- `/home/z/my-project/src/lib/game/beacon.ts`(v0.5 原版 358 行:每日挑战 + 本地排行榜)
- `/home/z/my-project/src/components/game/BeaconPanel.tsx`(原版 301 行)
- `/home/z/my-project/src/store/gameStore.ts` 第 173 行 `trackBeacon` 函数 + 7 处调用点(pulse/clickNode/autoDecodeTick/resolveCurrentNode 等)
## 实现细节
### 1. beacon.ts 扩展(358 → 873 行)
**周挑战(WEEKLY CHALLENGEsection**
- `BeaconWeeklyChallenge` 接口 + `BeaconWeeklyProgress` 接口
- `getWeekKey(now)`ISO 8601 周键(YYYY-Www,周一为起点,含首个周四的周为第一周)
- `weekKeyToSeed`FNV-1a 哈希
- `generateWeeklyChallenge(now)`:基于 weekKey 种子确定性生成
- 难度加权:anomaly 60% / singular 40%
- mult = 3 + floor(rng() * 3) → 3-5 倍
- goal 范围:decode 18-75 / expedition 3-15 / pulse 60-245 / boss 3-12 / insight 120-595
- `loadWeeklyProgress` / `saveWeeklyProgress` / `addWeeklyProgress` / `claimWeeklyReward`
- `msUntilNextWeek(now)`
- `BEACON_WEEKLY_KEY = "echo-nexus-beacon-weekly-v1"`
**信标链(BEACON CHAINsection**
- `BeaconChainState` 接口(lastCompletedDateKey / currentStreak / longestStreak / totalCompletions / graceUsed / milestonesClaimed
- `BEACON_CHAIN_KEY = "echo-nexus-beacon-chain-v1"`
- `BEACON_CHAIN_MILESTONES = [3, 7, 14, 30] as const`
- `BEACON_CHAIN_REWARDS`4 个里程碑
- 3 天:+50 洞见 / +5 接触 / "三日谐振"
- 7 天:+120 洞见 / +12 接触 / "七日回响"
- 14 天:+280 洞见 / +28 接触 / "半月星潮"
- 30 天:+680 洞见 / +68 接触 / "满月飞升"
- `loadChainState` / `saveChainState`(每次返回新对象避免引用共享 bug
- `recordChainCompletion(dateKey)` 核心逻辑:
- 同日重复完成 → 忽略
- 次日 → streak++
- 隔一天 miss 且 graceUsed<1 → 续命 streak++ graceUsed++
- 其他 → 断链 streak=1 graceUsed=0
- 返回 `{ state, newMilestones }`
- `claimChainMilestone(milestone)` / `getNextMilestone(streak)` / `getChainProgress(streak)`
- `dateKeyToTimestamp` / `dateKeyDiffDays` 工具
`BeaconScoreEntry` 新增可选 `isWeekly?: boolean` 字段,向后兼容。
### 2. gameStore.ts 集成
- import 扩展:新增 generateWeeklyChallenge / loadWeeklyProgress / loadChainState / addWeeklyProgress / recordChainCompletion / claimWeeklyReward / claimChainMilestone / getTodayKey + 类型
- `trackBeacon(type, delta)` 返回值从 `boolean` 升级为 `{ dailyJustCompleted, weeklyJustCompleted, newChainMilestones }`
- 同时更新日挑战进度 + 周挑战进度
- 日挑战刚完成时调用 `recordChainCompletion(getTodayKey())`
- 所有 7 处调用点原本忽略返回值,向后兼容
- 新增 action `claimWeeklyBeacon()`:调用 claimWeeklyReward → 发放奖励到 state
- 新增 action `claimChainReward(milestone)`:前置校验 → claimChainMilestone → 发放奖励
- GameActions 接口同步扩展
### 3. BeaconPanel.tsx 重写(301 → 638 行)
- 头部 + 每日挑战卡片(v0.5 保留)+ 难度色按钮主题
- **周挑战区块**fuchsia 主题):标题 + weekKey + 倒计时 + 卡片(标签/标题/描述/进度/奖励/领取按钮 emerald+ weekly-glow 动画
- **信标链区块**amber→rose 渐变):
- 标题"信标链 · CHAIN" + 大字号 streakchain-streak-text 流动渐变动画)
- 今日完成状态徽章
- 4 个里程碑节点(w-12 h-12 圆形):
- claimed: emerald 实心 + ✓
- reachable: rose 脉冲动画 + "领取"按钮
- inProgress (next milestone): amber 半亮
- 未到达: muted 灰
- 节点间连线:背景灰 + 已达成部分 amber→rose 渐变 + 辉光,基于 prev→next 插值定位
- 进度条 + 底部统计(最长链/累计完成/续命状态)
- 桌面端 lg:grid-cols-2 让周挑战 + 信标链并排,移动端单列
- 排行榜区分日/周:周挑战 entry 显示 "WEEK" 徽章 + fuchsia 高亮背景
- 4 个新 CSS 动画:weekly-glow / chain-milestone-pulse / chain-streak-flux
## QA 验证结果
### 1. lint
- `bun run lint` 零错误(每次修改后均验证)
### 2. dev 服务器
- dev.log 全程无错误,所有编译 < 300msHTTP 200
### 3. 信标链逻辑测试(bun 直接运行 TS,5 个场景全 PASS)
1. ✅ 昨日 streak=1 → 今日完成 → streak=2normal increment
2. ✅ 同日重复完成 → 忽略
3. ✅ 明日完成 → streak=3,无需 grace
4. ✅ 隔一天 misslastCompleted + 2 天)→ streak=4graceUsed=1(续命触发)
5. ✅ 再次 missgrace 已用)→ 断链 streak=1graceUsed=0
### 4. UI 集成测试(agent-browser
- localStorage 模拟设置 chain statelastCompletedDateKey=yesterday, currentStreak=1)→ reload → UI 正确显示 streak=1
- 设置 streak=3 + milestonesClaimed=[] → milestone 3 节点显示 rose 脉冲动画 + "领取"按钮
- 点击"领取"→ milestonesClaimed=[3] + Toast "✦ 三日谐振 已领取 +50 洞见 · +5.0 接触" + 按钮消失 ✅
### 5. VLM 视觉评分(目标 ≥7/10)
- 首屏截图:**8/10**(四色一致、布局合理、信标链清晰)
- 里程碑可领取状态:**8/10**(amber→rose 渐变醒目、里程碑层次分明、fuchsia 主题清晰)
- 重置后干净状态:**8/10**(WEEKLY 字体对比度可优化,但整体可读性强)
## 修改的文件
1. `src/lib/game/beacon.ts` — 358 → 873 行(+515 行)
2. `src/store/gameStore.ts` — trackBeacon 升级 + 2 个新 action+~80 行)
3. `src/components/game/BeaconPanel.tsx` — 301 → 638 行(+337 行,重写)
## 不破坏的现有功能
- 日挑战进度追踪与领奖流程
- 本地排行榜 Top20
- 现有 5 种挑战类型 + 3 档难度
- 现有 localStorage keysecho-nexus-beacon-lb-v1 / echo-nexus-beacon-prog-v1
## 新增的 localStorage keys
- `echo-nexus-beacon-weekly-v1`(周挑战进度)
- `echo-nexus-beacon-chain-v1`(信标链状态)
## 四色全息规范遵循
- 周挑战主题:**fuchsia** (#e879f9)
- 信标链主题:**amber → rose** 渐变 (#fbbf24#fb7185)
- 领取按钮:**emerald** (#34d399)
- 难度色:routine emerald / anomaly amber / singular rose
- **零蓝色/靛色违规**
## 截图资产
- `/home/z/my-project/agent-ctx/beacon-panel-v0.8.png` — 初次进入信标页
- `/home/z/my-project/agent-ctx/beacon-chain-streak1.png` — chain streak=1 状态
- `/home/z/my-project/agent-ctx/beacon-chain-milestone3.png` — chain streak=3 milestone 可领取
- `/home/z/my-project/agent-ctx/beacon-milestone3-claimable.png` — milestone 3 领取前
- `/home/z/my-project/agent-ctx/beacon-final-fresh.png` — 重置后干净状态
Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

-2005
View File
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
BIN
View File
Binary file not shown.
-95
View File
@@ -1,95 +0,0 @@
# 游戏市场调研报告
> 调研日期:2025 年(持续更新)
> 调研者:Super_Z
> 目的:为原创游戏《回响星核 / Echo Nexus》的设计提供数据支撑,避免落入「1:1 复刻」陷阱。
---
## 一、市场大盘(数据时效性已核实,多源交叉验证)
### 1.1 浏览器 / HTML5 游戏市场
- **市场规模**2025 年浏览器游戏市场约 **78 亿 ~ 118 亿美元**(不同机构口径略有差异),预计 2033-2034 年达到 **92 亿 ~ 163 亿美元**CAGR 约 3.5%。
- 来源:Research and Markets78.1 亿→80.1 亿)、Dataintelo118 亿→163 亿)、Business Research Insights。
- **HTML5 游戏细分**2024 年约 53.2 亿美元,2033 年预计 92.2 亿美元,CAGR 约 7.7%LinkedIn / Metastat)。
- **供给爆发**2025 年有 **15,000+** 款 HTML5 新作上线,同比增长 **2.7 倍**Business Research Insights)。
- **技术趋势**WebAssembly 让浏览器游戏性能逼近原生;WebGL/WebGPU 渲染能力持续提升。
**结论**:浏览器游戏是增长赛道,**可及性高、潜在受众巨大(尤其国际市场)**,但供给端竞争激烈,**必须靠差异化创新突围**。
### 1.2 独立游戏品类格局(Steam 视角)
- **2025 年叙事游戏**成为 Steam 达到 1000 评价数量最多的品类(#1),超越传统品类(HowToMarketAGame 年度报告)。
- **新一代独立原生品类**:开放世界生存、卡牌构筑(deckbuilder)、Roguelike 持续走强(SuperJump)。
- **红海/饱和品类**(Reddit 独开者共识,建议避开正面对撞):Roguelike Deckbuilder、视觉小说、本地合作、4X。
- **2025 独立代表作**Hades II、Deltarune、Ways of Alchemy、Cluckmech Oasis 等。
**结论**:纯复刻热门品类(如又一款肉鸽卡牌)会被淹没;**叙事 + 创新机制组合**是突破口。
### 1.3 放置 / 增量(Idle / Incremental)品类
- **极强留存**:Idle 类天然契合浏览器场景,代表作 Cookie Clicker、Universal Paperclips、Melvor Idle 长盛不衰。
- **2025 热度持续**GameSpot、Bits N' Pixels 年度榜单均大量收录 idle/clicker 游戏。
- **典型玩法**:资源自动累积 + 主动操作 + 解锁/重置(prestige)循环。
- **优势**:开发成本可控、留存曲线平缓、适合持续迭代。
### 1.4 玩家留存关键洞察
- **核心循环(core loop**是留存的根基;D1/D7/D30 是关键基准。
- **渐进式功能发现**:让玩家逐步解锁新机制,避免一次性信息过载。
- **社交连接 + 流畅运营**:长期留存的两大支柱。
- **动机分层**:成就型、探索型、社交型玩家需要差异化设计。
---
## 二、竞品分析与差异化机会
| 维度 | 主流放置游戏 | 主流叙事游戏 | 主流肉鸽 | 《回响星核》定位 |
|---|---|---|---|---|
| 核心 | 数值增长 | 剧情推进 | 战斗随机 | **解码谜题 + 放置 + 探险** |
| 叙事 | 弱/无 | 强但线性 | 弱 | **碎片化叙事,由玩法涌现** |
| 美术 | 简笔画/像素 | 插画 | 像素 | **全息晶体 + 深空粒子美学** |
| 留存钩子 | prestige | 章节 | 随机性 | **「接触」终局 + 多周目** |
| 单局 | 长 | 中 | 短 | **可长可短(放置 + 主动)** |
**差异化结论**
1. 没有任何一款主流游戏同时是「深空考古 + 记忆解码谜题 + 放置 + 肉鸽探险 + 碎片叙事」。
2. 叙事由解码行为**涌现**而非过场灌输,符合 2025 叙事品类崛起趋势。
3. 放置核心保证留存,解码谜题提供主动乐趣,探险提供随机性重玩价值。
---
## 三、技术选型调研结论
- **框架**Next.js 16App Router+ TypeScript —— 本项目既有栈,SSR/路由/API 一体化,适合 web 游戏。
- **渲染**2D 场景用 **HTML5 Canvas**(星空/粒子/晶体动画),UI 用 **React + shadcn/ui**。3D 暂不需要,控制复杂度。
- **状态**Zustand(客户端)+ localStorage 持久化;后续可接 API 同步存档。
- **后端**Next.js API Routes + Prisma(SQLite),承载存档同步、全局统计、(后续)排行榜。
- **实时**:socket.io 小服务,用于全局「星潮」事件(后续阶段)。
**结论**:在既有 Next.js 栈内即可实现,无需引入重框架,性能与可维护性平衡良好。
---
## 四、设计原则(源自调研)
1. **原创优先**:不做任何现有游戏的 1:1 复刻,机制组合与世界观均自创。
2. **核心循环扎实**:放置产出 → 解码消耗 → 解锁成长 → 探险扩张 → 终局「接触」。
3. **叙事涌现**:故事从解码碎片中拼出,不打断玩法。
4. **渐进披露**:功能随进度解锁,符合留存最佳实践。
5. **视觉差异化**:全息晶体 + 深空粒子,第一眼即与同类区分。
6. **可持续迭代**:模块化架构,便于后续通过工单需求持续扩展。
---
## 五、数据来源清单(真实性/时效性核验)
1. Business Research Insights — Browser Games Market Report
2. Research and Markets — Browser Games Market 2026
3. Dataintelo — Browser Game Market Research 2034
4. Metastat Insights — HTML5 Games Market 2030
5. LinkedIn (行业分析) — Casual HTML5 Games 2024-2033
6. HowToMarketAGame — 2025 Steam 品类年度报告
7. SuperJump — State of Indie Games 2025
8. GameSpot / Bits N' Pixels — 2025 Idle 游戏榜单
9. GameDesignSkills / GameDeveloper — 留存策略 17 条
10. Reddit r/incremental_games、r/gamedev 社区共识
所有数据均为 2024-2026 年发布,时效性满足设计决策需要。
-141
View File
@@ -1,141 +0,0 @@
# 《回响星核 / Echo Nexus》游戏设计文档 (GDD)
> 版本:v0.1(首发概念版)
> 作者:Super_Z
> 状态:设计已定稿,进入实现阶段
---
## 一、一句话概念
**你是一名深空考古指挥官,驾驶自治勘探舰回收已「飞升」的以太文明遗留的记忆晶体,解码碎片拼凑出他们的故事,并最终跨越维度发起「接触」。**
## 二、游戏定位
- **品类**:深空考古放置策略 + 记忆解码谜题 + 肉鸽探险 + 涌现叙事
- **平台**:现代浏览器(桌面优先,移动适配)
- **单局时长**:可长可短(放置 30 秒也有产出;主动游玩 10-30 分钟一轮探险)
- **目标留存**D1 > 40%D7 > 15%idle 品类基准之上)
- **差异化**:业界首个「解码谜题驱动叙事涌现」的放置游戏
## 三、世界观
数万年前,银河系存在一个高度发达的文明——**以太族(Aetherians)**。他们没有灭亡,而是集体「飞升」到了更高维度。临走前,他们将文明的知识与记忆封存在遍布星系的**记忆晶体(Memory Crystals)**中,作为留给后来者的「面包屑」——只待某个文明能解读足够多的晶体,便有资格发起「接触」。
玩家扮演人类深空考古局的指挥官,舰载 AI「回响(Echo)」辅助你勘探遗迹、回收晶体、解码记忆。
## 四、核心循环
```
┌─────────────────────────────────────────────┐
▼ │
[自治无人机采矿] ──产出──> [记忆晶体库存] │
│ │
[解码谜题] <── 主动操作 │
│ │
┌──────────────┼──────────────┐ │
▼ ▼ ▼ │
[记忆碎片] [技术洞见] [剧情片段] │
(叙事资源) (解锁升级) (拼凑故事) │
│ │ │ │
└──────────────┴──────────────┘ │
│ │
[发起遗迹探险] ─肉鸽─> 新晶体类型
│ │
[「接触」进度条 ↑] │
│ │
────── 达成 100% ───── 终局 / 新周目
```
**核心循环一句话**:采矿→解码→成长→探险→填满接触条→新周目。
## 五、核心系统
### 5.1 记忆晶体采集(Idle 核心)
- 自治无人机以 **晶体/秒** 速率自动产出记忆晶体。
- 玩家可主动「脉冲扫描」(点击/按键)获得瞬时增量 + 连击加成。
- 离线时按 50% 效率持续产出(最多累积 8 小时)。
### 5.2 解码系统(主动玩法核心 ⭐)
每颗晶体需经「解码」才能释放其中内容。解码是一个**原创的图案共振谜题**:
- 屏幕呈现一个由发光节点构成的**共振阵列**(类似星图)。
- 玩家需在限定步数内,按特定**共振顺序**点亮节点,使能量流闭合形成回路。
- 成功 → 释放记忆碎片 + 技术洞见 + 剧情片段。
- 不同晶体等级 / 类型 → 阵列规模与共振规则不同(渐进难度)。
- **设计意图**:把「解码」从被动等待变成有节奏的主动小谜题,避免放置游戏「只挂机不动手」的枯燥。
### 5.3 技术树(成长系统)
四个分支,由「技术洞见」解锁:
- **采矿**:提升晶体/秒、仓库上限、离线效率。
- **解码**:降低解码步数消耗、解锁阵列提示、自动解码低级晶体。
- **探险**:提升探险成功率、降低风险、增加稀有晶体掉落。
- **叙事**:解锁更深记忆层、揭示隐藏剧情、提升「接触」进度转化率。
### 5.4 遗迹探险(Roguelike 层)
- 消耗「能量」向随机生成的遗迹节点图派出探险队。
- 每个节点有事件(战斗/解谜/宝藏/抉择),路径分支。
- 失败损失探险队,成功带回稀有晶体 + 大量洞见 + 独有剧情。
- 单次探险 10-30 分钟,提供短期目标与随机性重玩价值。
### 5.5 碎片叙事(叙事层)
- 解码与探险产出的「记忆碎片」自动归档到**记忆图谱(Memory Codex**。
- 图谱以星图形式呈现,碎片拼合后揭示以太族编年史(5 个纪元)。
- 关键碎片触发**「回响回放」**——一段沉浸式全息短叙(文字 + 视觉),但**绝不打断玩法**(可随时关闭)。
### 5.6 「接触」终局与多周目
- 全部行为累积「接触进度」。
- 达 100% → 触发终局「接触」事件(戏剧性叙事高潮)。
- 之后可选择**飞升(Prestige)**进入新周目:保留叙事图谱与技术蓝图的一部分,重置数值,开启更高难度与隐藏内容。
## 六、美术与音频方向
- **视觉**:深空黑紫底色 + 全息青/品虹光晶体 + 流动星尘粒子。整体「冷峻神秘 + 科技仪式感」。
- **字体**:标题用未来感无衬线,正文清晰可读。
- **动效**:晶体生长、解码共振波纹、粒子涌动、飞升白光。
- **音频**:环境氛围 pad + 解码成功清脆音 + 关键叙事轻旋律(后续阶段)。
## 七、UI 信息架构
```
顶栏:晶体库存 | 接触进度 | 能量 | 设置
左侧:主视图(Canvas 星空 + 晶体集群 + 解码阵列)
右侧:标签面板
- 采矿 (Mining)
- 解码 (Decode) ⭐默认
- 技术 (Tech)
- 探险 (Expedition)
- 记忆图谱 (Codex)
底栏:当前目标提示 | 在线/离线收益 | 版本号
```
## 八、首版(v0.1MVP 范围
为快速验证核心乐趣,首版聚焦:
- [x] 放置采矿(晶体/秒 + 主动脉冲)
- [x] 解码共振谜题(3 种阵列规模)
- [x] 技术树 4 分支 × 3 级 = 12 个升级
- [x] 记忆图谱(首纪元 8 个碎片)
- [x] 接触进度 + 飞升(Prestige
- [x] localStorage 存档 + 离线收益
- [x] 精美 Canvas 星空背景 + 晶体动画
- [x] 响应式布局(桌面/移动)
**v0.1 暂不做**:遗迹探险肉鸽、socket 全局事件、音频、多语言切换、账号云存档(留待后续版本/工单驱动)。
## 九、后续路线图
- **v0.2**:遗迹探险肉鸽系统
- **v0.3**:音频系统 + 全局「星潮」socket 事件
- **v0.4**:账号云存档 + 排行榜
- **v0.5**:多周目隐藏内容 + 全 5 纪元叙事
- **持续**:根据工单需求迭代
## 十、设计风险与对策
| 风险 | 对策 |
|---|---|
| 解码谜题单调 | 多种阵列规则 + 自动解码低级晶体 |
| 放置枯燥 | 主动脉冲 + 连击 + 探险提供短期目标 |
| 叙事打断玩法 | 所有叙事可跳过,图谱自愿浏览 |
| 数值膨胀 | 飞升重置 + 指数曲线校准 |
| 移动端操作 | 解码阵列适配触控,UI 自适应 |

Some files were not shown because too many files have changed in this diff Show More