@playcraft/cli 0.0.53 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/README.md +21 -0
  2. package/REMIX.md +103 -19
  3. package/dist/atom-tree/fusion-hints.js +8 -3
  4. package/dist/atom-tree/fusion-intent.js +62 -0
  5. package/dist/atom-tree/selection.js +3 -1
  6. package/dist/commands/create.js +439 -159
  7. package/dist/commands/image.js +79 -69
  8. package/dist/commands/levels.js +29 -0
  9. package/dist/commands/patterns.js +75 -0
  10. package/dist/commands/remix.js +47 -0
  11. package/dist/commands/skills.js +48 -17
  12. package/dist/index.js +13 -6
  13. package/dist/remix/clone-source.js +208 -0
  14. package/dist/remix/clone.js +255 -0
  15. package/dist/scaffold/templates.js +35 -0
  16. package/dist/utils/agent-api-client.js +10 -3
  17. package/dist/utils/remix-image-attachments.js +4 -32
  18. package/dist/utils/sandbox-path.js +20 -0
  19. package/package.json +3 -3
  20. package/project-template/.claude/agents/creative-director.md +114 -0
  21. package/project-template/.claude/agents/developer.md +5 -2
  22. package/project-template/.claude/agents/game-designer.md +41 -20
  23. package/project-template/.claude/agents/refs/README.md +8 -0
  24. package/project-template/.claude/agents/refs/creative-director-guide.md +35 -0
  25. package/project-template/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  26. package/project-template/.claude/agents/refs/creative-discovery-guide.md +76 -0
  27. package/project-template/.claude/agents/refs/creative-north-star-examples.md +82 -0
  28. package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
  29. package/project-template/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  30. package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +4 -4
  31. package/project-template/.claude/agents/reviewer.md +11 -0
  32. package/project-template/.claude/hooks/README.md +12 -12
  33. package/project-template/.claude/hooks/block-developer-preflight.mjs +49 -13
  34. package/project-template/.claude/hooks/block-ring1-tasklist.mjs +1 -1
  35. package/project-template/.claude/hooks/user-prompt.mjs +3 -13
  36. package/project-template/.claude/hooks/validate-workflow-stop.mjs +209 -12
  37. package/project-template/.claude/settings.json +2 -2
  38. package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  39. package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -1
  40. package/project-template/.claude/snapshot.mjs +0 -1
  41. package/project-template/.codebuddy/settings.json +1 -1
  42. package/project-template/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  43. package/project-template/CLAUDE.md +9 -9
  44. package/project-template/docs/project-state.md +1 -1
  45. package/project-template/docs/team/agent-conduct.md +18 -18
  46. package/project-template/docs/team/collaboration.md +19 -7
  47. package/project-template/docs/team/workflow-changelog.md +15 -0
  48. package/project-template/fusion-patterns/catalog.json +77 -0
  49. package/project-template/package.json +2 -0
  50. package/project-template/skills/_shared/scripts/create-playable.mjs +31 -11
  51. package/project-template/skills/_shared/scripts/dev-start.mjs +3 -3
  52. package/project-template/skills/_shared/scripts/disclosure-config.mjs +65 -39
  53. package/project-template/skills/_shared/scripts/harness-smoke.mjs +3 -1
  54. package/project-template/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  55. package/project-template/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  56. package/project-template/skills/_shared/scripts/slice-set.mjs +29 -2
  57. package/project-template/skills/_shared/scripts/state-get.mjs +124 -7
  58. package/project-template/skills/_shared/scripts/state-store.mjs +25 -2
  59. package/project-template/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  60. package/project-template/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  61. package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  62. package/project-template/skills/playable-record/scripts/record-playable.mjs +2 -1
  63. package/project-template/skills/playable-record/scripts/verify-contract.mjs +4 -4
  64. package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  65. package/project-template/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  66. package/project-template/skills/playable-record/scripts/verify-slice.mjs +2 -2
  67. package/project-template/templates/design-brief.template.md +34 -0
  68. package/project-template/templates/design-methodology.md +1 -2
  69. package/project-template/templates/design.template.md +65 -0
  70. package/project-template/templates/developer-log.template.md +12 -1
  71. package/project-template/templates/five-axis-framework.md +12 -0
  72. package/project-template/templates/game-designer-creative-process.template.md +99 -0
  73. package/project-template/templates/level-sketch.template.json +56 -0
  74. package/project-template/templates/north-star.template.md +43 -0
  75. package/project-template/templates/project-state.template.md +1 -1
  76. package/project-template/vite.config.ts +4 -5
  77. package/project-templates/phaser/.claude/agents/creative-director.md +114 -0
  78. package/project-templates/phaser/.claude/agents/game-designer.md +41 -20
  79. package/project-templates/phaser/.claude/agents/refs/creative-director-guide.md +35 -0
  80. package/project-templates/phaser/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  81. package/project-templates/phaser/.claude/agents/refs/creative-discovery-guide.md +76 -0
  82. package/project-templates/phaser/.claude/agents/refs/creative-north-star-examples.md +82 -0
  83. package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +1 -1
  84. package/project-templates/phaser/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  85. package/project-templates/phaser/.claude/agents/reviewer.md +11 -0
  86. package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  87. package/project-templates/phaser/.claude/hooks/user-prompt.mjs +17 -18
  88. package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +209 -12
  89. package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  90. package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  91. package/project-templates/phaser/CLAUDE.md +88 -54
  92. package/project-templates/phaser/docs/DESIGN.md +25 -0
  93. package/project-templates/phaser/docs/project-state.md +1 -1
  94. package/project-templates/phaser/docs/team/collaboration.md +19 -7
  95. package/project-templates/phaser/docs/team/workflow-changelog.md +14 -0
  96. package/project-templates/phaser/fusion-patterns/catalog.json +77 -0
  97. package/project-templates/phaser/package.json +3 -1
  98. package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +3 -3
  99. package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +65 -39
  100. package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +3 -1
  101. package/project-templates/phaser/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  102. package/project-templates/phaser/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  103. package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +124 -7
  104. package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +25 -2
  105. package/project-templates/phaser/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  106. package/project-templates/phaser/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  107. package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  108. package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +2 -1
  109. package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +4 -4
  110. package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  111. package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  112. package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +2 -2
  113. package/project-templates/phaser/templates/design-brief.template.md +42 -11
  114. package/project-templates/phaser/templates/design-methodology.md +1 -2
  115. package/project-templates/phaser/templates/design.template.md +77 -0
  116. package/project-templates/phaser/templates/five-axis-framework.md +50 -1
  117. package/project-templates/phaser/templates/game-designer-creative-process.template.md +99 -0
  118. package/project-templates/phaser/templates/level-sketch.template.json +56 -0
  119. package/project-templates/phaser/templates/north-star.template.md +43 -0
  120. package/project-templates/phaser/templates/project-state.template.md +1 -1
  121. package/project-templates/phaser/vite.config.ts +4 -5
  122. package/project-templates/playable-template-phaser/.playcraftignore +10 -0
  123. package/project-templates/playable-template-phaser/CLAUDE.md +178 -0
  124. package/project-templates/playable-template-phaser/PLAYCRAFT-MIGRATION.md +328 -0
  125. package/project-templates/playable-template-phaser/README.md +381 -0
  126. package/project-templates/playable-template-phaser/assets/brain.png +0 -0
  127. package/project-templates/playable-template-phaser/assets/button.png +0 -0
  128. package/project-templates/playable-template-phaser/assets/interact.png +0 -0
  129. package/project-templates/playable-template-phaser/assets/logo.webp +0 -0
  130. package/project-templates/playable-template-phaser/assets/warn_brain.png +0 -0
  131. package/project-templates/playable-template-phaser/builds.config.js +12 -0
  132. package/project-templates/playable-template-phaser/globals.d.ts +2 -0
  133. package/project-templates/playable-template-phaser/index.html +12 -0
  134. package/project-templates/playable-template-phaser/package.json +45 -0
  135. package/project-templates/playable-template-phaser/scripts/base64-to-mp3.js +340 -0
  136. package/project-templates/playable-template-phaser/scripts/convert-to-webp.js +370 -0
  137. package/project-templates/playable-template-phaser/src/Game.ts +154 -0
  138. package/project-templates/playable-template-phaser/src/config/LoadingConfig.ts +40 -0
  139. package/project-templates/playable-template-phaser/src/config/ThemeConfig.ts +81 -0
  140. package/project-templates/playable-template-phaser/src/index.css +18 -0
  141. package/project-templates/playable-template-phaser/src/index.html +9 -0
  142. package/project-templates/playable-template-phaser/src/index.ts +30 -0
  143. package/project-templates/playable-template-phaser/src/json5.d.ts +4 -0
  144. package/project-templates/playable-template-phaser/src/managers/AudioManager.ts +559 -0
  145. package/project-templates/playable-template-phaser/src/scenes/MainScene.ts +301 -0
  146. package/project-templates/playable-template-phaser/src/scenes/PreloadScene.ts +256 -0
  147. package/project-templates/playable-template-phaser/src/theme/README.md +194 -0
  148. package/project-templates/playable-template-phaser/src/theme/blue-theme/assets/.gitkeep +24 -0
  149. package/project-templates/playable-template-phaser/src/theme/blue-theme/index.ts +27 -0
  150. package/project-templates/playable-template-phaser/src/theme/blue-theme/theme.data.json5 +19 -0
  151. package/project-templates/playable-template-phaser/src/theme/green-theme/assets/.gitkeep +3 -0
  152. package/project-templates/playable-template-phaser/src/theme/green-theme/index.ts +20 -0
  153. package/project-templates/playable-template-phaser/src/theme/green-theme/theme.data.json5 +19 -0
  154. package/project-templates/playable-template-phaser/src/theme/index.ts +1 -0
  155. package/project-templates/playable-template-phaser/src/theme/red-theme/assets/.gitkeep +3 -0
  156. package/project-templates/playable-template-phaser/src/theme/red-theme/index.ts +20 -0
  157. package/project-templates/playable-template-phaser/src/theme/red-theme/theme.data.json5 +19 -0
  158. package/project-templates/playable-template-phaser/src/theme/theme.metadata.md +60 -0
  159. package/project-templates/playable-template-phaser/src/theme/theme.schema.json5 +149 -0
  160. package/project-templates/playable-template-phaser/src/ui/BrainAgeUI.ts +681 -0
  161. package/project-templates/playable-template-phaser/src/ui/CountdownUI.ts +565 -0
  162. package/project-templates/playable-template-phaser/src/ui/PlayButton.ts +51 -0
  163. package/project-templates/playable-template-phaser/src/ui/ScoreUI.ts +207 -0
  164. package/project-templates/playable-template-phaser/src/ui/SuccessUI.ts +43 -0
  165. package/project-templates/playable-template-phaser/src/ui/index.ts +15 -0
  166. package/project-templates/playable-template-phaser/tsconfig.json +26 -0
  167. package/project-templates/playable-template-phaser/vite.config.ts +64 -0
  168. package/project-templates/playable-template-phaser//346/226/260/346/211/213/345/274/200/345/217/221/346/214/207/345/215/227.md +209 -0
  169. package/project-templates/playable-template-three/README.md +795 -0
  170. package/project-templates/playable-template-three/assets/Stork.glb +0 -0
  171. package/project-templates/playable-template-three/assets/button.png +0 -0
  172. package/project-templates/playable-template-three/assets/logo.webp +0 -0
  173. package/project-templates/playable-template-three/build.json +7 -0
  174. package/project-templates/playable-template-three/builds.config.example.js +151 -0
  175. package/project-templates/playable-template-three/builds.config.js +158 -0
  176. package/project-templates/playable-template-three/package.json +36 -0
  177. package/project-templates/playable-template-three/scripts/convert-to-webp.js +370 -0
  178. package/project-templates/playable-template-three/scripts/theme-builds.js +514 -0
  179. package/project-templates/playable-template-three/src/Game.ts +215 -0
  180. package/project-templates/playable-template-three/src/config/LoadingConfig.ts +27 -0
  181. package/project-templates/playable-template-three/src/config/ThemeConfig.ts +36 -0
  182. package/project-templates/playable-template-three/src/global.d.ts +108 -0
  183. package/project-templates/playable-template-three/src/index.css +18 -0
  184. package/project-templates/playable-template-three/src/index.html +9 -0
  185. package/project-templates/playable-template-three/src/index.ts +20 -0
  186. package/project-templates/playable-template-three/src/managers/AudioManager.ts +538 -0
  187. package/project-templates/playable-template-three/src/scenes/PreloadScene.ts +305 -0
  188. package/project-templates/playable-template-three/src/theme/README.md +225 -0
  189. package/project-templates/playable-template-three/src/theme/blue-theme/assets/.gitkeep +28 -0
  190. package/project-templates/playable-template-three/src/theme/blue-theme/index.ts +45 -0
  191. package/project-templates/playable-template-three/src/theme/current.ts +12 -0
  192. package/project-templates/playable-template-three/src/theme/green-theme/assets/.gitkeep +3 -0
  193. package/project-templates/playable-template-three/src/theme/green-theme/index.ts +34 -0
  194. package/project-templates/playable-template-three/src/theme/red-theme/assets/.gitkeep +3 -0
  195. package/project-templates/playable-template-three/src/theme/red-theme/index.ts +34 -0
  196. package/project-templates/playable-template-three/tsconfig.json +24 -0
  197. package/project-templates/threejs/.claude/agents/creative-director.md +114 -0
  198. package/project-templates/threejs/.claude/agents/game-designer.md +41 -20
  199. package/project-templates/threejs/.claude/agents/refs/creative-director-guide.md +35 -0
  200. package/project-templates/threejs/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
  201. package/project-templates/threejs/.claude/agents/refs/creative-discovery-guide.md +76 -0
  202. package/project-templates/threejs/.claude/agents/refs/creative-north-star-examples.md +82 -0
  203. package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +1 -1
  204. package/project-templates/threejs/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
  205. package/project-templates/threejs/.claude/agents/reviewer.md +11 -0
  206. package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +2 -2
  207. package/project-templates/threejs/.claude/hooks/user-prompt.mjs +17 -18
  208. package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +209 -12
  209. package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
  210. package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +1 -1
  211. package/project-templates/threejs/CLAUDE.md +88 -54
  212. package/project-templates/threejs/docs/DESIGN.md +25 -0
  213. package/project-templates/threejs/docs/project-state.md +1 -1
  214. package/project-templates/threejs/docs/team/collaboration.md +19 -7
  215. package/project-templates/threejs/docs/team/workflow-changelog.md +14 -0
  216. package/project-templates/threejs/fusion-patterns/catalog.json +77 -0
  217. package/project-templates/threejs/package.json +3 -1
  218. package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +3 -3
  219. package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +65 -39
  220. package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +3 -1
  221. package/project-templates/threejs/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
  222. package/project-templates/threejs/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
  223. package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +124 -7
  224. package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +25 -2
  225. package/project-templates/threejs/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
  226. package/project-templates/threejs/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
  227. package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
  228. package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +2 -1
  229. package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +4 -4
  230. package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
  231. package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +37 -2
  232. package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +2 -2
  233. package/project-templates/threejs/templates/design-brief.template.md +42 -11
  234. package/project-templates/threejs/templates/design-methodology.md +1 -2
  235. package/project-templates/threejs/templates/design.template.md +77 -0
  236. package/project-templates/threejs/templates/five-axis-framework.md +50 -1
  237. package/project-templates/threejs/templates/game-designer-creative-process.template.md +99 -0
  238. package/project-templates/threejs/templates/level-sketch.template.json +56 -0
  239. package/project-templates/threejs/templates/north-star.template.md +43 -0
  240. package/project-templates/threejs/templates/project-state.template.md +1 -1
  241. package/project-templates/threejs/vite.config.ts +4 -5
  242. package/project-template/docs/DESIGN.md +0 -65
