用 Claude 生成会断电自救的赛博城市:HTML 单文件与状态机实战

发布时间:2026/8/27 7:46:07
用 Claude 生成会断电自救的赛博城市:HTML 单文件与状态机实战 最近不少开发者在讨论 Claude 和 HTML 的组合但真正让人兴奋的不是“AI 能写代码”这件事本身而是它能用一段自然语言描述直接产出一个可以双击运行、带完整交互逻辑的单文件应用。比如这个“会断电自救的赛博城市”霓虹灯闪烁、雨水下落、建筑群灯火通明突然全城陷入黑暗几秒后备用电源启动核心设施重新亮起城市自己完成了一次“电力自愈”。如果只看表面这像是一个用来炫技的娱乐 Demo。但拆开看它其实是一个很小的前端状态机项目单文件交付、实时渲染、事件驱动切换、自动恢复机制。它能够在一百多行代码里表达清楚恰好说明 Claude 这类大模型真正降低的不是“写码”的成本而是“把一个想法变成可运行原型”的成本。这篇文章会从四个维度展开第一为什么单文件 HTML 应用值得关注第二断电自救逻辑的核心原理是什么第三如何用 Claude 生成类似项目以及提示词怎么写第四我会给出一份可运行的最小实现代码并完整拆解运行验证、常见问题和工程化建议。读完你可以照着跑通这个项目也可以把它改造成自己的城市模拟器、状态机演示应用或 AI 生成前端页面的实践模板。1. 一个 HTML 文件为什么能“装下”一座城市“单文件 HTML 应用”这个概念听起来很简单但它和常规前端项目的差异恰恰是理解 Claude 输出价值的关键。一个传统的前端项目通常是这样的结构index.html负责结构style.css负责样式app.js负责逻辑再配上一堆构建工具、依赖包和配置文件。开发流程是写代码、启动本地服务、编译、打包、部署。这个流程没有问题但它解决的是“复杂产品需要长期迭代”的需求。而 Claude 直接生成一个赛博城市交付物是单个.html文件。这个文件里同时包含 CSS、JavaScript 和 HTML 结构没有外部依赖不需要安装 Node.js不需要npm install不需要打包器只要操作系统里有一个浏览器就能运行。两种方式没有优劣之分但使用场景完全不同对比维度单文件 HTML 应用常规前端工程交付形态一个文件双击即可运行多文件、多模块、需构建环境依赖只需浏览器需要 Node.js、包管理器、构建工具迭代方式适合原型验证、教学演示、个人作品适合生产系统、多人协作、长期维护AI 生成的难度低模型更容易一次输出完整文件高需要理解项目结构和模块依赖典型场景Demo、小工具、单页动画、状态机演示中大型 Web 应用、前端工程化项目Claude 在生成单文件 HTML 时的优势在于它可以把“视觉设计 动画逻辑 状态管理 交互事件”一次性编排在同一个作用域里不需要像传统开发那样考虑模块拆分和依赖注入。换句话说AI 生成 HTML 不再只是一个“写页面”的工具而是变成了一个“生成微型应用程序”的入口。这个赛博城市项目就是一个典型的例子。它表面上是一个视觉作品实际是一个包含状态机的实时渲染程序。城市里的每一扇窗户、每一滴雨水、每一条霓虹灯带都是运行在 Canvas 上的动态数据。断电、备用电源、恢复供电是一个完整的事件驱动流程。Claude 把所有这些压缩进一个文件这让“生成一个可运行的交互系统”变得像“生成一个静态页面”一样简单。2. 断电自救功能的本质是状态机“断电自救”听起来很高级但它的本质并不复杂一个状态机。状态机是前端开发中非常基础也非常重要的概念。它把程序运行时的状态抽象成有限个节点再用事件驱动状态之间的切换。拿这个赛博城市来说电力系统可以抽象成三个状态正常供电normal城市所有灯光正常点亮霓虹灯闪烁雨滴落下。断电blackout全城灯光熄灭画面变暗只保留极少数应急信号。备用电源backup城市不是一次性恢复而是优先恢复核心设施的供电比如中央塔、主干道、医疗和通信建筑其他区域慢慢亮起。这三个状态之间的切换逻辑是当前状态触发事件下一个状态说明normal自动计时到达或用户点击“模拟断电”blackout进入全城黑暗blackout持续 2~3 秒后进入 backupbackup启动备用电源核心设施恢复backup持续数秒后进入 normalnormal全城恢复正常供电任意状态用户点击“手动恢复”normal强制恢复供电状态机的价值在于它把“城市是否亮着”这种模糊的视觉状态转换成了代码里明确可判断、可控制、可测试的离散状态。你不需要在十几处 if 里分散地判断“是不是断电了”而是可以统一定义状态、统一响应事件、统一渲染。断电自救的“自救”体现在哪里就体现在blackout - backup - normal这条自动恢复链路上。城市在断电后并不是永远黑着而是按照预设的恢复策略自动切换。实际上现代电力系统里的“自愈”概念也是类似思路检测到故障后通过自动化装置隔离故障区恢复非故障区供电。这个赛博城市 Demo 用三行状态切换模拟了一个简化版的电网自愈机制。所以在写这个项目的提示词时最核心的不是要求 Claude “画一座好看的城市”而是要求它“用状态机控制城市电力系统”。技术模型一旦正确视觉表现只是渲染层的问题。3. 用 Claude 生成赛博城市的提示词设计很多人让 AI 生成网页得到的只是一个“看起来很漂亮但啥也不能点”的静态页面。原因很简单提示词里只写了“风格”没有写“行为”。要让 Claude 生成一个会断电自救的城市提示词必须包含四类信息交付形态一个单文件 HTML包含 CSS 和 JavaScript无外部依赖。视觉风格赛博朋克、夜晚、霓虹灯、下雨、建筑窗户灯光。核心功能城市有三种电力状态能模拟断电和备用电源自动恢复。交互细节提供手动触发断电和恢复按钮自动演示流程状态提示要实时显示。下面是一段可以直接复制使用的示例提示词你是一个资深前端工程师擅长 Canvas 2D 动画和像素美术风格。 请在单个 index.html 文件中实现一个赛博朋克风格的夜晚城市模拟要求如下 1. 城市由多层背景建筑、霓虹灯牌、雨滴特效构成。 2. 页面顶部显示城市电力状态正常绿色、断电红色、备用电源黄色。 3. 提供两个按钮模拟断电、手动恢复。 4. 默认运行 20 秒后自动模拟一次断电断电后 3 秒启动备用电源核心设施恢复亮灯再 5 秒后全城恢复供电。 5. 城市至少包含 50 栋建筑建筑窗户随机亮灯。 6. 断电时所有窗户灯全灭备用电源只点亮核心建筑的灯光。 7. 使用 requestAnimationFrame 驱动渲染不要用外部图片、字体或库。 8. 保持单文件代码结构清晰关键函数加注释。这段提示词的关键不是“赛博城市”这四个字而是后面的约束条件。你给 Claude 的行为约束越多它生成的结果就越接近可运行的程序而不是一张静态图片。如果第一次生成的效果不理想不要急着重开对话可以继续追加修改要求。比如建筑太稀疏了把建筑密度提高一倍楼顶增加一些红色航空警示灯。 断电过程要平滑不要突然切换用 0.5 秒的渐入渐出效果。 备用电源恢复时先点亮主干道两侧的建筑再逐层向外扩散。Claude 对已有的代码进行增量修改比从头生成新代码更稳定。这一点在 AI 辅助开发中非常实用先让模型给出一个“最小可用版本”再通过多轮对话逐步调优而不是指望一次提示词就得到完美成品。4. 环境准备你需要哪些工具虽然这个项目最终交付物只有一个 HTML 文件但在生成和运行过程中你仍然需要准备几样东西。4.1 生成端Claude 的访问方式要生成这个项目你需要能够使用 Claude。目前常见的方式包括Claude 网页版适合零基础用户直接在对话窗口输入提示词即可无需安装任何软件。Claude 桌面端适合日常高频使用的用户体验更接近本地应用。Claude Code 命令行工具适合开发者可以在终端里通过命令行让 Claude 读写文件、执行命令适合把生成的 HTML 直接写入项目目录。如果使用 Claude Code在本地初始化项目时通常会经历类似下面的流程# 安装命令行工具具体命令以官方文档为准 npm install -g anthropic-ai/claude-code # 在项目目录初始化 cd city-simulator claude在终端输入你的需求请在当前目录生成一个 cities.html 内容是赛博朋克城市夜景包含断电和备用电源自动恢复机制单文件实现。如果你还不太熟悉命令行工具直接用网页版把输出的 HTML 复制到本地文件即可。网页版更适合第一次尝试这个项目的人重点先跑通流程和验证效果。4.2 运行端浏览器与本地预览工具一个现代浏览器Chrome、Edge、Firefox 都可以。一个文本编辑器VS Code、Sublime、Notepad 均可。可选VS Code 的 Live Server 插件用于本地静态预览。直接把 HTML 文件拖到浏览器窗口一般就能运行。如果后续涉及调试建议使用 VS Code 的 Live Server它可以帮你启动一个本地静态服务避免file://协议下某些浏览器限制带来的干扰。# 在项目目录启动 Live Server # VS Code 中右键 cities.html选择 Open with Live Server4.3 网络与服务状态提醒使用 Claude 网页版或桌面端时需要保证网络可以正常访问官方服务。如果你收到类似 “unfortunately, claude is not available to new users right now” 的提示通常是服务策略或容量限制问题可以稍后重试或者检查账号状态。在本地运行 HTML 则不需要联网所有动画和逻辑都在浏览器里本地执行。另外需要提醒的是不要把项目中的密钥、私人数据或未公开的业务信息发给 AI 模型生成代码后也建议先审查再运行尤其是当代码来自外部工具时。5. 手写最小实现一个可运行的断电自救赛博城市为了让你理解这个项目背后的代码结构我手写了一个简化版本。它没有 Claude 生成的效果那么华丽但核心机制完整城市建筑、窗户灯光、雨滴、电力状态机、自动恢复流程全部包含在同一个 HTML 文件里。5.1 完整代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 title赛博城市 - 断电自救模拟/title style * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #0a0e1a; overflow: hidden; font-family: Courier New, monospace; } #city { display: block; width: 100vw; height: 100vh; } #panel { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; align-items: center; padding: 10px 16px; background: rgba(0, 0, 0, 0.65); border: 1px solid #2affd0; border-radius: 8px; color: #2affd0; z-index: 10; font-size: 14px; } .status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 6px; } button { background: #102a2a; color: #2affd0; border: 1px solid #2affd0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-family: inherit; } button:hover { background: #2affd0; color: #0a0e1a; } /style /head body canvas idcity/canvas div idpanel span classstatus-dot iddot/span span idstatusText电力状态正常/span button idbtnBlackout模拟断电/button button idbtnRecover手动恢复/button /div script const canvas document.getElementById(city); const ctx canvas.getContext(2d); const statusText document.getElementById(statusText); const dot document.getElementById(dot); let W, H; function resize() { W canvas.width window.innerWidth; H canvas.height window.innerHeight; rebuildCity(); } window.addEventListener(resize, resize); // 电力状态 const MODE { NORMAL: normal, BLACKOUT: blackout, BACKUP: backup }; let mode MODE.NORMAL; let autoTimer 0; // 霓虹灯配色 const NEON_COLORS [#ff2d95, #2affd0, #ffd166, #4d8dff]; // 建筑数据 let buildings []; function rebuildCity() { buildings []; const cols Math.ceil(W / 24); for (let i 0; i cols; i) { const w 18 Math.random() * 22; const h 50 Math.random() * 240; const x i * 24; const y H - 60 - h; const rooms []; const roomRows Math.floor(h / 20); const roomCols Math.max(1, Math.floor(w / 12)); for (let r 0; r roomRows; r) { for (let c 0; c roomCols; c) { rooms.push({ x: x 4 c * 12, y: y 8 r * 20, w: 6, h: 10, on: Math.random() 0.45 }); } } buildings.push({ x, y, w, h, rooms, core: Math.random() 0.15, neonColor: NEON_COLORS[Math.floor(Math.random() * NEON_COLORS.length)] }); } } // 雨滴 let raindrops []; function initRain() { const count Math.min(220, Math.floor(W / 6)); for (let i 0; i count; i) { raindrops.push({ x: Math.random() * W, y: Math.random() * H, len: 10 Math.random() * 14, speed: 8 Math.random() * 8 }); } } function drawBuildings() { buildings.forEach(b { const isCore b.core; let brightness 1; if (mode MODE.BLACKOUT) brightness 0.08; if (mode MODE.BACKUP) brightness isCore ? 0.85 : 0.18; ctx.fillStyle rgba(20, 28, 55, ${0.5 brightness * 0.4}); ctx.fillRect(b.x, b.y, b.w, b.h); b.rooms.forEach(room { let on room.on; if (mode MODE.BLACKOUT) on false; if (mode MODE.BACKUP) on isCore on; if (on) { ctx.fillStyle rgba(255, 220, 120, ${0.6 Math.random() * 0.3}); ctx.fillRect(room.x, room.y, room.w, room.h); } }); if (b.core mode ! MODE.BLACKOUT) { ctx.fillStyle b.neonColor; ctx.fillRect(b.x - 2, b.y b.h / 2 - 2, b.w 4, 3); } }); } function drawRain() { ctx.strokeStyle rgba(120, 200, 255, 0.4); ctx.lineWidth 1; raindrops.forEach(d { ctx.beginPath(); ctx.moveTo(d.x, d.y); ctx.lineTo(d.x, d.y d.len); ctx.stroke(); d.y d.speed; if (d.y H) { d.y -20; d.x Math.random() * W; } }); } function updateStatus() { const map { normal: [电力状态正常, #2ecc71], blackout: [电力状态断电, #e74c3c], backup: [电力状态备用电源, #f1c40f] }; statusText.textContent map[mode][0]; dot.style.background map[mode][1]; document.body.style.background mode MODE.BLACKOUT ? #05060a : #0a0e1a; } function doBlackout() { if (mode ! MODE.NORMAL) return; mode MODE.BLACKOUT; updateStatus(); setTimeout(() { if (mode ! MODE.BLACKOUT) return; mode MODE.BACKUP; updateStatus(); setTimeout(() { if (mode ! MODE.BACKUP) return; mode MODE.NORMAL; updateStatus(); }, 4000); }, 2500); } function tick(ts) { if (!ts) ts 0; autoTimer 16; if (autoTimer 22000) { autoTimer 0; doBlackout(); } ctx.clearRect(0, 0, W, H); drawBuildings(); drawRain(); ctx.fillStyle rgba(10, 15, 30, 0.9); ctx.fillRect(0, H - 40, W, 40); if (mode ! MODE.BLACKOUT) { const x (ts / 50) % (W 200) - 100; ctx.fillStyle rgba(255, 180, 80, 0.04); ctx.fillRect(x, 0, 100, H); } requestAnimationFrame(tick); } document.getElementById(btnBlackout).addEventListener(click, doBlackout); document.getElementById(btnRecover).addEventListener(click, () { mode MODE.NORMAL; updateStatus(); }); resize(); initRain(); updateStatus(); requestAnimationFrame(tick); /script /body /html5.2 关键逻辑拆解这段代码的核心可以拆成四个部分。第一部分是建筑生成。rebuildCity()遍历整个屏幕宽度按固定间距生成建筑。每栋建筑内部再把窗户分割成若干个房间格子每个房间在初始化时随机决定是否亮灯。这里用Math.random() 0.45控制亮灯比例保证城市看起来有生活气息。第二部分是电力状态机。MODE常量定义了normal、blackout、backup三个状态mode变量保存当前状态。updateStatus()根据状态更新页面上的文字、指示灯和背景色。所有渲染逻辑都根据mode分支执行没有散落的状态判断。第三部分是断电自救流程。doBlackout()先检查是否处于正常状态然后切换为断电等待 2.5 秒后进入备用电源状态再等待 4 秒后恢复供电。每个setTimeout回调中都再次检查当前状态避免状态被其他事件覆盖后产生错误跳转。第四部分是渲染循环。tick()函数通过requestAnimationFrame驱动每帧都调用drawBuildings()画建筑、drawRain()画雨滴。城市是否亮灯完全由mode和建筑/房间属性共同决定。这个实现里用了一个简易的状态流转方式setTimeout嵌套。严格来说它不算最优雅的状态机写法因为时序控制分散在多个回调里。真实项目中更推荐用统一的事件管理器或基于时间戳的调度器。但这个简化版本足够让你理解状态切换的完整链路。6. 运行验证与效果检查代码写完之后怎么验证它真的做到了“断电自救”推荐按下面的步骤操作。6.1 运行方式将上面的代码保存为city.html然后双击文件用浏览器打开。也可以使用 VS Code 的 Live Server 启动。# 如果在项目目录下使用 Python 也可以 python3 -m http.server 8000 # 浏览器访问 http://localhost:80006.2 预期效果打开页面后你应该能看到深色背景下有一排排高低错落的建筑。建筑窗户有温暖的黄色灯光部分核心建筑有彩色霓虹灯条。雨滴持续下落整个画面在微微呼吸。页面顶部显示“电力状态正常”指示灯为绿色。等待大约 20 秒或者直接点击“模拟断电”按钮城市会进入断电状态。此时建筑灯光全部熄灭画面明显变暗状态文字变为“电力状态断电”指示灯变为红色。再过约 2.5 秒城市自动进入备用电源状态。核心建筑的灯光重新亮起大多数普通建筑仍然黑暗状态文字变为“电力状态备用电源”指示灯变为黄色。再过约 4 秒全城恢复供电所有灯光重新亮起状态回到“正常”。这个自动从断电切换到备用电源再恢复供电的过程就是标题里说的“断电自救”。6.3 如何判断成功判断标准很简单状态文字和画面效果是否同步变化。如果点击“模拟断电”后画面没有变暗说明事件绑定或状态切换代码可能有问题。如果一切正常可以手动点击“手动恢复”按钮城市应该立即从任意状态恢复到正常供电。这一步验证了状态机的“任意状态可强制复位”能力。6.4 失败了先看哪里如果页面一片空白第一件事不是改代码而是按 F12 打开浏览器开发者工具切到 Console 面板看有没有红色的 JavaScript 报错。大部分问题都是语法错误、变量或 DOM 元素找不到控制台会直接告诉你。如果页面有建筑和雨滴但按钮没有反应优先检查按钮的addEventListener是否在 DOM 加载完成后执行以及相关的函数是否被正确引用。如果断电时画面没有变暗重点检查drawBuildings()里brightness的分支判断是不是漏掉了blackout状态。7. 常见问题与排查思路结合这个项目和其他人用 Claude 生成 HTML 时经常遇到的问题这里整理了一份排查表。问题现象可能原因排查方式解决方案双击 HTML 文件后页面空白有 JavaScript 报错或浏览器安全策略阻止了某些 API按 F12 打开控制台查看具体报错根据报错修复语法问题如果涉及外部资源改用本地静态服务器预览城市只在屏幕一部分显示不同步没有监听 resize 事件检查窗口大小变化时代码是否重新计算画布宽度使用window.addEventListener(resize, ...)重新初始化画布断电时灯光没有熄灭渲染逻辑没有判断状态检查drawBuildings()中是否有blackout状态分支在断电状态下强制关闭所有窗户灯按钮点击无反应事件绑定错误或函数作用域问题在控制台输入按钮对应函数确认函数是否存在使用document.getElementById正确获取按钮并绑定事件自动恢复流程没有执行定时器逻辑被跳过检查doBlackout()中的 setTimeout 是否被正确触发确认没有在doBlackout()开头提前 return断电和恢复的效果太突兀缺少过渡动画添加透明度渐变或亮度插值用一个transitionValue变量控制灯光渐亮渐暗Claude Code 安装后提示error: claude native binary not installed. either postinstall did not run...依赖安装中断或原生二进制缺失查看安装日志重新执行安装步骤清理 npm 缓存后重装或使用官方安装脚本访问 Claude 时提示claude is not available to new users right now服务容量或账号策略限制检查官方状态页确认服务恢复情况稍后重试或使用已注册且可正常访问的账号把自定义模型名传给 Claude Code 后提示is not a model this version of claude code recognizes模型标识与当前版本支持范围不匹配检查当前 Claude Code 版本支持的模型列表更新 Claude Code 版本或移除不支持的模型配置这里想多说一句关于“模型不识别”的问题。有些开发者会尝试通过兼容接口接入第三方模型以期望使用 Claude Code 的工程化能力。这类做法在技术上存在可行性但要注意第三方模型的接口兼容性、版本匹配、服务条款和稳定性都是不确定因素。社区热搜里出现的deepseek-v4-pro is not a model this version of claude code recognizes这类报错本质就是版本不匹配的典型表现。建议在正式项目中使用官方支持的模型配置避免把时间花在环境兼容问题上。8. 从“好玩”到“工程化”写给你我的最佳实践如果只是跑通一个 Demo这篇文章到这里就够了。但如果你想把这类 AI 生成页面的能力用到真实项目中下面几条建议值得收藏。8.1 提示词要写“行为”而不是只写“外观”很多人让 AI 生成页面只写“做一个赛博城市”得到的基本上是静态图片。原因在于模型只知道你要什么风格不知道你要什么功能。好的做法是把需求分层第一层交付形态比如“单文件 HTML无外部依赖”。第二层视觉风格比如“赛博朋克、夜晚、霓虹灯、雨滴”。第三层交互行为比如“点击按钮触发断电3 秒后启动备用电源”。第四层自动流程比如“20 秒后自动演示一次”。行为描述越具体生成结果越接近可运行程序。建议把提示词当成需求文档来写而不是当成一句口号。8.2 AI 生成代码也必须审查无论代码是 AI 写的还是同事写的运行前都建议快速扫描。对单文件 HTML 来说重点看三处是否有外部请求比如fetch、XMLHttpRequest、图片链接。是否有危险 API比如eval、Function、动态脚本注入。是否有敏感信息比如密钥、Cookie、内部地址。AI 生成代码的优点是快缺点是它可能基于训练数据里的“常见模式”来推断不保证每一行都符合你的项目要求。生产环境接入前必须经过代码评审和功能测试。8.3 单文件应用适合什么不适合什么适合单文件 HTML 的场景包括原型验证、个人作品、教学演示、小工具、内部测试页、数据可视化看板。这类场景追求的是“快速得到一个能用的东西”而不是“这个系统能活十年”。不建议用单文件 HTML 构建的场景包括多角色权限系统、复杂数据交互、需要持续维护的业务系统、需要团队并行开发的模块。这类系统没有工程化支撑后期维护成本会非常高。如果你发现一个单文件项目的代码已经超过几千行或者开始有人在多个地方改状态就该考虑拆分了。8.4 状态逻辑要集中不要散落在各处这个赛博城市 Demo 里的状态逻辑很简单但如果你要扩展它比如增加“白天 / 夜晚”、“下雨 / 晴天”、“节日模式”等状态一定要把状态集中管理。最直接的做法是定义一个状态枚举对象统一管理状态列表再定义一个事件函数负责状态切换最后在渲染函数里只根据当前状态绘制内容。不要在每个按钮的点击回调里直接改一堆变量那样代码很快会变成一团乱麻。如果项目复杂到状态切换需要依赖多个条件可以考虑引入xstate这类状态机库或者在纯前端项目里用最朴素的switch-case管理。小型项目没有必要上重框架但中大型项目建议遵循“状态集中、渲染分离”的原则。8.5 在线操作和本地运行要分开这个项目是纯前端的所有代码都在浏览器本地运行不涉及服务器操作风险较低。但如果你把类似思路用到服务端自动化或运维场景比如让 AI 生成一个会操作服务器的脚本就必须遵循最小权限、先测试、再备份、可回滚的原则。任何涉及生产环境的变更都要先在测试环境验证并对可能破坏数据的操作做好备份。另外不要把个人 API Key、数据库连接串、内网地址写进任何由 AI 生成的 HTML 或脚本文件里。这类文件一旦被分享敏感信息就暴露了。8.6 用好 Claude 的渐进式生成直接让 Claude 一次生成最复杂的效果成功率往往不高。更可靠的路径是先生成一个最小版本比如一个城市、一个状态切换。运行确认效果。再让 Claude 增加细节比如建筑密度、雨滴数量、霓虹灯牌。继续迭代比如增加渐变动画、声音效果、统计面板。渐进式生成的另一个好处是出错点更容易定位。如果你一次让模型生成 50 个功能中途任何一个环节出错你根本不知道是模型理解错了还是某个功能影响了另一个功能。9. 总结与下一步实践“Claude 用 HTML 现场造出会断电自救的赛博城市”这个项目看起来像是一个娱乐向 AI 演示但它背后的技术链条值得认真学一遍单文件 HTML 应用如何承载完整交互逻辑、状态机如何驱动视觉状态变化、提示词如何从“描述外观”升级为“定义行为”、AI 生成代码如何在验证和审查后落地。建议你按下面的顺序动手练习第一把文章中的最小实现代码复制到本地跑通重点观察电力状态的切换过程。第二用 Claude 重新生成一版自己的城市增加更多细节比如街道车辆流动、城市飞行器、不同的恢复策略。第三把提示词里的约束条件逐步收紧比如要求 Claude 使用 TypeScript 风格注释、增加 60 帧性能监控、加入渐变动画。这会让你更熟悉 AI 辅助开发的工作方式。第四如果你对状态机感兴趣可以把setTimeout嵌套改成更规范的状态调度器或者干脆用xstate重写一版。这会让“断电自救”从 Demo 变成一个更健壮的示例项目。最后一个经验AI 生成前端应用真正的分水岭不是它能生成多少代码而是你能不能把一个模糊想法拆解成清晰需求。状态机、渲染循环、事件驱动、数据模型——这些基础概念懂得越扎实你越能让 AI 生成为你所用。这个赛博城市只是一扇门门后面是“用自然语言构建可运行系统”的新工作方式。