# 测试题目：天空之城——用代码拍摄一段城市电影

请编写一个完整的、可直接在浏览器中打开的 **单个 HTML 文件**，使用 SVG、Canvas、CSS 和原生 JavaScript，呈现一段 **60 秒自动播放的天空之城漫游**。

你需要用代码构建一座悬浮于云海之上的未来都市，让观众感觉自己正在观看一段精心设计的动画短片。城市既要有令人震撼的尺度，也要有可观察的日常生活。

## 一、场景与视觉方向

这是一座具有全球化设计语言的天空之城：不同高度的悬浮城区通过空中轨道、步行桥和飞行交通连接，建筑、花园与公共空间在云层之间展开。

不要依赖某个国家的传统建筑或文化符号建立风格。你可以自行命名城市、设计地标，并决定其科技与自然如何共存。

画面需要同时表现：

- **悬空感：** 展示浮岛或平台的边缘、底部结构与下方云海，让观众清楚感受到城市的高度。
- **宏观尺度：** 至少三个不同高度的城区，具有辨识度的地标，以及连接城区的立体交通。
- **街头生活：** 空中咖啡店、维修站、便利店、观景站台、行人或服务机器人等可读细节。
- **未来科技：** 飞行载具、悬浮列车、全息信息或建筑媒体立面，技术应自然融入城市生活。
- **环境氛围：** 用流动云层、日光、阴影和空气透视表现纵深，可加入空中花园或垂落云间的瀑布。

追求电影感、清晰的视觉焦点和统一的设计语言。配色与时间由你决定，不强制使用霓虹夜景。避免用随机粒子和过度光晕代替场景设计。

## 二、60 秒自动演示

页面打开后，无需点击，自动开始演示。采用连续运镜，按照以下节奏展开：

| 时间 | 演示内容 |
| :--- | :--- |
| 0–15 秒 | **穿云初见：** 镜头穿过或越过前景云层，逐渐揭示悬浮城区、城市地标及下方云海。 |
| 15–40 秒 | **进入城市：** 镜头沿空中街区推进或横移，展示交通、店铺与日常生活；自动依次展示三个不同店铺的信息卡片。 |
| 40–55 秒 | **视觉高潮：** 安排一个有铺垫的城市事件，例如巨型飞行船从城区间穿过、悬浮列车跨越云间大桥，或云层打开后露出更庞大的上层城区。 |
| 55–60 秒 | **收束定格：** 镜头落在适合截图的全景构图上，显示简洁的城市名称；最后 2 秒停止镜头运动，场景内部动画继续。 |

具体运镜由你设计，但应遵守：

1. 以一次连续漫游为主，各个城区应具有连贯的空间关系。
2. 至少包含远景、中景、近景三个层次，通过视差或透视产生纵深。
3. 运镜应体现天空之城的高度差，可以适度升降，但避免突然跳转、快速旋转和频繁缩放。
4. 60 秒后保持最终画面，不自动黑屏或突然跳回开头。
5. 支持按 `R` 从头重播，按空格暂停或继续；暂停时镜头和场景动画一起暂停。

## 三、动态元素

在演示的 **20–35 秒区间**，画面内必须同时存在至少 **50 个可辨认、实际运动或变化的场景对象**，覆盖至少四类，例如：

- 沿不同空中航线飞行的载具或无人机。
- 穿行于城区之间的悬浮列车、升降舱或缆车。
- 在平台、步行桥或街道上行走的行人与机器人。
- 有内容变化的招牌、全息广告或建筑显示屏。
- 工作中的机械装置、气象设备或空中花园设施。

计数规则：

- 同一辆车、同一个人、同一块广告牌各算一个对象，其内部零件不重复计数。
- 仅因镜头移动而改变屏幕位置的静态对象不算动态元素。
- 云雾、雨滴、星点、火花、背景粒子和整片瀑布效果不计入这 50 个对象。
- 动态对象应在速度、路线、节奏或行为上存在合理差异，避免所有元素同步闪烁或机械往复。
- 飞行交通应有可理解的航线与层次，避免大量对象随机穿过建筑。

## 四、店铺交互与自动展示

场景中至少包含 **六家可交互店铺**，每家拥有不同的名称、业务与一条实时状态，例如：

> CLOUD NINE CAFÉ
>
> Coffee Above the Clouds
>
> Rating 98% · Window Seats Available

鼠标悬停店铺时，显示与该店铺对应的信息卡片；移开后收起。卡片应靠近目标，保持在画面内，并避免遮挡主要视觉焦点。

为方便无人操作录屏：

- 在 15–40 秒期间，自动依次展示三家不同店铺的卡片，每家展示约 3 秒。
- 自动展示与鼠标悬停使用同一套卡片组件和数据。
- 用户操作优先；鼠标正在悬停店铺时，不要被自动展示抢走。
- 在触摸设备上，允许点击店铺打开卡片。

## 五、交付与录屏要求

- 只交付一个 HTML 文件，样式和脚本全部内嵌。
- 所有视觉内容均由代码生成，不使用图片、视频、外部字体、第三方库或任何网络资源。
- 使用 SVG 或 Canvas 构建主要场景，可以结合 CSS 动画。
- 以 **1920 × 1080、16:9** 为主要展示尺寸，并适配其他窗口大小，不出现页面滚动条。
- 演示期间不显示导航栏、调试面板、大段说明或显眼的控制按钮。
- 默认静音，不要求浏览器授权，不需要点击“开始”。
- 随机生成的场景使用固定种子；重播时，应得到相同的场景布局与事件顺序。
- 优先保证稳定、流畅的播放体验，避免持续创建无限增长的 DOM 节点或动画对象。

## 六、测试接口

请在 `window.__CITY_DEMO__` 上暴露以下接口。接口仅供测试使用，不显示在画面上：

```js
window.__CITY_DEMO__ = {
  // 从头播放，同时重置场景状态
  restart(),

  // 暂停或继续全部动画
  pause(),
  play(),

  // 返回实际播放状态
  getState(),
  // {
  //   time: 0,              // 当前演示秒数，范围 0–60
  //   paused: false,
  //   phase: "arrival",     // arrival / street / climax / finale
  //   activeShopId: null
  // }

  // 返回当前画面内实际参与动画的对象
  getAnimatedObjects(),
  // [{ id, category, screenBounds: { x, y, width, height } }]

  // 返回店铺数据及当前屏幕位置，供测试模拟真实鼠标悬停
  getShops()
  // [{ id, name, business, status, visible, screenBounds }]
};
```

接口必须与实际渲染和交互状态关联，不得返回用于满足测试的固定数字或虚构对象。

**最终输出：只输出完整 HTML 代码，不附加解释。**