@@ -0,0 +1,681 @@
1
+ /**
2
+ * 脑龄UI配置接口
3
+ */
4
+ export interface BrainAgeUIConfig {
5
+ scale?: number;
6
+ width?: number;
7
+ height?: number;
8
+ radius?: number;
9
+ padding?: number;
10
+ iconSize?: number;
11
+ iconOffset?: number;
12
+ labelFontSize?: string;
13
+ ageFontSize?: string;
14
+ labelText?: string;
15
+ colors?: {
16
+ bg?: string;
17
+ borderNormal?: string;
18
+ text?: string;
19
+ ageNormal?: string;
20
+ };
21
+ borderWidth?: number;
22
+ }
23
+
24
+ /**
25
+ * 脑龄游戏逻辑配置
26
+ */
27
+ export interface BrainAgeGameConfig {
28
+ /** 初始脑龄 */
29
+ initial?: number;
30
+ /** 最大脑龄(达到则 Game Over) */
31
+ max?: number;
32
+ /** 最小脑龄 */
33
+ min?: number;
34
+ /** 每秒增加的脑龄 */
35
+ incrementPerSecond?: number;
36
+ /** 每次匹配减少的脑龄 */
37
+ decrementPerMatch?: number;
38
+ /** 警告阈值脑龄 */
39
+ warningAge?: number;
40
+ /** 正常状态图标 URL */
41
+ iconUrl?: string;
42
+ /** 警告状态图标 URL */
43
+ warnIconUrl?: string;
44
+ /** 是否启用危险闪烁边框 */
45
+ enableDangerWarning?: boolean;
46
+ /** 脑龄达到上限回调 */
47
+ onGameOver?: () => void;
48
+ }
49
+
50
+ /**
51
+ * 完整配置类型(所有属性都必填,包括嵌套对象)
52
+ */
53
+ type FullBrainAgeUIConfig = Required<Omit<BrainAgeUIConfig, "colors">> & {
54
+ colors: Required<NonNullable<BrainAgeUIConfig["colors"]>>;
55
+ };
56
+
57
+ type FullBrainAgeGameConfig = Required<Omit<BrainAgeGameConfig, 'onGameOver'>>;
58
+
59
+ /**
60
+ * 默认 UI 配置
61
+ */
62
+ const DEFAULT_CONFIG: FullBrainAgeUIConfig = {
63
+ scale: 1,
64
+ width: 400,
65
+ height: 100,
66
+ radius: 50,
67
+ padding: 15,
68
+ iconSize: 75,
69
+ iconOffset: 35,
70
+ labelFontSize: "36px",
71
+ ageFontSize: "72px",
72
+ labelText: "Brain Age:",
73
+ colors: {
74
+ bg: "#ffffff",
75
+ borderNormal: "#dddddd",
76
+ text: "#000000",
77
+ ageNormal: "#2b5c9e",
78
+ },
79
+ borderWidth: 0,
80
+ };
81
+
82
+ /**
83
+ * 默认游戏逻辑配置
84
+ */
85
+ const DEFAULT_GAME_CONFIG: FullBrainAgeGameConfig = {
86
+ initial: 60,
87
+ max: 100,
88
+ min: 20,
89
+ incrementPerSecond: 1,
90
+ decrementPerMatch: 5,
91
+ warningAge: 90,
92
+ iconUrl: '',
93
+ warnIconUrl: '',
94
+ enableDangerWarning: true,
95
+ };
96
+
97
+ /**
98
+ * 脑龄UI组件(纯DOM+CSS实现)
99
+ */
100
+ export class BrainAgeUI {
101
+ private container!: HTMLDivElement;
102
+ private background!: HTMLDivElement;
103
+ private icon?: HTMLElement;
104
+ private ageText!: HTMLDivElement;
105
+ private config: FullBrainAgeUIConfig;
106
+
107
+ // --- 内部状态 ---
108
+ private _age: number = 60;
109
+ private gameConfig: FullBrainAgeGameConfig = { ...DEFAULT_GAME_CONFIG };
110
+ private timer?: number;
111
+ private onGameOver: () => void;
112
+
113
+ // --- 危险警告闪烁 ---
114
+ private dangerOverlay?: HTMLDivElement;
115
+ private isDangerMode: boolean = false;
116
+
117
+ /**
118
+ * @param x X 坐标(像素)
119
+ * @param y Y 坐标(像素)
120
+ * @param iconContent 图标内容(图片 URL 优先,或 emoji/文本,留空不显示)
121
+ * @param uiConfig UI 外观配置
122
+ * @param gameConfig 游戏逻辑配置(含 onGameOver)
123
+ */
124
+ constructor(
125
+ x: number = 30,
126
+ y: number = 30,
127
+ iconContent?: string,
128
+ uiConfig?: BrainAgeUIConfig,
129
+ gameConfig?: BrainAgeGameConfig
130
+ ) {
131
+ this.config = this.mergeConfig(uiConfig);
132
+ this.gameConfig = this.mergeGameConfig(gameConfig);
133
+ this.onGameOver = gameConfig?.onGameOver ?? (() => {});
134
+ this._age = this.gameConfig.initial;
135
+ this.create(x, y, iconContent);
136
+
137
+ // 立即显示初始值
138
+ this.update(this._age);
139
+
140
+ // 初始化危险闪烁边框(如果启用)
141
+ if (this.gameConfig.enableDangerWarning) {
142
+ this.initDangerWarning();
143
+ }
144
+
145
+ // 自动启动计时
146
+ this.start();
147
+ }
148
+
149
+ // ========================================
150
+ // 启动 / 暂停 / 恢复
151
+ // ========================================
152
+
153
+ /**
154
+ * 启动脑龄计时器(每秒 +incrementPerSecond,达到 max 触发 onGameOver)
155
+ */
156
+ public start(): void {
157
+ // 防止重复启动
158
+ if (this.timer) return;
159
+
160
+ this.timer = window.setInterval(() => {
161
+ this._age += this.gameConfig.incrementPerSecond;
162
+ this.update(this._age);
163
+
164
+ // 达到警告值 → 切换图标 + 启动闪烁
165
+ if (this._age >= this.gameConfig.warningAge) {
166
+ this.setIcon(this.gameConfig.warnIconUrl || '⚠️');
167
+ if (!this.isDangerMode) {
168
+ this.isDangerMode = true;
169
+ this.startDangerFlash();
170
+ }
171
+ }
172
+
173
+ // 达到最大值 → 停止闪烁 + Game Over
174
+ if (this._age >= this.gameConfig.max) {
175
+ this.stopDangerFlash();
176
+ if (this.timer) {
177
+ clearInterval(this.timer);
178
+ this.timer = undefined;
179
+ }
180
+ this.onGameOver();
181
+ }
182
+ }, 1000);
183
+ }
184
+
185
+ /**
186
+ * 匹配成功时减少脑龄(内部自管理,自动处理图标切换和闪烁解除)
187
+ * @param customDecrement 自定义减少量(默认使用 gameConfig.decrementPerMatch)
188
+ */
189
+ public onMatchSuccess(customDecrement?: number): void {
190
+ const decrement = customDecrement ?? this.gameConfig.decrementPerMatch;
191
+ this._age = Math.max(this.gameConfig.min, this._age - decrement);
192
+ this.update(this._age);
193
+
194
+ if (this._age < this.gameConfig.warningAge) {
195
+ this.setIcon(this.gameConfig.iconUrl || '🧠');
196
+ this.stopDangerFlash();
197
+ }
198
+ }
199
+
200
+ /**
201
+ * 暂停计时器
202
+ */
203
+ public pause(): void {
204
+ if (this.timer) {
205
+ clearInterval(this.timer);
206
+ this.timer = undefined;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * 恢复计时器
212
+ */
213
+ public resume(): void {
214
+ if (!this.timer) {
215
+ this.start();
216
+ }
217
+ }
218
+
219
+ /**
220
+ * 获取当前脑龄
221
+ */
222
+ public getAge(): number {
223
+ return this._age;
224
+ }
225
+
226
+ /**
227
+ * 外部直接设置脑龄
228
+ */
229
+ public setAge(age: number): void {
230
+ this._age = age;
231
+ this.update(this._age);
232
+
233
+ // 脱离警告区间 → 恢复正常图标 + 停止闪烁
234
+ if (this._age < this.gameConfig.warningAge && this.isDangerMode) {
235
+ this.setIcon(this.gameConfig.iconUrl || '🧠');
236
+ this.stopDangerFlash();
237
+ }
238
+ }
239
+
240
+ // ========================================
241
+ // 内部实现
242
+ // ========================================
243
+
244
+ private mergeGameConfig(userConfig?: BrainAgeGameConfig): FullBrainAgeGameConfig {
245
+ if (!userConfig) return { ...DEFAULT_GAME_CONFIG };
246
+ return {
247
+ initial: userConfig.initial ?? DEFAULT_GAME_CONFIG.initial,
248
+ max: userConfig.max ?? DEFAULT_GAME_CONFIG.max,
249
+ min: userConfig.min ?? DEFAULT_GAME_CONFIG.min,
250
+ incrementPerSecond: userConfig.incrementPerSecond ?? DEFAULT_GAME_CONFIG.incrementPerSecond,
251
+ decrementPerMatch: userConfig.decrementPerMatch ?? DEFAULT_GAME_CONFIG.decrementPerMatch,
252
+ warningAge: userConfig.warningAge ?? DEFAULT_GAME_CONFIG.warningAge,
253
+ iconUrl: userConfig.iconUrl ?? DEFAULT_GAME_CONFIG.iconUrl,
254
+ warnIconUrl: userConfig.warnIconUrl ?? DEFAULT_GAME_CONFIG.warnIconUrl,
255
+ enableDangerWarning: userConfig.enableDangerWarning ?? DEFAULT_GAME_CONFIG.enableDangerWarning,
256
+ };
257
+ }
258
+
259
+ private mergeConfig(userConfig?: BrainAgeUIConfig): FullBrainAgeUIConfig {
260
+ if (!userConfig) {
261
+ return { ...DEFAULT_CONFIG, colors: { ...DEFAULT_CONFIG.colors } };
262
+ }
263
+
264
+ return {
265
+ scale: userConfig.scale ?? DEFAULT_CONFIG.scale,
266
+ width: userConfig.width ?? DEFAULT_CONFIG.width,
267
+ height: userConfig.height ?? DEFAULT_CONFIG.height,
268
+ radius: userConfig.radius ?? DEFAULT_CONFIG.radius,
269
+ padding: userConfig.padding ?? DEFAULT_CONFIG.padding,
270
+ iconSize: userConfig.iconSize ?? DEFAULT_CONFIG.iconSize,
271
+ iconOffset: userConfig.iconOffset ?? DEFAULT_CONFIG.iconOffset,
272
+ labelFontSize: userConfig.labelFontSize ?? DEFAULT_CONFIG.labelFontSize,
273
+ ageFontSize: userConfig.ageFontSize ?? DEFAULT_CONFIG.ageFontSize,
274
+ labelText: userConfig.labelText ?? DEFAULT_CONFIG.labelText,
275
+ colors: {
276
+ bg: userConfig.colors?.bg ?? DEFAULT_CONFIG.colors.bg,
277
+ borderNormal: userConfig.colors?.borderNormal ?? DEFAULT_CONFIG.colors.borderNormal,
278
+ text: userConfig.colors?.text ?? DEFAULT_CONFIG.colors.text,
279
+ ageNormal: userConfig.colors?.ageNormal ?? DEFAULT_CONFIG.colors.ageNormal,
280
+ },
281
+ borderWidth: userConfig.borderWidth ?? DEFAULT_CONFIG.borderWidth,
282
+ };
283
+ }
284
+
285
+ private create(x: number, y: number, iconContent?: string): void {
286
+ const {
287
+ scale: s,
288
+ colors,
289
+ borderWidth: rawBorderWidth,
290
+ labelText,
291
+ } = this.config;
292
+
293
+ const width = this.config.width * s;
294
+ const height = this.config.height * s;
295
+ const radius = this.config.radius * s;
296
+ const padding = this.config.padding * s;
297
+ const iconSize = this.config.iconSize * s;
298
+ const iconOffset = this.config.iconOffset * s;
299
+ const borderWidth = rawBorderWidth * s;
300
+ const labelFontSize = `${parseFloat(this.config.labelFontSize) * s}px`;
301
+ const ageFontSize = `${parseFloat(this.config.ageFontSize) * s}px`;
302
+
303
+ // 创建主容器
304
+ this.container = document.createElement('div');
305
+ this.container.className = 'brainage-ui-container';
306
+
307
+ Object.assign(this.container.style, {
308
+ position: 'fixed',
309
+ left: `${x}px`,
310
+ top: `${y}px`,
311
+ width: `${width}px`,
312
+ height: `${height}px`,
313
+ zIndex: '100',
314
+ transform: 'scale(var(--brainage-ui-scale, 1))',
315
+ transformOrigin: 'top left',
316
+ pointerEvents: 'none',
317
+ userSelect: 'none',
318
+ } as CSSStyleDeclaration);
319
+
320
+ // 创建背景
321
+ this.background = document.createElement('div');
322
+ this.background.className = 'brainage-ui-background';
323
+
324
+ Object.assign(this.background.style, {
325
+ position: 'absolute',
326
+ width: '100%',
327
+ height: '100%',
328
+ backgroundColor: colors.bg,
329
+ border: borderWidth > 0 ? `${borderWidth}px solid ${colors.borderNormal}` : 'none',
330
+ borderRadius: `${radius}px`,
331
+ display: 'flex',
332
+ alignItems: 'center',
333
+ padding: `0 ${padding}px`,
334
+ boxSizing: 'border-box',
335
+ } as CSSStyleDeclaration);
336
+
337
+ // 创建图标(如果提供)
338
+ if (iconContent) {
339
+ // 判断是图片 URL 还是 emoji/文本
340
+ const isImageUrl = iconContent.startsWith('data:image/') || // base64 data URL
341
+ iconContent.startsWith('http') ||
342
+ iconContent.startsWith('/') ||
343
+ iconContent.startsWith('./') ||
344
+ iconContent.includes('.png') ||
345
+ iconContent.includes('.jpg') ||
346
+ iconContent.includes('.webp') ||
347
+ iconContent.includes('.svg');
348
+
349
+ if (isImageUrl) {
350
+ // 使用图片
351
+ const imgElement = document.createElement('img');
352
+ imgElement.className = 'brainage-ui-icon';
353
+ imgElement.src = iconContent;
354
+ imgElement.alt = 'Brain Icon';
355
+
356
+ Object.assign(imgElement.style, {
357
+ width: `${iconSize}px`,
358
+ height: `${iconSize}px`,
359
+ marginRight: `${iconOffset * 0.6}px`, // 调整间距为原值的 0.6 倍
360
+ flexShrink: '0',
361
+ objectFit: 'contain',
362
+ alignSelf: 'center', // 垂直居中对齐
363
+ } as CSSStyleDeclaration);
364
+
365
+ this.icon = imgElement;
366
+ this.background.appendChild(imgElement);
367
+ } else {
368
+ // 使用 emoji/文本
369
+ this.icon = document.createElement('div');
370
+ this.icon.className = 'brainage-ui-icon';
371
+ this.icon.textContent = iconContent;
372
+
373
+ Object.assign(this.icon.style, {
374
+ fontSize: `${iconSize}px`,
375
+ lineHeight: '1', // 改为 1,避免行高冲突
376
+ marginRight: `${iconOffset * 0.6}px`, // 调整间距为原值的 0.6 倍
377
+ flexShrink: '0',
378
+ display: 'flex',
379
+ alignItems: 'center', // 垂直居中对齐
380
+ justifyContent: 'center', // 水平居中对齐
381
+ height: `${iconSize}px`, // 显式设置高度
382
+ } as CSSStyleDeclaration);
383
+
384
+ this.background.appendChild(this.icon);
385
+ }
386
+ }
387
+
388
+ // 创建文本容器
389
+ const textContainer = document.createElement('div');
390
+ textContainer.className = 'brainage-ui-text-container';
391
+ Object.assign(textContainer.style, {
392
+ display: 'flex',
393
+ alignItems: 'center',
394
+ justifyContent: 'space-between',
395
+ flex: '1',
396
+ } as CSSStyleDeclaration);
397
+
398
+ // 创建标签文本
399
+ const labelTextObj = document.createElement('div');
400
+ labelTextObj.className = 'brainage-ui-label';
401
+ labelTextObj.textContent = labelText;
402
+
403
+ Object.assign(labelTextObj.style, {
404
+ fontFamily: 'Arial, sans-serif',
405
+ fontSize: labelFontSize,
406
+ color: colors.text,
407
+ fontWeight: 'bold',
408
+ } as CSSStyleDeclaration);
409
+
410
+ // 创建脑龄文本
411
+ this.ageText = document.createElement('div');
412
+ this.ageText.className = 'brainage-ui-age';
413
+ this.ageText.textContent = this._age.toString();
414
+
415
+ Object.assign(this.ageText.style, {
416
+ fontFamily: 'Arial, sans-serif',
417
+ fontSize: ageFontSize,
418
+ color: colors.ageNormal,
419
+ fontWeight: '700',
420
+ } as CSSStyleDeclaration);
421
+
422
+ textContainer.appendChild(labelTextObj);
423
+ textContainer.appendChild(this.ageText);
424
+ this.background.appendChild(textContainer);
425
+ this.container.appendChild(this.background);
426
+
427
+ document.body.appendChild(this.container);
428
+
429
+ // 响应式处理
430
+ this.setupResponsive();
431
+ }
432
+
433
+ /**
434
+ * 设置响应式缩放
435
+ */
436
+ private setupResponsive(): void {
437
+ const updateScale = () => {
438
+ const currentWidth = window.innerWidth;
439
+ const currentHeight = window.innerHeight;
440
+
441
+ // 判断是否为移动设备
442
+ const isMobile = currentWidth < 768 || (currentWidth < 1024 && currentHeight < 768);
443
+
444
+ let scale: number;
445
+ if (isMobile) {
446
+ // 移动端:基于 375px 基准,最小 0.8 倍,最大 1.2 倍
447
+ const baseWidth = 375;
448
+ scale = Math.max(0.4, Math.min(currentWidth / baseWidth, 0.5));
449
+ } else {
450
+ // PC端:基于 1920px 基准,最小 0.6 倍,最大 1 倍
451
+ const baseWidth = 1920;
452
+ scale = Math.max(0.6, Math.min(currentWidth / baseWidth, 1));
453
+ }
454
+
455
+ document.documentElement.style.setProperty('--brainage-ui-scale', scale.toString());
456
+ };
457
+
458
+ updateScale();
459
+ window.addEventListener('resize', updateScale);
460
+
461
+ // 保存清理函数
462
+ (this.container as any)._cleanupResize = () => {
463
+ window.removeEventListener('resize', updateScale);
464
+ };
465
+ }
466
+
467
+ /**
468
+ * 更新脑龄显示
469
+ */
470
+ public update(age: number): void {
471
+ if (this.ageText) {
472
+ this.ageText.textContent = age.toString();
473
+ }
474
+ }
475
+
476
+ /**
477
+ * 初始化危险警告边框
478
+ */
479
+ public initDangerWarning(): void {
480
+ if (this.dangerOverlay) return; // 避免重复初始化
481
+
482
+ this.dangerOverlay = document.createElement('div');
483
+ this.dangerOverlay.className = 'brainage-danger-overlay';
484
+
485
+ Object.assign(this.dangerOverlay.style, {
486
+ position: 'fixed',
487
+ left: '0',
488
+ top: '0',
489
+ width: '100vw',
490
+ height: '100vh',
491
+ border: '8px solid #ff3333',
492
+ boxSizing: 'border-box',
493
+ pointerEvents: 'none',
494
+ zIndex: '99',
495
+ opacity: '0',
496
+ display: 'none',
497
+ } as CSSStyleDeclaration);
498
+
499
+ document.body.appendChild(this.dangerOverlay);
500
+ }
501
+
502
+ /**
503
+ * 启动危险警告闪烁(使用 CSS 动画)
504
+ */
505
+ private startDangerFlash(): void {
506
+ if (!this.dangerOverlay) return;
507
+
508
+ // 注入 CSS 动画(如果尚未注入)
509
+ if (!document.getElementById('brainage-danger-keyframes')) {
510
+ const style = document.createElement('style');
511
+ style.id = 'brainage-danger-keyframes';
512
+ style.textContent = `
513
+ @keyframes brainage-danger-flash {
514
+ 0%, 100% { opacity: 0; }
515
+ 50% { opacity: 0.6; }
516
+ }
517
+ `;
518
+ document.head.appendChild(style);
519
+ }
520
+
521
+ this.dangerOverlay.style.display = 'block';
522
+ this.dangerOverlay.style.animation = 'brainage-danger-flash 1s ease-in-out infinite';
523
+ }
524
+
525
+ public stopDangerFlash(): void {
526
+ if (this.dangerOverlay) {
527
+ this.dangerOverlay.style.display = 'none';
528
+ this.dangerOverlay.style.animation = '';
529
+ }
530
+ this.isDangerMode = false;
531
+ }
532
+
533
+ /**
534
+ * 切换图标
535
+ */
536
+ public setIcon(iconContent: string): void {
537
+ if (!this.icon) return;
538
+
539
+ // 判断是图片 URL 还是 emoji/文本
540
+ const isImageUrl = iconContent.startsWith('data:image/') || // base64 data URL
541
+ iconContent.startsWith('http') ||
542
+ iconContent.startsWith('/') ||
543
+ iconContent.startsWith('./') ||
544
+ iconContent.includes('.png') ||
545
+ iconContent.includes('.jpg') ||
546
+ iconContent.includes('.webp') ||
547
+ iconContent.includes('.svg');
548
+
549
+ if (isImageUrl) {
550
+ // 如果是 img 元素,直接更新 src
551
+ if (this.icon instanceof HTMLImageElement) {
552
+ this.icon.src = iconContent;
553
+ } else {
554
+ // 如果原来是 div,需要替换为 img
555
+ const parent = this.icon.parentElement;
556
+ if (parent) {
557
+ const imgElement = document.createElement('img');
558
+ imgElement.className = 'brainage-ui-icon';
559
+ imgElement.src = iconContent;
560
+ imgElement.alt = 'Brain Icon';
561
+
562
+ const computedStyle = getComputedStyle(this.icon);
563
+ Object.assign(imgElement.style, {
564
+ width: computedStyle.width,
565
+ height: computedStyle.height,
566
+ marginRight: computedStyle.marginRight,
567
+ flexShrink: '0',
568
+ objectFit: 'contain',
569
+ alignSelf: 'center', // 垂直居中对齐
570
+ } as CSSStyleDeclaration);
571
+
572
+ parent.replaceChild(imgElement, this.icon);
573
+ this.icon = imgElement;
574
+ }
575
+ }
576
+ } else {
577
+ // 如果是 div 元素,直接更新文本
578
+ if (this.icon instanceof HTMLDivElement) {
579
+ this.icon.textContent = iconContent;
580
+ } else {
581
+ // 如果原来是 img,需要替换为 div
582
+ const parent = this.icon.parentElement;
583
+ if (parent) {
584
+ const divElement = document.createElement('div');
585
+ divElement.className = 'brainage-ui-icon';
586
+ divElement.textContent = iconContent;
587
+
588
+ const computedStyle = getComputedStyle(this.icon);
589
+ Object.assign(divElement.style, {
590
+ fontSize: computedStyle.width, // 使用宽度作为字体大小
591
+ lineHeight: '1',
592
+ height: computedStyle.height,
593
+ marginRight: computedStyle.marginRight,
594
+ flexShrink: '0',
595
+ display: 'flex',
596
+ alignItems: 'center',
597
+ justifyContent: 'center',
598
+ } as CSSStyleDeclaration);
599
+
600
+ parent.replaceChild(divElement, this.icon);
601
+ this.icon = divElement;
602
+ }
603
+ }
604
+ }
605
+ }
606
+
607
+ /**
608
+ * Game Over 遮罩 + 面板放大居中
609
+ */
610
+ public showGameOver(gameOverScale: number = 1.5, overlayAlpha: number = 0.6): void {
611
+ const s = this.config.scale;
612
+ const extraW = 30 * s;
613
+ const baseW = this.config.width * s;
614
+ const newW = baseW + extraW;
615
+
616
+ // 创建遮罩层
617
+ const overlay = document.createElement('div');
618
+ overlay.className = 'brainage-gameover-overlay';
619
+
620
+ Object.assign(overlay.style, {
621
+ position: 'fixed',
622
+ left: '0',
623
+ top: '0',
624
+ width: '100vw',
625
+ height: '100vh',
626
+ backgroundColor: `rgba(0, 0, 0, ${overlayAlpha})`,
627
+ zIndex: '300',
628
+ } as CSSStyleDeclaration);
629
+
630
+ document.body.appendChild(overlay);
631
+
632
+ // 更新背景样式
633
+ this.background.style.width = `${newW}px`;
634
+ this.background.style.border = `${6 * s}px solid #ff4444`;
635
+
636
+ // 更新脑龄文本颜色
637
+ this.ageText.style.color = '#ff4444';
638
+
639
+ // 放大并居中容器
640
+ this.container.style.transform = `scale(${gameOverScale * parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--brainage-ui-scale') || '1')})`;
641
+ this.container.style.left = '50%';
642
+ this.container.style.top = '50%';
643
+ this.container.style.transform += ' translate(-50%, -60%)';
644
+ this.container.style.zIndex = '301';
645
+ }
646
+
647
+ public setPosition(x: number, y: number): void {
648
+ this.container.style.left = `${x}px`;
649
+ this.container.style.top = `${y}px`;
650
+ }
651
+
652
+ public setVisible(visible: boolean): void {
653
+ this.container.style.display = visible ? 'block' : 'none';
654
+ }
655
+
656
+ public getConfig(): FullBrainAgeUIConfig {
657
+ return { ...this.config };
658
+ }
659
+
660
+ public destroy(): void {
661
+ if (this.timer) {
662
+ clearInterval(this.timer);
663
+ this.timer = undefined;
664
+ }
665
+
666
+ this.stopDangerFlash();
667
+
668
+ // 清理响应式监听器
669
+ const cleanup = (this.container as any)._cleanupResize;
670
+ if (cleanup) cleanup();
671
+
672
+ // 移除 DOM 元素
673
+ if (this.container) {
674
+ this.container.remove();
675
+ }
676
+
677
+ if (this.dangerOverlay) {
678
+ this.dangerOverlay.remove();
679
+ }
680
+ }
681
+ }