deploy: v0.11 解码卡片hover增强 + StatsPanel性能优化
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,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 +0,0 @@
|
||||
12512
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 · 持续开发中
|
||||
@@ -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
|
||||
@@ -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",{}]]
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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()
|
||||
@@ -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"}
|
||||
@@ -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))}]);
|
||||
@@ -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",{}]]
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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",{}]]
|
||||
@@ -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)`:确定性生成,难度 routine,goal = 日基准 ×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` action(GameActions 接口同步)
|
||||
- 新增 `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-glow(amber 呼吸)/ timed-urgent(rose 急促脉冲)
|
||||
- 排行榜 entry 区分 TIMED(amber 徽章)/ WEEK(fuchsia 徽章)
|
||||
- 空状态提示更新为"完成限时、每日或本周信标即可登榜"
|
||||
|
||||
### 工单 #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 + targetLenBonus,stepLimitBase 同步增加
|
||||
- 路径构造法自动适配更长目标序列(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 触发):
|
||||
- surge(emerald 绿):8/10,横幅 + 全屏绿色微光
|
||||
- prism(fuchsia 紫):7/10,横幅 + 全屏紫色微光
|
||||
- eclipse(rose 粉):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 旧星潮全部保留,无回归
|
||||
@@ -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 action:grantBeaconReward/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=1(reparent 到 init),进程脱离原 bash session,uptime 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 专属刷新按钮(RefreshCw,loading 时 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 keys(echo-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,用于全球榜高亮)
|
||||
|
||||
## 四色全息规范遵循
|
||||
|
||||
- 本地 tab:amber (#fbbf24)
|
||||
- 全球 tab:emerald (#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 与之互不冲突。
|
||||
|
Before Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 243 KiB |
@@ -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-50,DPR cap 2,resize 监听
|
||||
- **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 + recordRun,rewardGrantedRef 防重
|
||||
|
||||
### 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) 四色全息色谱,零蓝色/靛色
|
||||
@@ -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 action;pulse/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 逻辑层
|
||||
- **类型系统**:AttributeKey(4 个键)/ CharacterAttributes(0-100 数值)/ AttributeProgressEntry(exp + 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)
|
||||
- 顶部:标题 + 待分配点数 badge(pendingAttrPoints > 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
|
||||
- ✅ 编译 < 250ms(177ms 实测)
|
||||
- ✅ 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] 数值,避免漂移
|
||||
@@ -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 CHALLENGE)section**:
|
||||
- `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 CHAIN)section**:
|
||||
- `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" + 大字号 streak(chain-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 全程无错误,所有编译 < 300ms,HTTP 200
|
||||
|
||||
### 3. 信标链逻辑测试(bun 直接运行 TS,5 个场景全 PASS)
|
||||
1. ✅ 昨日 streak=1 → 今日完成 → streak=2(normal increment)
|
||||
2. ✅ 同日重复完成 → 忽略
|
||||
3. ✅ 明日完成 → streak=3,无需 grace
|
||||
4. ✅ 隔一天 miss(lastCompleted + 2 天)→ streak=4,graceUsed=1(续命触发)
|
||||
5. ✅ 再次 miss(grace 已用)→ 断链 streak=1,graceUsed=0
|
||||
|
||||
### 4. UI 集成测试(agent-browser)
|
||||
- localStorage 模拟设置 chain state(lastCompletedDateKey=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 keys(echo-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` — 重置后干净状态
|
||||
|
Before Width: | Height: | Size: 299 KiB |
|
Before Width: | Height: | Size: 380 KiB |
|
Before Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 471 KiB |
|
Before Width: | Height: | Size: 472 KiB |
|
Before Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 387 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 223 KiB |
@@ -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"
|
||||
}
|
||||
@@ -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 Markets(78.1 亿→80.1 亿)、Dataintelo(118 亿→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 16(App 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 年发布,时效性满足设计决策需要。
|
||||
@@ -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.1)MVP 范围
|
||||
|
||||
为快速验证核心乐趣,首版聚焦:
|
||||
- [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 自适应 |
|
||||