Initial commit
This commit is contained in:
218
01-需求与初始设计.md
Normal file
218
01-需求与初始设计.md
Normal file
@@ -0,0 +1,218 @@
|
||||
|
||||
# 🎮 产品需求文档 (PRD):《Project Vibe: Ship It!》
|
||||
|
||||
## 1. 产品愿景与核心体验
|
||||
* **游戏类型:** 桌面堆叠生存 (Desktop Stacking Survival) + 模拟经营 + Roguelike。
|
||||
* **目标受众:** 开发者、PM、QA、设计师,以及喜欢《Stacklands》或《密教模拟器》的策略玩家。
|
||||
* **核心体验:** 玩家在一个没有网格的“桌面”上,通过**拖拽合并卡牌**来推进项目进度(写代码、提需求、修Bug)。在有限的时间内(Sprint),一边应对由**真实大语言模型 (LLM) 动态生成的突发危机**,一边积攒资源完成交付物。
|
||||
* **VibeCoding 特色:** 游戏内的终极神卡是【Vibe助手】,它可以跨职业融合任何卡牌,大幅缩短进度条,但有概率引入“技术债”卡牌。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心游戏循环 (Core Loop & Roguelike)
|
||||
|
||||
游戏采用经典的“局内战斗+局外成长”的 Roguelike 循环。
|
||||
|
||||
### 2.1 局内:Sprint 冲刺期(生存与合成)
|
||||
* **周期时长:** 每个 Sprint 现实时间约为 3-5 分钟。
|
||||
* **胜利条件:** 在倒计时结束前,利用桌面卡牌合成出规定数量的【交付物】(如:3 个可用模块),且【项目资金】> 0。
|
||||
* **失败条件:** 【项目资金】归零(被 Bug 或超时危机扣光)。
|
||||
* **核心操作:** 拖拽卡牌重叠 -> 触发对应配方 -> 显示进度条 -> 倒计时结束产出新卡/资源。
|
||||
|
||||
### 2.2 局外:Retrospective 复盘大会(成长抉择)
|
||||
* **结算与奖励:** Sprint 成功后,剩余的时间和资源按比例转化为【项目奖金】。
|
||||
* **肉鸽抉择(3选1):** 玩家从随机刷出的 3 个选项中选择 1 个强化自身:
|
||||
* *新增高级卡牌:* 如【自动化测试脚本】(永久复用)。
|
||||
* *获取遗物 (Relics):* 如【敏捷教练】(全局 Buff:所有合成时间缩短 10%,但每回合扣除 5% 资金)。
|
||||
* *清理牌库:* 花费资金销毁桌面上难以处理的【技术债】或【屎山代码】卡。
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心机制设计
|
||||
|
||||
### 3.1 桌面与状态机 (The Board & State)
|
||||
* **无网格桌面:** 界面是一个类似操作系统的桌面。卡牌可以任意摆放和堆叠。
|
||||
* **全局资源:** 顶部 UI 显示当前 Sprint 的【倒计时】和【项目资金】。
|
||||
* **卡牌堆叠判定:** 当卡牌 A 被拖拽并释放 (Drop) 在卡牌 B 上时,系统检索 `Crafting_Recipes` (合成配方表)。若匹配,两张卡牌进入 `Processing` 状态锁定,头顶出现进度条。
|
||||
|
||||
### 3.2 基础卡牌与职业卡池 (Classes & Decks)
|
||||
玩家开局需选择职业,决定初始手牌和专属配方路线。
|
||||
|
||||
|
||||
|
||||
#### 1. 基础公共卡池 (所有职业共用的环境底座)
|
||||
* **【资源卡】**
|
||||
* `专注力 (Focus)`:基础合成消耗品。随时间自然蒸发。
|
||||
* `咖啡 (Coffee)`:万能提神药。
|
||||
* `预算 (Budget)`:用于在商店购买服务器或外包。
|
||||
* `脑洞 (Idea)`:随机飘落在桌面的灵感。
|
||||
* **【建筑/设施卡】 (放在桌面上长期生效)**
|
||||
* `咖啡机 (Coffee Machine)` + `预算` = 自动每 15 秒产出 1 杯 `咖啡`。
|
||||
* `云服务器 (Cloud Server)`:承载高级代码,产出项目进度,但每 10 秒消耗 1 点 `预算`。
|
||||
* **【负面/垃圾卡】 (需要想办法消除,否则占满桌面或引爆危机)**
|
||||
* `Bug`:由脏代码变异,会吃掉附近的 `专注力`。
|
||||
* `技术债 (Tech Debt)`:无法移动,占据桌面空间。3个 `技术债` 聚在一起会召唤一次 【LLM 重大危机】。
|
||||
* `无意义的会议 (Pointless Meeting)`:随机抓取桌面上的一张 `实体卡(人)`,使其被锁定 20 秒,产出 `疲惫 (Burnout)`。
|
||||
|
||||
---
|
||||
|
||||
#### 2. 👨💻 资深开发 (Developer) 的深度合成树:架构师之路
|
||||
开发流派的乐趣在于**“从写屎山到重构成微服务”**的基建狂魔体验。
|
||||
|
||||
* **T1:造砖阶段 (脏与快)**
|
||||
* `开发人员` + `专注力` = `脏代码 (Dirty Code)` (需 3 秒)
|
||||
* `脏代码` + `脏代码` = `面条代码 (Spaghetti Code)` (只需 2 秒,但 30% 几率同时产出 `Bug`)
|
||||
* **T2:重构阶段 (质量提升)**
|
||||
* `面条代码` + `开发人员` + `咖啡` = `干净的代码 (Clean Code)` (需 15 秒)
|
||||
* `脏代码` + `Vibe AI 助手` = `干净的代码` (只需 2 秒,极速重构!但 10% 几率产出 `开源协议侵权警告(危机卡)`)
|
||||
* **T3:架构阶段 (质变)**
|
||||
* `干净的代码` + `干净的代码` = `可用模块 (Working Module)`
|
||||
* `可用模块` + `云服务器` = **【微服务节点 (Microservice)】**(建筑卡:每 5 秒自动产出 1 个 `项目进度` 和 100 `预算`)
|
||||
* *隐藏配方:* `开发人员` + `无意义的会议` = `摸鱼打代码` -> 产出 `开源组件` (高价值卡牌) + `老板的怒火`。
|
||||
|
||||
---
|
||||
|
||||
#### 3. 📊 产品经理 (PM) 的深度合成树:空手套白狼
|
||||
产品流派没有直接产出代码的能力,乐趣在于**资源调度、画大饼和踢皮球**。
|
||||
|
||||
* **T1:需求制造**
|
||||
* `产品经理` + `脑洞` = `一句话需求 (Vague Requirement)` (极快)
|
||||
* `一句话需求` + `无意义的会议` = `PRD文档 (PRD)` + `画大饼 (Empty Promise)`卡片。
|
||||
* **T2:白嫖与外包**
|
||||
* `画大饼` + `开发人员(NPC/队友)` = 临时将该开发者的合成速度提升 200%,但 10 秒后必定产出 `离职倾向 (Burnout)`(极度危险的负面卡)。
|
||||
* `PRD文档` + `预算(1000)` = `外包团队` -> 产出 `粗糙的模块` (可用但随时会坏)。
|
||||
* **T3:乾坤大挪移 (解决危机的独特方式)**
|
||||
* `产品经理` + `Bug` = `特性 (Feature)`。*(神级配方:直接把 Bug 卡强行重命名为特性卡,变成可交付物,极其幽默)*
|
||||
* `产品经理` + `技术债` = `排期延期卡`。*(把技术债塞进排期,消除占用,但会减少 Sprint 最终结算奖励)*
|
||||
|
||||
---
|
||||
|
||||
#### 4. 🕵️ 测试工程师 (QA) 的深度合成树:陷阱与净化
|
||||
测试流派的乐趣在于**“找茬”、建立自动化流水线**,看着 Bug 灰飞烟灭。
|
||||
|
||||
* **T1:挑刺阶段**
|
||||
* `测试员` + `脏代码` / `面条代码` = `Bug 报告` + `干净的代码` (提纯过程,需 10 秒)。
|
||||
* `Bug 报告` + `开发人员` = `争吵 (Argument)` + `重构好的代码`。
|
||||
* **T2:自动化防御**
|
||||
* `测试员` + `专注力` + `咖啡` = `断言脚本 (Assert Script)` (消耗品武器卡)。
|
||||
* 将 `断言脚本` 拖到 `Bug` 或 `技术债` 上 = 瞬间将其秒杀,并掉落 `项目质量分`。
|
||||
* **T3:建立流水线 (质变)**
|
||||
* `测试员` + `可用模块` + `云服务器` = **【CI/CD 流水线】**(自动化建筑)。
|
||||
* *作用:* 只要你把任何 `脏代码` 扔进 【CI/CD 流水线】,它会自动吃进去,5 秒后吐出 `干净的代码` 或者 `报警邮件`。玩家无需再手动派人去测试。
|
||||
|
||||
---
|
||||
|
||||
#### 5. 🎨 UI/UX 设计师 (Designer) 的深度合成树:美学的代价
|
||||
设计师流派主打**提升项目溢价(卖更多的钱)**,但要面对甲方的反复无常。
|
||||
|
||||
* **T1:原型设计**
|
||||
* `设计师` + `专注力` = `线框图 (Wireframe)`。
|
||||
* `线框图` + `咖啡` = `高保真原型 (Figma Prototype)`。
|
||||
* **T2:前端融合**
|
||||
* `高保真原型` + `可用模块(后端)` = `绚丽的 App (Gorgeous App)`(交付时获得 3 倍预算奖励)。
|
||||
* **T3:甲方的折磨 (特殊的卡牌连环演变)**
|
||||
* 当你打出 `绚丽的 App` 时,有概率触发连环负面状态:
|
||||
* `设计师` + `甲方反馈` = `设计稿_V2`。
|
||||
* `设计师` + `设计稿_V2` = `设计稿_最终版`。
|
||||
* `设计师` + `设计稿_最终版` = `设计稿_打死不改_绝对最终_V8.psd`。(只有这张卡才能真正交付)。
|
||||
|
||||
---
|
||||
|
||||
### 💡 VibeCoding 视角的 JSON 数据结构设计
|
||||
|
||||
有了这么庞大且幽默的配方树,如果你用传统的 IF-ELSE 来写代码绝对会疯掉。但对于 AI 辅助编程来说,你只需要让 AI 帮你搭建一个**基于 JSON 解析的合成引擎**。
|
||||
|
||||
你可以直接把这个 JSON 结构发给大模型(如 Cursor),让它生成核心逻辑:
|
||||
|
||||
```json
|
||||
// recipes.json (合成配方配置表)
|
||||
[
|
||||
{
|
||||
"id": "recipe_spaghetti_code",
|
||||
"inputs": ["dirty_code", "dirty_code"],
|
||||
"required_actor": null,
|
||||
"time_ms": 2000,
|
||||
"outputs": [
|
||||
{ "card_id": "spaghetti_code", "chance": 1.0 },
|
||||
{ "card_id": "bug", "chance": 0.3 }
|
||||
],
|
||||
"description": "复制粘贴就是快,但也容易出Bug。"
|
||||
},
|
||||
{
|
||||
"id": "recipe_qa_purify",
|
||||
"inputs": ["spaghetti_code"],
|
||||
"required_actor": "qa_engineer",
|
||||
"time_ms": 10000,
|
||||
"outputs": [
|
||||
{ "card_id": "clean_code", "chance": 1.0 },
|
||||
{ "card_id": "bug_report", "chance": 1.0 }
|
||||
],
|
||||
"description": "测试老哥强行帮你揪出Bug并提纯代码。"
|
||||
},
|
||||
{
|
||||
"id": "recipe_pm_magic",
|
||||
"inputs": ["bug"],
|
||||
"required_actor": "product_manager",
|
||||
"time_ms": 5000,
|
||||
"outputs": [
|
||||
{ "card_id": "feature", "chance": 1.0 }
|
||||
],
|
||||
"description": "这不是Bug,这是特性!"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. LLM 动态危机系统 (AI Director)
|
||||
|
||||
这是游戏的核心亮点。LLM 将作为“甲方/系统”实时生成针对玩家当前桌面弱点的危机卡。
|
||||
|
||||
### 4.1 触发与生成机制
|
||||
1. **心跳检测:** 每隔 30 秒,游戏抓取当前桌面关键卡牌数量(如:资金 1w,脏代码 5,测试 0)。
|
||||
2. **异步请求:** 将状态发送给 LLM(如 OpenAI API),请求生成一个针对性的危机。
|
||||
|
||||
### 4.2 API 延迟处理策略(极简且优雅)
|
||||
* **预警卡生成(立即执行):** API 请求发出的瞬间,桌面上立刻凭空掉落一张中立卡牌【⚠️ 甲方正在输入...】。
|
||||
* **UI 表现:** 卡牌显示 Loading 动画。玩家此时会有未知的压迫感,但无法对其进行操作。
|
||||
* **翻转与爆发(Promise Resolve):** LLM 返回 JSON 后,预警卡带有动画翻转为真正的【红色危机卡】,并开始倒计时。
|
||||
* **本地兜底 (Fallback):** 若请求超过 5 秒未返回,触发兜底逻辑,预警卡翻转为本地配置表中的随机通用危机(如【服务器宕机】)。
|
||||
|
||||
### 4.3 LLM 通信数据结构 (JSON 契约)
|
||||
**System Prompt 要求 LLM 返回以下结构:**
|
||||
```json
|
||||
{
|
||||
"crisis_name": "内存泄漏灾难",
|
||||
"description": "你的脏代码堆积如山,服务器马上就要炸了!",
|
||||
"timer_seconds": 30, // 玩家解题的时间限制
|
||||
"penalty_type": "lose_money", // 超时惩罚:扣除资金
|
||||
"penalty_value": 20000,
|
||||
"required_solution_tags": ["need_test_card", "need_focus_card"] // 解除危机需要的卡牌标签
|
||||
}
|
||||
```
|
||||
**解题玩法:** 危机卡带有两个空槽(对应 `required_solution_tags`)。玩家必须在 30 秒内将具有相应 Tag 的卡牌(如 `测试员卡` 和 `专注力卡`)拖拽到危机卡上,读条 3 秒后危机解除,转化为奖励。超时则执行惩罚。
|
||||
|
||||
---
|
||||
|
||||
## 5. VibeCoding 开发任务拆解建议
|
||||
|
||||
为了充分利用 VibeCoding 的能力,建议您按照以下阶段向 AI (Cursor/Copilot) 提交 Prompt,逐步构建 MVP:
|
||||
|
||||
### Phase 1: 核心前端框架与拖拽 (MVP)
|
||||
* **技术栈选型:** React + TypeScript + Vite + TailwindCSS (或者 Zustand 用于全局状态管理)。
|
||||
* **核心任务 1:定义数据结构**。让 AI 帮您生成 `Card`, `Recipe`, `BoardState` 的 TypeScript Interface。
|
||||
* **核心任务 2:实现拖拽与碰撞判定 (Drag & Drop)**。不要用复杂的物理引擎,让 AI 写一个基于 HTML5 DnD API 或 `framer-motion` 的拖拽组件。判定逻辑为:“松开鼠标时,检查当前卡牌的坐标是否与另一张卡牌的包围盒重叠”。
|
||||
* **核心任务 3:实现倒计时状态机**。用 `useEffect` 或 `requestAnimationFrame` 实现两个重叠卡牌进入 `Processing` 状态,读条结束后触发回调生成新卡。
|
||||
|
||||
### Phase 2: 完善 JSON 配方表与职业系统
|
||||
* 将配方逻辑完全抽离为静态 JSON 配置。
|
||||
* **VibeCoding 优势:** 您只需写出 2 个示例配方,可以直接让大模型批量生成几十个符合“软件工程梗”的配方配置表。
|
||||
|
||||
### Phase 3: 接入 LLM 危机生成器 (API 联调)
|
||||
* 编写 `useCrisisDirector` 自定义 Hook。
|
||||
* 实现“预警卡 -> API Fetch -> 解析 JSON -> 翻转为危机卡 -> 5秒超时本地 Fallback” 的完整异步逻辑链路。
|
||||
|
||||
### Phase 4: Roguelike 循环包装
|
||||
* 增加 Sprint 的总时间计时器。
|
||||
* 增加结算画面和 3 选 1 的 UI 组件。
|
||||
57
02-用户故事和测试用例说明.md
Normal file
57
02-用户故事和测试用例说明.md
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
|
||||
# 📋 第一部分:用户故事 (User Stories)
|
||||
|
||||
我们将整个游戏拆解为 4 个史诗任务 (Epics)。
|
||||
|
||||
### Epic 1: 桌面交互与核心卡牌系统 (The Board & Cards)
|
||||
* **US1.1 自由拖拽:** 作为玩家,我希望能用鼠标在桌面上自由拖拽任意卡牌并放置在任何位置,以便我整理我的工作台。
|
||||
* **US1.2 职业与初始卡组:** 作为玩家,我希望在游戏开局时能选择职业(如开发、产品),以便获得该职业专属的初始手牌(如“开发人员”、“PRD文档”)和基础资金。
|
||||
* **US1.3 资源消耗与显示:** 作为玩家,我希望在屏幕顶部清晰看到当前 Sprint 的“剩余时间”和“项目资金”,以便我评估当前的生存压力。
|
||||
|
||||
### Epic 2: 堆叠合成与进度条 (Crafting & Processing)
|
||||
* **US2.1 触发合成:** 作为玩家,我希望当我把一张卡牌(如“开发人员”)拖拽并覆盖到另一张有效卡牌(如“脏代码”)上时,它们会自动吸附并进入“合成中”状态。
|
||||
* **US2.2 进度条反馈:** 作为玩家,我希望在“合成中”的卡牌组上方看到一个倒计时进度条,以便我知道还需要等多久才能产出结果。
|
||||
* **US2.3 产出与概率:** 作为玩家,我希望进度条结束后,原有的消耗品卡牌消失,并在原位置弹射出新的产物卡(如“干净的代码”),并且支持概率掉落副产物(如 30% 掉落“Bug”卡)。
|
||||
* **US2.4 无效堆叠拒绝:** 作为玩家,我希望当我把两张没有配方关联的卡牌叠在一起时(如“咖啡”叠“咖啡”),它们不会发生任何反应,并在松开鼠标时弹开,防止误操作。
|
||||
|
||||
### Epic 3: LLM 动态危机系统 (The AI Director)
|
||||
* **US3.1 预警卡掩盖延迟:** 作为玩家,我希望在系统即将降临危机时,桌面上会先掉落一张带有 Loading 动画的【⚠️ 甲方正在输入...】卡牌,以便在等待网络请求时给我提供真实的压迫感。
|
||||
* **US3.2 危机爆发:** 作为玩家,我希望 Loading 结束后,预警卡瞬间翻转为一张带有具体描述和倒计时的【红色危机卡】。
|
||||
* **US3.3 危机解除:** 作为玩家,我希望能在倒计时结束前,将带有对应 Tag 的卡牌(如带有 `need_test` 标签的“测试员卡”)拖入危机卡的解决槽位中,以消除危机并获得资金奖励。
|
||||
* **US3.4 危机惩罚:** 作为玩家,我希望如果危机卡倒计时归零我仍未解决,系统会立即执行惩罚(如扣除 50000 资金),若资金低于 0 则触发 Game Over。
|
||||
|
||||
### Epic 4: Roguelike 循环与结算 (The Loop)
|
||||
* **US4.1 Sprint 结算:** 作为玩家,我希望在全局 Sprint 倒计时(如 3 分钟)结束且我存活时,系统暂停桌面,弹出胜利结算画面。
|
||||
* **US4.2 局外成长 (3选1):** 作为玩家,我希望在结算画面能看到 3 个随机的奖励选项(如新卡牌、永久遗物、删除垃圾卡),我可以选择其一加入下一轮,以获得肉鸽游戏的成长快感。
|
||||
|
||||
---
|
||||
|
||||
# 🧪 第二部分:核心测试用例 (Test Cases)
|
||||
|
||||
这些测试用例主要针对**状态机逻辑**和**异步 API 处理**,这是 VibeCoding(AI 写代码)时最容易出 Bug 的地方。
|
||||
|
||||
### 模块一:合成状态机 (Crafting State Machine)
|
||||
|
||||
| 用例编号 | 测试模块 | 测试标题 | 前置条件 | 操作步骤 | 预期结果 (Expected Result) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **TC-1.1** | 合成引擎 | **基础配方成功合成** | 桌面上有一张“开发”和“脏代码”,配方需 5 秒。 | 1. 将“开发”拖拽到“脏代码”上并松开。<br>2. 等待 5 秒。 | 1. 两张卡被锁定,出现 5 秒进度条。<br>2. 5秒后,两张卡消失,原地生成一张“干净的代码”。 |
|
||||
| **TC-1.2** | 合成引擎 | **合成中途强行打断 (Edge Case)** | “开发”与“脏代码”正在读条合成中(第 2 秒)。 | 1. 玩家用鼠标强行拖走处于底部的“脏代码”卡。 | 1. 进度条立即取消。<br>2. 两张卡恢复独立状态,不产出任何新卡,也不消耗原有卡牌。 |
|
||||
| **TC-1.3** | 合成引擎 | **多重副产物概率掉落** | 触发“脏代码”+“Vibe助手”配方(设定 100%产出模块,20%产出Bug)。 | 1. 连续触发该配方 10 次并观察产出。 | 每次必定产出“可用模块”,大约有 2 次额外在旁边掉落一张“Bug”卡。 |
|
||||
|
||||
### 模块二:LLM 危机触发与网络降级 (AI Director & Fallback)
|
||||
|
||||
| 用例编号 | 测试模块 | 测试标题 | 前置条件 | 操作步骤 | 预期结果 (Expected Result) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **TC-2.1** | LLM 系统 | **正常的危机生成链路** | 游戏运行到第 30 秒,触发危机心跳检测。网络正常,API 延迟为 2 秒。 | 1. 观察桌面变化。<br>2. 等待 2 秒后。 | 1. 第30秒时,桌面上生成一张【预警卡】并播放 Loading 动画。<br>2. 第32秒时,预警卡瞬间变成【危机卡】,显示 LLM 返回的 JSON 描述,倒计时开始。 |
|
||||
| **TC-2.2** | LLM 系统 | **API 超时兜底机制 (断网测试)** | 拦截或断开本地网络,触发危机心跳检测。设定超时阈值为 5 秒。 | 1. 观察桌面出现【预警卡】。<br>2. 计时等待超过 5 秒。 | 1. 前 5 秒维持 Loading 状态。<br>2. 第 5.1 秒,触发兜底机制,预警卡变为本地 JSON 表中的随机通用危机(如“服务器宕机”),游戏不会崩溃卡死。 |
|
||||
| **TC-2.3** | LLM 系统 | **危机成功解除逻辑** | 桌面上存在一张危机卡,要求 `need_pm` (需要产品) 标签。 | 1. 拖拽一张无标签的“咖啡”上去。<br>2. 拖拽一张带 `need_pm` 标签的“产品经理”上去。 | 1. 拖拽咖啡无效,弹开。<br>2. 拖拽产品经理后,危机卡被锁定读条 3 秒,随后危机卡消失,顶部资金增加。 |
|
||||
| **TC-2.4** | LLM 系统 | **危机超时惩罚触发** | 桌面上存在一张“资金扣除”危机卡(倒计时 10 秒,惩罚 50000)。 | 1. 玩家不进行任何操作,等待 10 秒倒计时归零。 | 1. 倒计时变为 0,危机卡爆裂并消失。<br>2. 顶部面板的【项目资金】瞬间扣除 50000。 |
|
||||
|
||||
### 模块三:核心循环与边界判定 (Core Loop Edge Cases)
|
||||
|
||||
| 用例编号 | 测试模块 | 测试标题 | 前置条件 | 操作步骤 | 预期结果 (Expected Result) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **TC-3.1** | 全局状态 | **资金归零触发 Game Over** | 玩家当前资金为 10000。桌面存在一张惩罚为扣除 20000 的危机卡。 | 1. 让该危机卡倒计时归零触发惩罚。 | 1. 资金被扣至 -10000。<br>2. 游戏立即暂停,弹出“项目破产 (Game Over)”结算弹窗,无法继续拖拽卡牌。 |
|
||||
| **TC-3.2** | 全局状态 | **倒计时结束正常结算** | 当前 Sprint 全局倒计时剩余 3 秒,资金为 5000,且桌面有一张正在读条的危机卡。 | 1. 等待 3 秒全局倒计时归零。 | 1. 游戏立即暂停,危机卡倒计时冻结。<br>2. 弹出“Sprint 成功”弹窗,进入 3选1 的 Roguelike 奖励界面。 |
|
||||
|
||||
132
03-技术架构关键路径.md
Normal file
132
03-技术架构关键路径.md
Normal file
@@ -0,0 +1,132 @@
|
||||
|
||||
|
||||
# 🏗️ 《Project Vibe: Ship It!》技术架构与关键路径文档
|
||||
|
||||
## 一、 技术栈选型 (Tech Stack)
|
||||
*强烈建议在 Prompt 开头向 AI 明确这些技术栈,防止它自由发挥引入不兼容的库。*
|
||||
|
||||
* **前端框架:** React 18 + Vite (纯前端单页应用,无需复杂后端)。
|
||||
* **开发语言:** TypeScript (*极其重要!严格的类型定义是 VibeCoding 不翻车的绝对保障*)。
|
||||
* **状态管理:** **Zustand** (*极其推荐!比 Redux 轻量,比 Context 性能好,非常适合做游戏的状态机更新*)。
|
||||
* **样式方案:** Tailwind CSS (*方便 AI 快速生成好看的 UI 卡片*)。
|
||||
* **拖拽与动画:** `framer-motion` (处理卡牌平滑移动和翻转动画) + 原生 Pointer Events (处理自由桌面坐标 x,y,**不要用**基于网格的 `dnd-kit`,因为我们要的是类似操作系统的无网格自由堆叠)。
|
||||
|
||||
---
|
||||
|
||||
## 二、 核心数据模型 (Data Models - TypeScript Interfaces)
|
||||
*这是整个系统的灵魂。第一步请先让 AI 生成并完善这些类型定义文件 `types.ts`。有了它,AI 后续写的逻辑就不会乱。*
|
||||
|
||||
```typescript
|
||||
// 1. 卡牌基础模型
|
||||
export interface Card {
|
||||
id: string; // 唯一实例ID (uuid)
|
||||
templateId: string; // 对应配置表中的模板ID (如 'dirty_code', 'developer')
|
||||
name: string;
|
||||
type: 'resource' | 'actor' | 'module' | 'crisis' | 'warning';
|
||||
tags: string[]; // 用于危机判定 (如 ['need_test', 'human'])
|
||||
position: { x: number; y: number }; // 桌面绝对坐标
|
||||
status: 'idle' | 'dragging' | 'processing' | 'locked';
|
||||
processEndTime?: number; // 如果在合成中/危机倒计时中,记录结束的绝对时间戳
|
||||
}
|
||||
|
||||
// 2. 合成配方模型 (静态配置)
|
||||
export interface Recipe {
|
||||
id: string;
|
||||
inputs: string[]; // 需要堆叠在一起的 templateId 列表
|
||||
timeMs: number; // 合成所需时间 (毫秒)
|
||||
outputs: Array<{ templateId: string; chance: number }>; // 产物及概率
|
||||
}
|
||||
|
||||
// 3. 全局状态模型 (Zustand Store)
|
||||
export interface GameState {
|
||||
// --- 资源状态 ---
|
||||
funds: number; // 项目资金
|
||||
sprintTimeRemaining: number; // 当前 Sprint 倒计时
|
||||
sprintNumber: number; // 当前处于第几个 Sprint
|
||||
|
||||
// --- 实体状态 ---
|
||||
cards: Card[]; // 桌面上的所有卡牌
|
||||
|
||||
// --- 核心方法 (Actions) ---
|
||||
moveCard: (id: string, x: number, y: number) => void;
|
||||
checkCraftingOverlap: (cardId: string) => void; // 碰撞检测核心逻辑
|
||||
tick: () => void; // 游戏主循环 (Game Loop),每帧/每秒更新状态
|
||||
triggerCrisis: (crisisData: any) => void; // LLM 返回后触发危机
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、 系统架构设计图 (单向数据流)
|
||||
|
||||
游戏的架构必须是**数据驱动**的。UI 层只负责渲染 `cards` 数组和捕获鼠标事件。
|
||||
|
||||
```text
|
||||
[ 玩家操作 (拖拽/松开) ]
|
||||
│
|
||||
▼
|
||||
[ 操作意图 (Pointer Events) ] ──发起请求──> [ LLM 危机生成器 (异步 Hook) ]
|
||||
│ │ (返回JSON/超时Fallback)
|
||||
▼ ▼
|
||||
[ 碰撞检测算法 (Overlap Check) ] [ 触发预警卡/危机卡生成 ]
|
||||
│ │
|
||||
▼ (匹配 Recipe) │
|
||||
[ 状态机引擎 (Zustand Store) ] <─────────────────┘
|
||||
├─ 更新卡牌坐标 (x, y)
|
||||
├─ 锁定卡牌状态 (status: 'processing')
|
||||
└─ 结算资金/时间 (funds, sprintTime)
|
||||
│
|
||||
▼ (响应式数据)
|
||||
[ React 渲染层 (UI Components) ]
|
||||
├─ <DesktopBoard /> (渲染桌面)
|
||||
├─ <CardItem /> (渲染实体卡、倒计时进度条)
|
||||
└─ <HudPanel /> (渲染顶部资金与时间)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、 VibeCoding 关键开发路径 (Critical Paths)
|
||||
|
||||
千万不要让 AI “一口气写一个游戏”。请严格按照以下 4 个阶段向 AI 提需求 (Prompting),每完成一个阶段就运行测试,确保无误后再进入下一阶段。
|
||||
|
||||
### 🚩 Path 1: 静态桌面与全局状态 (The Dumb Board)
|
||||
* **目标:** 能在屏幕上看到几张卡牌,资金数值能够显示。
|
||||
* **AI 任务:**
|
||||
1. 初始化 Vite + React + TS 项目,安装 Zustand 和 Tailwind。
|
||||
2. 创建 `types.ts`(使用上面提供的模型)。
|
||||
3. 创建 `useGameStore.ts` (Zustand),初始化几个假数据卡牌 (Mock Data)。
|
||||
4. 编写 `<DesktopBoard>` 和 `<CardItem>` 组件。卡牌通过绝对定位 `absolute` 渲染在对应的 `x, y` 坐标上。
|
||||
|
||||
### 🚩 Path 2: 自由拖拽与物理碰撞 (Drag & Collision)
|
||||
* **目标:** 卡牌可以被鼠标拖拽,松开鼠标时,能判断两张卡是否重叠。
|
||||
* **AI 任务:**
|
||||
1. 给 `<CardItem>` 添加 `onPointerDown`, `onPointerMove`, `onPointerUp` 事件。
|
||||
2. 拖拽时,调用 Store 的 `moveCard` 更新坐标。
|
||||
3. **核心算法要求:** 松开鼠标时,写一个 `checkOverlap(cardA, cardB)` 函数(通过判断两个矩形的边界 bounding box 是否相交)。如果重叠率超过 50%,则判定为“堆叠成功”。
|
||||
|
||||
### 🚩 Path 3: 核心主循环与合成引擎 (Game Loop & Crafting)
|
||||
* **目标:** 两张配方卡重叠 -> 出现进度条 -> 读条结束产出新卡。
|
||||
* **AI 任务:**
|
||||
1. 创建一个静态的 `recipes.json`(包含 2-3 个基础配方,如 开发+脏代码=模块)。
|
||||
2. 在 Store 中创建一个 `requestAnimationFrame` 或 `setInterval(..., 100)` 驱动的 `tick()` 函数(这就是游戏的心跳)。
|
||||
3. **合成逻辑:** 当卡牌重叠,匹配 JSON 成功后,将这两张卡的 status 改为 `processing`,并设置 `processEndTime = Date.now() + timeMs`。
|
||||
4. **心跳检测:** `tick()` 函数不断检查当前时间是否超过 `processEndTime`。如果超过,删除旧卡,在原坐标 `push` 新产物卡。
|
||||
|
||||
### 🚩 Path 4: LLM 异步危机总线 (The AI Director)
|
||||
* **目标:** 每隔一段时间,根据桌面状态调用大模型,生成危机并处理延迟。
|
||||
* **AI 任务:**
|
||||
1. 编写一个独立的服务 `llmService.ts`。
|
||||
2. 编写 `useCrisisDirector` 自定义 Hook。内部设置一个定时器(如每 30 秒触发一次)。
|
||||
3. **时序逻辑编写:**
|
||||
* 触发时:立刻在 Store 中推入一张 status 为 `processing` 的【预警卡】。
|
||||
* 发起 `fetch` 请求调用 OpenAI/DeepSeek API,附带当前桌面状态。
|
||||
* 使用 `Promise.race` 实现 5 秒超时控制。
|
||||
* 请求 Resolve (或超时 Fallback) 时:将那张【预警卡】的数据替换为真实的【危机卡】JSON 数据,并开始倒计时。
|
||||
|
||||
---
|
||||
|
||||
## 💡 给开发者的 VibeCoding 避坑指南 (Tips)
|
||||
|
||||
1. 这是一个自由摆放的桌面,没有列表,只需要纯粹的 x,y 坐标更新和基于宽高的 AABB (Axis-Aligned Bounding Box) 碰撞检测。”
|
||||
2. 在 Zustand store 里统一管理所有状态。**不要**在 `<CardItem>` 组件内部用 `useState` 去管理倒计时!所有的进度条渲染都应该基于 `processEndTime - Date.now()` 实时计算得出,这样即使组件卸载/重新渲染,进度也不会丢失。
|
||||
3. **Mock First:** 在写 LLM 接入时,用 `setTimeout` 模拟一个假的大模型返回。把游戏跑通了,最后再填入真实的 API Key 和 `fetch` 请求。
|
||||
277
css/main.css
Normal file
277
css/main.css
Normal file
@@ -0,0 +1,277 @@
|
||||
/* css/main.css */
|
||||
|
||||
/* 背景图案生成 */
|
||||
.bg-desktop-pattern {
|
||||
background-image: radial-gradient(circle at 1px 1px, #334155 1px, transparent 0);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 卡片基础样式 - 核心交互与视觉 */
|
||||
.game-card, .stack-group {
|
||||
width: 170px;
|
||||
height: 240px;
|
||||
position: absolute;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
||||
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: visible; /* 允许进度条等元素溢出 */
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
cursor: grab;
|
||||
background-color: #1e293b;
|
||||
color: #f8fafc;
|
||||
will-change: transform, left, top;
|
||||
}
|
||||
|
||||
/* 正在被拖拽的状态 */
|
||||
.game-card.dragging, .stack-group.dragging {
|
||||
cursor: grabbing;
|
||||
transform: scale(1.08) rotate(2deg) translateY(-5px);
|
||||
box-shadow: 0 25px 35px -5px rgba(0, 0, 0, 0.5), 0 10px 15px -5px rgba(0, 0, 0, 0.3);
|
||||
z-index: 100 !important;
|
||||
transition: none; /* 拖拽时取消缓动动画,跟随鼠标更紧密 */
|
||||
}
|
||||
|
||||
/* 悬停时的微动效 */
|
||||
.game-card:hover:not(.dragging), .stack-group:hover:not(.dragging) {
|
||||
transform: translateY(-4px) scale(1.02);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* 卡牌内部结构 */
|
||||
.card-header {
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 14px 14px 0 0;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1;
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.2) 100%);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 10px 12px;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
border-top: 1px solid rgba(255,255,255,0.08);
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 0 0 14px 14px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
color: #cbd5e1;
|
||||
font-weight: 500;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
/* ================= 职业卡系主题 ================= */
|
||||
|
||||
/* 实体角色 (Actor) */
|
||||
.card-actor {
|
||||
background: linear-gradient(145deg, #1e3a8a, #1d4ed8);
|
||||
border-color: rgba(59, 130, 246, 0.5);
|
||||
box-shadow: 0 0 15px rgba(29, 78, 216, 0.2);
|
||||
}
|
||||
|
||||
/* 资源卡 (Resource) */
|
||||
.card-resource {
|
||||
background: linear-gradient(145deg, #064e3b, #047857);
|
||||
border-color: rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
/* 负面卡 (Negative) */
|
||||
.card-negative {
|
||||
background: linear-gradient(145deg, #4c1d95, #6d28d9);
|
||||
border-color: rgba(139, 92, 246, 0.4);
|
||||
}
|
||||
|
||||
/* 目标模块卡 (Module) */
|
||||
.card-module {
|
||||
background: linear-gradient(145deg, #78350f, #92400e);
|
||||
border-color: rgba(245, 158, 11, 0.5);
|
||||
}
|
||||
|
||||
/* 危机预警卡 (Warning) */
|
||||
.card-warning {
|
||||
background: linear-gradient(145deg, #450a0a, #7f1d1d);
|
||||
border-color: rgba(239, 68, 68, 0.6);
|
||||
animation: pulse-border 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* 危机爆发卡 (Crisis) */
|
||||
.card-crisis {
|
||||
background: linear-gradient(145deg, #2a0808, #5a1010);
|
||||
border-color: #f87171;
|
||||
box-shadow: 0 0 30px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
|
||||
@keyframes pulse-border {
|
||||
0%, 100% {
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
box-shadow: 0 0 15px rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
50% {
|
||||
border-color: rgba(239, 68, 68, 0.9);
|
||||
box-shadow: 0 0 25px rgba(239, 68, 68, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= 组件 ================= */
|
||||
|
||||
/* 进度条 (Processing Bar) */
|
||||
.progress-container {
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 110%;
|
||||
height: 14px;
|
||||
background: #0f172a;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #334155;
|
||||
overflow: hidden;
|
||||
z-index: 20;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #3b82f6, #60a5fa);
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 2px 4px rgba(255,255,255,0.3);
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
/* 危机槽位 (Slots) */
|
||||
.crisis-slot {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: 2px dashed rgba(255,255,255,0.2);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.crisis-slot:hover {
|
||||
border-color: rgba(255,255,255,0.5);
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.crisis-slot.filled {
|
||||
border: 2px solid #10b981;
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
box-shadow: 0 0 15px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
/* Loading 动画 (API Requesting) */
|
||||
.loader {
|
||||
border: 4px solid rgba(255,255,255,0.05);
|
||||
border-top: 4px solid #f87171;
|
||||
border-radius: 50%;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
animation: spin 1s linear infinite;
|
||||
box-shadow: 0 0 15px rgba(248, 113, 113, 0.2);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 栈 (Stack Group) 调整,取消单独卡牌样式因为外层是一个容器 */
|
||||
.stack-group {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Stack Group 内的卡牌取消自身悬停动效以防止冲突 */
|
||||
.stack-group .game-card {
|
||||
position: absolute;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.stack-group .game-card:hover {
|
||||
transform: none;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* 高亮可连接卡牌 (Highlight Connectable) */
|
||||
.highlight-connectable {
|
||||
box-shadow: 0 0 20px 5px rgba(59, 130, 246, 0.6) !important;
|
||||
border-color: #60a5fa !important;
|
||||
transform: scale(1.05);
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
/* 新卡牌生成弹现动效 */
|
||||
@keyframes card-spawn {
|
||||
0% { transform: scale(0) translateY(-20px); opacity: 0; filter: blur(10px); }
|
||||
60% { transform: scale(1.05) translateY(5px); opacity: 1; filter: blur(0); }
|
||||
100% { transform: scale(1) translateY(0); opacity: 1; filter: blur(0); }
|
||||
}
|
||||
|
||||
/* 正在被连线悬停 (Hovering Connection Target) */
|
||||
@keyframes magnetic-fusion {
|
||||
0% { transform: scale(1.1) rotate(0deg); box-shadow: 0 0 60px 20px rgba(59,130,246,0.9), inset 0 0 20px rgba(59,130,246,0.8); border-color: #60a5fa; filter: brightness(1.2); }
|
||||
33% { transform: scale(0.9) rotate(-3deg); box-shadow: 0 0 80px 30px rgba(139,92,246,0.9), inset 0 0 30px rgba(139,92,246,0.8); border-color: #c084fc; filter: brightness(1.5); }
|
||||
66% { transform: scale(1.1) rotate(3deg); box-shadow: 0 0 80px 30px rgba(236,72,153,0.9), inset 0 0 30px rgba(236,72,153,0.8); border-color: #f472b6; filter: brightness(1.5); }
|
||||
100% { transform: scale(1.1) rotate(0deg); box-shadow: 0 0 60px 20px rgba(59,130,246,0.9), inset 0 0 20px rgba(59,130,246,0.8); border-color: #60a5fa; filter: brightness(1.2); }
|
||||
}
|
||||
|
||||
.highlight-hover {
|
||||
animation: magnetic-fusion 0.6s infinite ease-in-out !important;
|
||||
border-width: 3px !important;
|
||||
z-index: 90 !important;
|
||||
}
|
||||
|
||||
/* 危机卡悬停解救 (Hovering Crisis Resolution) */
|
||||
@keyframes crisis-resolve-hover {
|
||||
0%, 100% { transform: scale(1.15); box-shadow: 0 0 100px 40px rgba(16, 185, 129, 0.8); border-color: #34d399; filter: brightness(2) saturate(2); }
|
||||
50% { transform: scale(1.05); box-shadow: 0 0 60px 20px rgba(16, 185, 129, 0.6); border-color: #10b981; filter: brightness(1.5) saturate(1.5); }
|
||||
}
|
||||
|
||||
.highlight-crisis-hover {
|
||||
animation: crisis-resolve-hover 0.5s infinite ease-in-out !important;
|
||||
border-width: 4px !important;
|
||||
z-index: 95 !important;
|
||||
}
|
||||
|
||||
/* 连线 SVG 画布 */
|
||||
#svg-connections {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
157
dashboard.html
Normal file
157
dashboard.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>工作台 - Project Vibe</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="./css/main.css">
|
||||
</head>
|
||||
<body class="h-screen w-screen bg-[#1e293b] font-sans text-slate-100 flex flex-col overflow-hidden relative">
|
||||
|
||||
<!-- 顶栏 -->
|
||||
<header class="h-16 bg-slate-900/80 backdrop-blur-md border-b border-slate-700 flex items-center justify-between px-8 z-50">
|
||||
<h1 class="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-emerald-400 tracking-wider">
|
||||
WORKSTATION 🚀
|
||||
</h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm text-slate-400">工号: DEV-9527</span>
|
||||
<button onclick="window.location.href='login.html'" class="text-xs bg-slate-800 hover:bg-rose-900/50 text-slate-300 hover:text-rose-400 px-3 py-1.5 rounded border border-slate-700 transition-colors">注销</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主容器 -->
|
||||
<div class="flex-1 flex overflow-hidden p-6 gap-6 relative z-10">
|
||||
|
||||
<!-- 左侧:新建项目 -->
|
||||
<div class="w-1/3 max-w-sm flex flex-col gap-6">
|
||||
<div class="bg-gradient-to-br from-slate-800/80 to-slate-900/80 border border-slate-600 rounded-2xl p-8 flex flex-col items-center text-center shadow-lg">
|
||||
<div class="text-6xl mb-6">📝</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-2">新的 Sprint</h2>
|
||||
<p class="text-sm text-slate-400 mb-8 leading-relaxed">你的上一个项目由于经费见底被公司强制终止。<br>准备好接手这个充满技术债的屎山新盘了吗?</p>
|
||||
<button onclick="window.location.href='index.html'" class="w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-4 px-6 rounded-xl shadow-[0_0_20px_rgba(37,99,235,0.4)] transition-transform hover:scale-105 tracking-widest text-lg uppercase flex justify-center items-center gap-2">
|
||||
<span>开始接锅</span>
|
||||
<span class="text-2xl">➔</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-800/50 border border-slate-700 rounded-xl p-4 flex gap-4 text-sm text-slate-400 items-center">
|
||||
<div class="text-3xl">🏆</div>
|
||||
<div>
|
||||
<div class="font-bold text-slate-200 mb-1">历史最高纪录</div>
|
||||
<div class="font-mono text-emerald-400">总资金: $ 25,000</div>
|
||||
<div class="font-mono text-blue-400">存活 Sprint: 3</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:我的卡组与图鉴 -->
|
||||
<div class="flex-1 bg-slate-800/60 border border-slate-600 rounded-2xl flex flex-col overflow-hidden shadow-lg">
|
||||
<div class="p-4 border-b border-slate-700 bg-slate-900/40 flex justify-between items-center">
|
||||
<h2 class="text-lg font-bold text-white flex items-center gap-2">🗃️ 我的卡组 (Deck Library)</h2>
|
||||
<span class="text-xs bg-slate-800 text-slate-400 px-2 py-1 rounded">当前已解锁 5/12 张</span>
|
||||
</div>
|
||||
|
||||
<!-- 卡片网格区 -->
|
||||
<div class="flex-1 overflow-y-auto p-6" id="deck-container">
|
||||
<div class="text-center text-slate-400 py-10">加载卡池数据中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./js/main.js"></script>
|
||||
<script>
|
||||
// Override game loop in dashboard so it doesn't crash or run
|
||||
window.gameInterval = null;
|
||||
window.secondInterval = null;
|
||||
|
||||
function renderDeck() {
|
||||
const container = document.getElementById('deck-container');
|
||||
const goodCards = [];
|
||||
const badCards = [];
|
||||
|
||||
Object.values(CardTemplates).forEach(tpl => {
|
||||
if (tpl.type === 'negative' || tpl.type === 'crisis' || tpl.tags.includes('bad') || tpl.tags.includes('crisis')) {
|
||||
badCards.push(tpl);
|
||||
} else {
|
||||
goodCards.push(tpl);
|
||||
}
|
||||
});
|
||||
|
||||
// Find recipes for a card
|
||||
const getRecipes = (cardId) => {
|
||||
return Recipes.filter(r => r.inputs.includes(cardId)).map(r => {
|
||||
const otherInput = r.inputs.find(i => i !== cardId) || cardId;
|
||||
const out1 = r.outputs[0]?.templateId;
|
||||
|
||||
const icon1 = `<span title="${CardTemplates[otherInput]?.name || '?'}" class="cursor-help border-b border-dashed border-slate-500">${CardTemplates[otherInput]?.icon || '?'}</span>`;
|
||||
const icon2 = `<span title="${CardTemplates[cardId]?.name || '?'}" class="cursor-help border-b border-dashed border-slate-500">${CardTemplates[cardId]?.icon || '?'}</span>`;
|
||||
const outIcon = out1 ? `<span title="${CardTemplates[out1]?.name}" class="cursor-help border-b border-dashed border-emerald-500">${CardTemplates[out1]?.icon}</span>` : '?';
|
||||
|
||||
return `<div class="flex items-center gap-1">${icon1} <span>+</span> ${icon2} <span class="text-emerald-400 font-bold ml-1">➔</span> ${outIcon}</div>`;
|
||||
}).join('');
|
||||
};
|
||||
|
||||
const buildGrid = (cards, isBad) => {
|
||||
let html = '<div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">';
|
||||
cards.forEach(tpl => {
|
||||
const isLocked = !isBad && tpl.unlocked === false;
|
||||
const recs = getRecipes(tpl.id);
|
||||
|
||||
let colorClasses = '';
|
||||
if (isLocked) {
|
||||
colorClasses = 'bg-slate-900/60 border-slate-700/50 grayscale opacity-50 relative pointer-events-none';
|
||||
} else if (isBad) {
|
||||
colorClasses = 'bg-red-900/20 border-red-600/50 hover:border-red-400 shadow-[0_0_8px_rgba(220,38,38,0.2)] hover:scale-105 group relative overflow-hidden transition-transform';
|
||||
} else {
|
||||
colorClasses = 'bg-blue-900/20 border-blue-600/50 hover:border-blue-400 shadow-[0_0_8px_rgba(37,99,235,0.2)] hover:scale-105 group relative overflow-hidden transition-transform';
|
||||
}
|
||||
|
||||
const textClass = isBad ? 'text-red-300' : (isLocked ? 'text-slate-400' : 'text-blue-200');
|
||||
const tagClass = isBad ? 'bg-red-900 text-red-300' : (isLocked ? 'bg-slate-800 text-slate-500' : 'bg-blue-900 text-blue-300');
|
||||
|
||||
const lockOverlay = isLocked ? `<div class="absolute inset-0 bg-slate-950/20 flex items-center justify-center backdrop-blur-[2px] z-10"><span class="text-3xl text-slate-300">🔒</span></div>` : '';
|
||||
|
||||
html += `
|
||||
<div class="${colorClasses} border-2 rounded-xl p-4 flex flex-col items-center text-center">
|
||||
${lockOverlay}
|
||||
<div class="text-5xl mb-2 relative z-0">${tpl.icon}</div>
|
||||
<h3 class="font-bold ${textClass} mb-1 relative z-0">${isLocked ? '???' : tpl.name}</h3>
|
||||
<span class="text-[10px] ${tagClass} px-2 py-0.5 rounded mb-3 relative z-0">${tpl.type}</span>
|
||||
<p class="text-xs text-slate-400 mb-4 h-10 relative z-0">${isLocked ? '在后续迭代中解锁。' : tpl.desc}</p>
|
||||
<div class="w-full bg-slate-900/60 rounded p-2 text-left min-h-[60px] relative z-20">
|
||||
<div class="text-[10px] ${isBad ? 'text-rose-500' : 'text-slate-500'} uppercase tracking-widest mb-1 font-bold">${isBad ? (tpl.trigger === 'countdown' ? '倒计时惩罚' : '惩罚结果') : '相关配方'}</div>
|
||||
<div class="text-xs text-slate-300 flex flex-col gap-1 mb-2">${isBad ? ('<span class="text-rose-400">' + (tpl.penalty || '无') + '</span>') : (recs || '<span class="text-slate-600">无配方</span>')}</div>
|
||||
|
||||
${isBad ? `
|
||||
<div class="text-[10px] text-emerald-500 uppercase tracking-widest mb-1 font-bold">化解方案</div>
|
||||
<div class="text-xs text-emerald-300 flex flex-wrap items-center gap-1">拖入 ${tpl.cancelWith ? tpl.cancelWith.map(id => `<span title="${CardTemplates[id]?.name}" class="cursor-help border-b border-dashed border-emerald-500 text-lg">${CardTemplates[id]?.icon}</span>`).join(' <span class="text-emerald-500/50">/</span> ') : ''} 抵消</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
return html;
|
||||
};
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="mb-4">
|
||||
<h3 class="text-sm font-bold text-blue-400 uppercase tracking-widest mb-4">🗃️ 己方手牌 (${goodCards.length} 张)</h3>
|
||||
${buildGrid(goodCards, false)}
|
||||
</div>
|
||||
|
||||
<div class="mt-8 mb-4 flex items-center gap-4">
|
||||
<div class="h-px bg-slate-700 flex-1"></div>
|
||||
<h3 class="text-sm font-bold text-rose-400 uppercase tracking-widest">⚠️ 系统风险 (${badCards.length} 张)</h3>
|
||||
<div class="h-px bg-slate-700 flex-1"></div>
|
||||
</div>
|
||||
${buildGrid(badCards, true)}
|
||||
`;
|
||||
|
||||
document.querySelector('.text-xs.bg-slate-800').textContent = `当前已录入 ${goodCards.length + badCards.length} 张卡`;
|
||||
}
|
||||
|
||||
renderDeck();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
47
index.html
Normal file
47
index.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Project Vibe: Ship It! - 完整流程高保真原型</title>
|
||||
<!-- 引入 TailwindCSS CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- 引入自定义样式 -->
|
||||
<link rel="stylesheet" href="./css/main.css">
|
||||
</head>
|
||||
<body class="h-screen w-screen relative overflow-hidden bg-[#1e1e2f] select-none font-sans text-slate-100">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div id="app-container"></div>
|
||||
<!-- 引入主逻辑前,先加载视图 -->
|
||||
<script>
|
||||
async function loadViews() {
|
||||
const views = ['screen-start', 'screen-game', 'screen-win', 'screen-gameover'];
|
||||
const container = document.getElementById('app-container');
|
||||
for(let v of views) {
|
||||
const res = await fetch('./views/' + v + '.html');
|
||||
const htmlStr = await res.text();
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = htmlStr;
|
||||
container.appendChild(div.firstElementChild);
|
||||
}
|
||||
// 加载完成后加载主脚本
|
||||
const script = document.createElement('script');
|
||||
script.src = './js/main.js';
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
loadViews();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1357
js/main.js
Normal file
1357
js/main.js
Normal file
File diff suppressed because it is too large
Load Diff
38
login.html
Normal file
38
login.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - Project Vibe: Ship It!</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="./css/main.css">
|
||||
</head>
|
||||
<body class="h-screen w-screen bg-[#0f172a] flex items-center justify-center font-sans text-slate-100 overflow-hidden relative">
|
||||
<!-- 背景装饰 -->
|
||||
<div class="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGNpcmNsZSBjeD0iMiIgY3k9IjIiIHI9IjEiIGZpbGw9IiMzMzQxNTUiLz48L3N2Zz4=')] opacity-20 pointer-events-none"></div>
|
||||
|
||||
<div class="bg-slate-800/80 border border-slate-600 rounded-2xl p-10 w-full max-w-md relative z-10 shadow-2xl backdrop-blur-md">
|
||||
<div class="text-center mb-10">
|
||||
<h1 class="text-4xl font-black text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-emerald-400 tracking-tighter mb-2">
|
||||
Project Vibe
|
||||
</h1>
|
||||
<p class="text-sm text-slate-400 font-mono tracking-widest">>>> 研发协同终端 v2.0</p>
|
||||
</div>
|
||||
|
||||
<form onsubmit="event.preventDefault(); window.location.href='dashboard.html';" class="flex flex-col gap-6">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">员工工号 (ID)</label>
|
||||
<input type="text" value="DEV-9527" class="w-full bg-slate-900/50 border border-slate-600 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">访问密钥 (Token)</label>
|
||||
<input type="password" value="********" class="w-full bg-slate-900/50 border border-slate-600 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="mt-4 w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-3 px-4 rounded-lg shadow-[0_0_15px_rgba(37,99,235,0.4)] transition-all hover:scale-[1.02]">
|
||||
接入系统 (Login)
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
136
views/screen-game.html
Normal file
136
views/screen-game.html
Normal file
@@ -0,0 +1,136 @@
|
||||
<div id="screen-game" class="screen absolute inset-0 hidden z-10">
|
||||
<!-- 桌面背景网格 -->
|
||||
<div class="absolute inset-0 bg-desktop-pattern pointer-events-none opacity-50"></div>
|
||||
<svg id="svg-connections" class="absolute inset-0 w-full h-full pointer-events-none z-[15]"></svg>
|
||||
|
||||
<!-- 顶部 HUD (状态栏) -->
|
||||
<header class="absolute top-0 left-0 w-full h-16 bg-slate-900/80 backdrop-blur-md border-b border-slate-700 flex items-center justify-between px-6 z-50 shadow-lg">
|
||||
<!-- 左侧:项目信息 -->
|
||||
<div class="flex items-center gap-4 w-1/3">
|
||||
<div class="flex items-center gap-2 bg-slate-800/80 px-3 py-1.5 rounded-md border border-slate-600/50">
|
||||
<span class="text-xs text-slate-400 uppercase font-bold tracking-wider">Sprint</span>
|
||||
<span id="hud-sprint" class="text-lg font-bold text-white">1</span>
|
||||
</div>
|
||||
<div class="hidden xl:flex items-center gap-2 bg-slate-800/80 px-3 py-1.5 rounded-md border border-slate-600/50">
|
||||
<span class="text-xs text-slate-400 uppercase font-bold tracking-wider">复杂度额度</span>
|
||||
<span id="hud-capacity" class="text-sm font-mono font-bold text-cyan-300">20 / 20</span>
|
||||
</div>
|
||||
<div class="hidden xl:flex items-center gap-2 bg-slate-800/80 px-3 py-1.5 rounded-md border border-slate-600/50">
|
||||
<span class="text-xs text-slate-400 uppercase font-bold tracking-wider">延期阶段</span>
|
||||
<span id="hud-overdue-stage" class="text-sm font-bold text-emerald-300">正常</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中侧:挣值管理 (EVM) -->
|
||||
<div class="flex-1 flex justify-center gap-4">
|
||||
<div class="bg-slate-950/80 px-6 py-2 rounded-full border border-amber-900/50 shadow-inner flex items-center gap-3">
|
||||
<span class="text-amber-500">📈</span>
|
||||
<span class="text-sm text-slate-300 font-bold tracking-wide">挣值(EV):</span>
|
||||
<span class="text-sm font-mono text-amber-400 font-bold bg-amber-900/30 px-2 py-0.5 rounded" id="hud-ev">$0 / $5000</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/80 px-6 py-2 rounded-full border border-blue-900/50 shadow-inner flex items-center gap-3">
|
||||
<span class="text-blue-500">🛡️</span>
|
||||
<span class="text-sm text-slate-300 font-bold tracking-wide">应急储备:</span>
|
||||
<span class="text-sm font-mono text-blue-400 font-bold bg-blue-900/30 px-2 py-0.5 rounded" id="hud-reserve">$10000</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:核心资源与控制 (只留按钮,条移到右侧边栏) -->
|
||||
<div class="flex items-center gap-6 w-1/3 justify-end">
|
||||
<div id="hud-negative" class="hidden xl:block text-xs text-rose-200 bg-rose-950/40 px-3 py-1.5 rounded border border-rose-700/40">红卡压力:无</div>
|
||||
<div id="hud-status" class="hidden lg:block text-xs text-slate-300 bg-slate-800/80 px-3 py-1.5 rounded border border-slate-600/50">动作完成后才扣项目天数</div>
|
||||
<div class="flex gap-2 border-slate-700">
|
||||
<button onclick="toggleRecipeModal()" class="w-8 h-8 flex items-center justify-center bg-slate-800 hover:bg-slate-700 rounded text-slate-300 border border-slate-600 transition-colors" title="图鉴/配方手册">📖</button>
|
||||
<button class="w-8 h-8 flex items-center justify-center bg-slate-800 hover:bg-slate-700 rounded text-slate-300 border border-slate-600 transition-colors" title="暂停/继续" id="btn-pause">⏸</button>
|
||||
<button class="w-8 h-8 flex items-center justify-center bg-slate-800 hover:bg-slate-700 rounded text-slate-300 border border-slate-600 transition-colors" title="加速 (未启用)" id="btn-fast">▶▶</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 右侧边栏 (竖向进度条区) -->
|
||||
<div class="absolute right-6 top-24 bottom-10 z-30 flex gap-6 pointer-events-none">
|
||||
<!-- 资金竖条 -->
|
||||
<div class="flex flex-col items-center justify-end h-full">
|
||||
<div class="text-[10px] text-slate-400 font-bold uppercase tracking-wider mb-2 bg-slate-900/80 px-2 py-1 rounded">项目资金</div>
|
||||
<div class="relative flex-1 w-6 bg-slate-800/80 border border-slate-600 rounded-full overflow-hidden shadow-inner flex flex-col justify-end">
|
||||
<div id="hud-funds-bar" class="w-full bg-gradient-to-t from-emerald-600 to-emerald-400 transition-all duration-500 ease-out" style="height: 100%;"></div>
|
||||
</div>
|
||||
<div class="mt-2 text-center pointer-events-auto bg-slate-900/80 px-3 py-1.5 rounded-lg border border-emerald-900/50 relative">
|
||||
<span id="hud-funds-delta" class="absolute -top-6 left-1/2 -translate-x-1/2 text-sm font-mono font-bold text-emerald-400 opacity-0 transition-all duration-500"></span>
|
||||
<span id="hud-funds" class="text-xl font-mono font-bold text-emerald-400 drop-shadow-[0_0_8px_rgba(52,211,153,0.4)] transition-colors duration-300">$15,000</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 周期竖条 -->
|
||||
<div class="flex flex-col items-center justify-end h-full">
|
||||
<div class="text-[10px] text-slate-400 font-bold uppercase tracking-wider mb-2 bg-slate-900/80 px-2 py-1 rounded">迭代周期(天)</div>
|
||||
<div class="relative flex-1 w-6 bg-slate-800/80 border border-slate-600 rounded-full overflow-hidden shadow-inner flex flex-col justify-end">
|
||||
<div id="hud-time-bar" class="w-full bg-gradient-to-t from-rose-600 to-rose-400 transition-all duration-1000 ease-linear" style="height: 100%;"></div>
|
||||
</div>
|
||||
<div class="mt-2 text-center pointer-events-auto bg-slate-900/80 px-3 py-1.5 rounded-lg border border-rose-900/50 relative">
|
||||
<div class="text-xl font-mono font-bold text-rose-400 drop-shadow-[0_0_8px_rgba(244,63,94,0.4)]" id="hud-time">30天</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 桌面固定图标 (Shop & Zones) -->
|
||||
<div class="absolute inset-0 pt-20 px-6 pointer-events-none z-30 flex flex-col flex-wrap gap-6">
|
||||
<!-- 基础资源包 -->
|
||||
<button class="w-24 flex flex-col items-center pointer-events-auto group hover:-translate-y-2 transition-transform" onclick="addFunds(-100); addCard('focus', 150, 100); addCard('focus', 200, 100); renderBoard(); updateHUD();">
|
||||
<div class="w-16 h-16 bg-slate-800/80 border border-slate-600 rounded-2xl flex items-center justify-center text-4xl shadow-lg group-hover:border-blue-400 group-hover:shadow-blue-500/50">📦</div>
|
||||
<span class="text-xs font-bold mt-2 text-white drop-shadow-md text-center leading-tight">基础资源包<br><span class="text-emerald-400">$100</span></span>
|
||||
</button>
|
||||
|
||||
<!-- 社招简历包 -->
|
||||
<button class="w-24 flex flex-col items-center pointer-events-auto group hover:-translate-y-2 transition-transform" onclick="addFunds(-1000); addCard('developer', 150, 220); renderBoard(); updateHUD();">
|
||||
<div class="w-16 h-16 bg-blue-900/80 border border-blue-700 rounded-2xl flex items-center justify-center text-4xl shadow-lg group-hover:border-blue-400 group-hover:shadow-blue-500/50">💼</div>
|
||||
<span class="text-xs font-bold mt-2 text-white drop-shadow-md text-center leading-tight">社招简历包<br><span class="text-emerald-400">$1000</span></span>
|
||||
</button>
|
||||
|
||||
<!-- 云服务包 -->
|
||||
<button class="w-24 flex flex-col items-center pointer-events-auto group hover:-translate-y-2 transition-transform" onclick="addFunds(-500); addCard('qa', 150, 340); renderBoard(); updateHUD();">
|
||||
<div class="w-16 h-16 bg-purple-900/80 border border-purple-700 rounded-2xl flex items-center justify-center text-4xl shadow-lg group-hover:border-purple-400 group-hover:shadow-purple-500/50">☁️</div>
|
||||
<span class="text-xs font-bold mt-2 text-white drop-shadow-md text-center leading-tight">云服务包<br><span class="text-emerald-400">$500</span></span>
|
||||
</button>
|
||||
|
||||
<!-- 交付打包区 -->
|
||||
<div id="delivery-zone" class="w-24 mt-auto flex flex-col items-center pointer-events-auto group">
|
||||
<div class="w-20 h-20 bg-emerald-900/40 border-2 border-dashed border-emerald-500/50 rounded-2xl flex items-center justify-center text-5xl shadow-lg group-hover:bg-emerald-800/60 group-hover:border-emerald-400 transition-colors">🚀</div>
|
||||
<span class="text-xs font-bold mt-2 text-emerald-200 drop-shadow-md text-center leading-tight bg-slate-900/80 px-2 py-1 rounded">交付模块</span>
|
||||
</div>
|
||||
|
||||
<!-- 垃圾回收站 -->
|
||||
<div id="trash-zone" class="w-24 flex flex-col items-center pointer-events-auto group mb-4">
|
||||
<div class="w-20 h-20 bg-rose-950/40 border-2 border-dashed border-rose-600/50 rounded-2xl flex items-center justify-center text-5xl shadow-lg group-hover:bg-rose-900/60 group-hover:border-rose-400 transition-colors">🗑️</div>
|
||||
<span class="text-xs font-bold mt-2 text-rose-200 drop-shadow-md text-center leading-tight bg-slate-900/80 px-2 py-1 rounded">回收出售</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 桌面交互区 (放置卡牌) -->
|
||||
<main id="desktop-board" class="w-full h-full pt-16 relative z-20">
|
||||
<svg id="svg-connections" class="absolute inset-0 w-full h-full pointer-events-none z-[15]"></svg>
|
||||
<!-- 卡牌将由 JS 动态渲染 -->
|
||||
</main>
|
||||
|
||||
<!-- 卡池图鉴与配方手册 (Recipe Book Modal) -->
|
||||
<div id="recipe-modal" class="absolute inset-0 z-[100] bg-slate-900/90 backdrop-blur-md hidden flex items-center justify-center">
|
||||
<div class="bg-slate-800 border border-slate-600 rounded-2xl w-full max-w-4xl h-[600px] flex flex-col shadow-2xl overflow-hidden relative">
|
||||
<div class="p-4 border-b border-slate-700 flex justify-between items-center bg-slate-900/50">
|
||||
<h2 class="text-xl font-bold text-white flex items-center gap-2">📖 卡池图鉴与合成说明</h2>
|
||||
<button onclick="document.getElementById('recipe-modal').classList.add('hidden')" class="w-8 h-8 flex items-center justify-center bg-slate-700 hover:bg-rose-600 rounded text-white transition-colors">✕</button>
|
||||
</div>
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<!-- 左侧边栏 -->
|
||||
<div class="w-48 border-r border-slate-700 bg-slate-900/30 p-4 flex flex-col gap-2">
|
||||
<button class="text-left px-4 py-2 bg-blue-900/50 text-blue-200 rounded font-bold border border-blue-700">基础合成</button>
|
||||
<button class="text-left px-4 py-2 bg-slate-800 text-slate-400 hover:text-slate-200 hover:bg-slate-700 rounded font-bold transition-colors">职场摸鱼 (未解锁)</button>
|
||||
<button class="text-left px-4 py-2 bg-slate-800 text-slate-400 hover:text-slate-200 hover:bg-slate-700 rounded font-bold transition-colors">系统危机 (查看)</button>
|
||||
</div>
|
||||
<!-- 右侧内容区 -->
|
||||
<div class="flex-1 p-6 overflow-y-auto bg-[#1e293b] flex flex-col gap-4" id="recipe-list-container">
|
||||
<!-- JS 动态渲染配方 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
28
views/screen-gameover.html
Normal file
28
views/screen-gameover.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<div id="screen-gameover" class="screen absolute inset-0 hidden z-[100] bg-blue-900 flex flex-col items-start justify-center p-20 font-mono text-blue-100">
|
||||
<!-- 模仿 BSOD 蓝屏风格 -->
|
||||
<h1 class="text-8xl font-black bg-blue-100 text-blue-900 px-4 py-2 inline-block mb-10">SYS_HALTED</h1>
|
||||
<p class="text-2xl mb-8">项目资金链断裂 / 公司已启动紧急重组预案。</p>
|
||||
|
||||
<div class="mb-10 max-w-3xl">
|
||||
<p class="mb-4">系统检测到一个不可恢复的商业错误:</p>
|
||||
<p class="text-lg bg-blue-950 p-4 border border-blue-400/30 font-bold" id="gameover-reason">
|
||||
*** 致命异常 0x0000DEAD: 因为你无视了“内存泄漏灾难”,导致服务器物理爆炸,公司面临巨额索赔,已申请破产清算。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 mb-12 text-sm text-blue-300">
|
||||
<p>项目遗产检查转储...</p>
|
||||
<p>> 存活 Sprint 数量: <span class="text-white font-bold" id="gameover-sprints">1</span></p>
|
||||
<p>> 历史最高资金: <span class="text-emerald-300 font-bold" id="gameover-maxfunds">$ 15,000</span></p>
|
||||
<p>> 最常使用的实体: <span class="text-white font-bold">👨💻 开发人员</span></p>
|
||||
</div>
|
||||
|
||||
<p class="text-sm mb-8 animate-pulse text-white">请按以下按钮重启你的职业生涯...</p>
|
||||
|
||||
<div class="flex gap-6">
|
||||
<button class="border-2 border-white px-6 py-2 hover:bg-white hover:text-blue-900 font-bold uppercase transition-colors" onclick="location.reload()">重新开始项目 (F5)</button>
|
||||
<button class="border-2 border-blue-400 text-blue-300 px-6 py-2 hover:bg-blue-400 hover:text-blue-900 font-bold uppercase transition-colors" onclick="location.reload()">返回桌面</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 引入数据和逻辑 -->
|
||||
121
views/screen-start.html
Normal file
121
views/screen-start.html
Normal file
@@ -0,0 +1,121 @@
|
||||
<div id="screen-start" class="screen active absolute inset-0 bg-[#0f172a] flex flex-col items-center justify-center z-50 overflow-auto">
|
||||
<div class="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGNpcmNsZSBjeD0iMiIgY3k9IjIiIHI9IjEiIGZpbGw9IiMzMzQxNTUiLz48L3N2Zz4=')] opacity-20 pointer-events-none"></div>
|
||||
|
||||
<div class="text-center mb-12 relative z-10">
|
||||
<h1 class="text-6xl font-black text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-emerald-400 tracking-tighter drop-shadow-2xl mb-4">
|
||||
Project Vibe: <span class="text-white">Ship It!</span>
|
||||
</h1>
|
||||
<p class="text-xl text-slate-400 font-mono tracking-widest">>> 员工入职登记系统_ v1.0.4</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-6 relative z-10 w-full max-w-7xl px-4 lg:px-8 justify-center items-stretch min-h-[400px]">
|
||||
<!-- 职业 1: 开发 -->
|
||||
<div class="class-card group w-full md:w-[30%] lg:w-[30%] bg-slate-800/80 border border-slate-600 rounded-2xl p-5 cursor-pointer hover:bg-blue-900/40 hover:border-blue-500 transition-all flex flex-col relative overflow-hidden" onclick="selectClass('developer')">
|
||||
<div class="absolute -right-10 -top-10 text-9xl opacity-10 group-hover:text-blue-500 group-hover:scale-110 transition-all">👨💻</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-1 flex items-center gap-2">👨💻 开发人员</h2>
|
||||
<p class="text-xs text-slate-400 mb-4 italic">"一行代码,十个 Bug"</p>
|
||||
<div class="space-y-3 flex-1">
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block">资金</span>
|
||||
<span class="text-emerald-400 font-mono font-bold text-base">$ 10,000</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50 flex-1">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">初始牌组</span>
|
||||
<div class="flex gap-1 flex-wrap mt-1">
|
||||
<span class="text-[10px] bg-blue-900/50 text-blue-200 px-1.5 py-0.5 rounded border border-blue-800">开发人员</span>
|
||||
<span class="text-[10px] bg-emerald-900/50 text-emerald-200 px-1.5 py-0.5 rounded border border-emerald-800">专注力 x2</span>
|
||||
<span class="text-[10px] bg-amber-900/50 text-amber-200 px-1.5 py-0.5 rounded border border-amber-800">冰美式</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-center"><button class="w-full bg-blue-600 hover:bg-blue-500 text-white font-bold py-1.5 px-4 rounded-full text-sm shadow-[0_0_15px_rgba(37,99,235,0.5)]">接受 Offer</button></div>
|
||||
</div>
|
||||
|
||||
<!-- 职业 2: 产品 -->
|
||||
<div class="class-card group w-full md:w-[30%] lg:w-[30%] bg-slate-800/80 border border-slate-600 rounded-2xl p-5 cursor-pointer hover:bg-amber-900/40 hover:border-amber-500 transition-all flex flex-col relative overflow-hidden" onclick="selectClass('pm')">
|
||||
<div class="absolute -right-10 -top-10 text-9xl opacity-10 group-hover:text-amber-500 group-hover:scale-110 transition-all">📊</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-1 flex items-center gap-2">📊 产品经理</h2>
|
||||
<p class="text-xs text-slate-400 mb-4 italic">"这个需求很简单,怎么实现我不管"</p>
|
||||
<div class="space-y-3 flex-1">
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block">资金</span>
|
||||
<span class="text-emerald-400 font-mono font-bold text-base">$ 25,000</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50 flex-1">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">初始牌组</span>
|
||||
<div class="flex gap-1 flex-wrap mt-1">
|
||||
<span class="text-[10px] bg-amber-900/50 text-amber-200 px-1.5 py-0.5 rounded border border-amber-800">产品经理</span>
|
||||
<span class="text-[10px] bg-slate-700/50 text-slate-300 px-1.5 py-0.5 rounded border border-slate-600">白板</span>
|
||||
<span class="text-[10px] bg-rose-900/50 text-rose-200 px-1.5 py-0.5 rounded border border-rose-800">无意义会议</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-center"><button class="w-full bg-amber-600 hover:bg-amber-500 text-white font-bold py-1.5 px-4 rounded-full text-sm shadow-[0_0_15px_rgba(217,119,6,0.5)]">拉个群聊</button></div>
|
||||
</div>
|
||||
|
||||
<!-- 职业 3: 测试 -->
|
||||
<div class="class-card group w-full md:w-[30%] lg:w-[30%] bg-slate-800/80 border border-slate-600 rounded-2xl p-5 cursor-pointer hover:bg-emerald-900/40 hover:border-emerald-500 transition-all flex flex-col relative overflow-hidden" onclick="selectClass('qa')">
|
||||
<div class="absolute -right-10 -top-10 text-9xl opacity-10 group-hover:text-emerald-500 group-hover:scale-110 transition-all">🕵️</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-1 flex items-center gap-2">🕵️ 测试(QA)</h2>
|
||||
<p class="text-xs text-slate-400 mb-4 italic">"又重现不了了?这绝不是我的问题"</p>
|
||||
<div class="space-y-3 flex-1">
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block">资金</span>
|
||||
<span class="text-emerald-400 font-mono font-bold text-base">$ 12,000</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50 flex-1">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">初始牌组</span>
|
||||
<div class="flex gap-1 flex-wrap mt-1">
|
||||
<span class="text-[10px] bg-emerald-900/50 text-emerald-200 px-1.5 py-0.5 rounded border border-emerald-800">测试工程师</span>
|
||||
<span class="text-[10px] bg-emerald-900/50 text-emerald-200 px-1.5 py-0.5 rounded border border-emerald-800">单元测试</span>
|
||||
<span class="text-[10px] bg-rose-900/50 text-rose-200 px-1.5 py-0.5 rounded border border-rose-800">系统 Bug</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-center"><button class="w-full bg-emerald-600 hover:bg-emerald-500 text-white font-bold py-1.5 px-4 rounded-full text-sm shadow-[0_0_15px_rgba(16,185,129,0.5)]">提 Bug 单</button></div>
|
||||
</div>
|
||||
|
||||
<!-- 职业 4: 运维 -->
|
||||
<div class="class-card group w-full md:w-[30%] lg:w-[30%] bg-slate-800/80 border border-slate-600 rounded-2xl p-5 cursor-pointer hover:bg-purple-900/40 hover:border-purple-500 transition-all flex flex-col relative overflow-hidden" onclick="selectClass('ops')">
|
||||
<div class="absolute -right-10 -top-10 text-9xl opacity-10 group-hover:text-purple-500 group-hover:scale-110 transition-all">🛠️</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-1 flex items-center gap-2">🛠️ 运维(Ops)</h2>
|
||||
<p class="text-xs text-slate-400 mb-4 italic">"删库跑路?拔网线最快"</p>
|
||||
<div class="space-y-3 flex-1">
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block">资金</span>
|
||||
<span class="text-emerald-400 font-mono font-bold text-base">$ 20,000</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50 flex-1">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">初始牌组</span>
|
||||
<div class="flex gap-1 flex-wrap mt-1">
|
||||
<span class="text-[10px] bg-purple-900/50 text-purple-200 px-1.5 py-0.5 rounded border border-purple-800">运维工程师</span>
|
||||
<span class="text-[10px] bg-purple-900/50 text-purple-200 px-1.5 py-0.5 rounded border border-purple-800">云服务器</span>
|
||||
<span class="text-[10px] bg-purple-900/50 text-purple-200 px-1.5 py-0.5 rounded border border-purple-800">安全补丁</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-center"><button class="w-full bg-purple-600 hover:bg-purple-500 text-white font-bold py-1.5 px-4 rounded-full text-sm shadow-[0_0_15px_rgba(168,85,247,0.5)]">获取 root</button></div>
|
||||
</div>
|
||||
|
||||
<!-- 职业 5: 路人 -->
|
||||
<div class="class-card group w-full md:w-[30%] lg:w-[30%] bg-slate-800/80 border border-slate-600 rounded-2xl p-5 cursor-pointer hover:bg-slate-700/40 hover:border-slate-400 transition-all flex flex-col relative overflow-hidden" onclick="selectClass('bystander')">
|
||||
<div class="absolute -right-10 -top-10 text-9xl opacity-10 group-hover:text-slate-400 group-hover:scale-110 transition-all">👶</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-1 flex items-center gap-2">👶 实习生(路人)</h2>
|
||||
<p class="text-xs text-slate-400 mb-4 italic">"大佬,我电脑蓝屏了..."</p>
|
||||
<div class="space-y-3 flex-1">
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block">资金</span>
|
||||
<span class="text-emerald-400 font-mono font-bold text-base">$ 2,000 (真穷)</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 p-2 rounded-lg border border-slate-700/50 flex-1">
|
||||
<span class="text-[10px] text-slate-500 uppercase tracking-wider block mb-1">初始牌组</span>
|
||||
<div class="flex gap-1 flex-wrap mt-1">
|
||||
<span class="text-[10px] bg-slate-700 text-slate-300 px-1.5 py-0.5 rounded border border-slate-600">实习生</span>
|
||||
<span class="text-[10px] bg-rose-900/50 text-rose-200 px-1.5 py-0.5 rounded border border-rose-800">技术债</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-center"><button class="w-full bg-slate-600 hover:bg-slate-500 text-white font-bold py-1.5 px-4 rounded-full text-sm shadow-[0_0_15px_rgba(71,85,105,0.5)]">找坑位</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
51
views/screen-win.html
Normal file
51
views/screen-win.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<div id="screen-win" class="screen absolute inset-0 hidden z-[100] bg-slate-900/90 backdrop-blur-xl flex flex-col items-center justify-center">
|
||||
<div class="absolute inset-0 pointer-events-none overflow-hidden" id="confetti-container"></div>
|
||||
|
||||
<h1 class="text-5xl font-black text-white mb-2 drop-shadow-lg">Sprint 成功结算!🎉</h1>
|
||||
<p class="text-slate-400 mb-8 text-lg">大家辛苦了,这个迭代我们没有挂掉服务器!</p>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<div class="bg-slate-800/80 border border-slate-600 rounded-xl p-6 mb-8 w-full max-w-2xl flex justify-around shadow-2xl">
|
||||
<div class="text-center">
|
||||
<div class="text-xs text-slate-400 uppercase tracking-widest mb-1">剩余时间奖励</div>
|
||||
<div class="text-2xl font-mono font-bold text-emerald-400">+$ 4,500</div>
|
||||
</div>
|
||||
<div class="w-px bg-slate-700"></div>
|
||||
<div class="text-center">
|
||||
<div class="text-xs text-slate-400 uppercase tracking-widest mb-1">解决 Bug</div>
|
||||
<div class="text-2xl font-mono font-bold text-blue-400">12 个</div>
|
||||
</div>
|
||||
<div class="w-px bg-slate-700"></div>
|
||||
<div class="text-center">
|
||||
<div class="text-xs text-slate-400 uppercase tracking-widest mb-1">结余总资金</div>
|
||||
<div class="text-2xl font-mono font-bold text-yellow-400" id="win-total-funds">$ 25,000</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-bold text-white mb-6">Roguelike 奖励:请选择一项带入下个 Sprint</h2>
|
||||
|
||||
<!-- 3选1 -->
|
||||
<div class="flex gap-6 w-full max-w-4xl px-4 justify-center">
|
||||
<!-- 选项1:永久实体卡 -->
|
||||
<div class="reward-card group flex-1 bg-gradient-to-br from-blue-900/50 to-slate-900 border-2 border-slate-600 rounded-xl p-6 cursor-pointer hover:border-blue-400 hover:-translate-y-2 transition-all text-center relative overflow-hidden" onclick="selectReward(1)">
|
||||
<div class="text-6xl mb-4 drop-shadow-lg group-hover:scale-110 transition-transform">🤖</div>
|
||||
<div class="text-xs font-bold bg-blue-900/80 text-blue-200 px-2 py-1 rounded inline-block mb-2 uppercase tracking-wide">永久建筑</div>
|
||||
<h3 class="text-xl font-bold text-white mb-2">自动化测试脚本</h3>
|
||||
<p class="text-xs text-slate-300 leading-relaxed">放入桌面上长期生效。当你把 Bug 拖给它时,可以瞬间将其秒杀,无需读条。</p>
|
||||
</div>
|
||||
<!-- 选项2:遗物 -->
|
||||
<div class="reward-card group flex-1 bg-gradient-to-br from-purple-900/50 to-slate-900 border-2 border-slate-600 rounded-xl p-6 cursor-pointer hover:border-purple-400 hover:-translate-y-2 transition-all text-center relative overflow-hidden" onclick="selectReward(2)">
|
||||
<div class="text-6xl mb-4 drop-shadow-lg group-hover:scale-110 transition-transform">🏆</div>
|
||||
<div class="text-xs font-bold bg-purple-900/80 text-purple-200 px-2 py-1 rounded inline-block mb-2 uppercase tracking-wide">全局遗物 (Relic)</div>
|
||||
<h3 class="text-xl font-bold text-white mb-2">敏捷教练的鞭策</h3>
|
||||
<p class="text-xs text-slate-300 leading-relaxed">所有人合成速度加快 20%。但如果 Sprint 结束时没有产出目标,扣除 50% 奖金。</p>
|
||||
</div>
|
||||
<!-- 选项3:清理 -->
|
||||
<div class="reward-card group flex-1 bg-gradient-to-br from-red-900/50 to-slate-900 border-2 border-slate-600 rounded-xl p-6 cursor-pointer hover:border-red-400 hover:-translate-y-2 transition-all text-center relative overflow-hidden" onclick="selectReward(3)">
|
||||
<div class="text-6xl mb-4 drop-shadow-lg group-hover:scale-110 transition-transform">🧹</div>
|
||||
<div class="text-xs font-bold bg-red-900/80 text-red-200 px-2 py-1 rounded inline-block mb-2 uppercase tracking-wide">一次性服务</div>
|
||||
<h3 class="text-xl font-bold text-white mb-2">保洁阿姨的凝视</h3>
|
||||
<p class="text-xs text-slate-300 leading-relaxed">立即销毁桌面上所有的“技术债”和“无意义的会议”卡牌。环境瞬间清爽!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user