diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 87c027d7..e1b45c6e 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -16,14 +16,102 @@ --- -## 2026-05-22 敲木鱼图片创作采用双图 image2 链路 +## 2026-05-24 创作 Tab banner 轮播只展示主题赛 -- 背景:敲木鱼自定义题材只生成中央敲击物时,运行态缺少与新主题匹配的竖屏背景;若直接让背景 prompt 自由发挥,又容易把敲击物或木槌画进背景里。 -- 决策:敲木鱼 `compile-draft` / `regenerate-hit-object` 图片链路固定为两步 image2 edits。第一步调用 VectorEngine `/v1/images/edits` + `gpt-image-2`,以默认木鱼图作为结构和画风参考,用户上传参考图只作为同次请求的新主题参考,结合用户题材关键词或参考图主题生成 `1:1` 透明底新敲击物并写回 `hitObjectAsset`;第二步以新敲击物图作为主题和画风参考,结合用户原始题材生成 `9:16` 背景环境图并写回 `backgroundAsset`。两步 prompt 使用 PRD 中固定隐藏关键词,不追加额外 negative prompt;背景图不得包含敲击物本体或木槌互动物品。 -- 影响范围:`api-server` 木鱼图片生成编排、`wooden_fish_work_profile.background_asset_json`、shared contracts、前端结果页 / 运行态背景展示、敲木鱼 PRD 和平台链路文档。 +- 背景:创作 Tab banner 曾经把后端入口配置里的默认活动横幅和两个主题赛一起轮播,导致首屏出现 58000 奖池活动卡,和当前只强调拼图 / 抓大鹅主题赛的产品口径不一致。 +- 决策:创作 Tab 首屏 banner 轮播只展示 `拼图主题创作赛` 与 `抓大鹅主题创作赛` 两张主题卡;后端返回的 `eventBanner` 仅作为开始时间、结束时间等公共字段来源,不再直接作为一张轮播卡渲染。banner 底部顺序固定为开始 / 结束时间条在上、分页点在下,且二者都在封面内容底部。 +- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +- 验证方式:`CustomWorldCreationHub.test.tsx` 应断言默认活动标题不出现在 start-only 创作页,且 `creation-event-banner__timebar` 位于 `creation-event-banner__pager` 前。 +- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-24 创作 Tab 首屏字号收敛到普通 UI 档位 + +- 背景:创作 Tab 的右上角泥点胶囊、赛事 banner、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明曾经偏向展示级字号,和其它页面的常规 UI 字号不一致。 +- 决策:创作首屏优先使用 `11px` 到 `14px` 的普通 UI 字号档位;仅在数字本体或强调值上做局部加粗,不使用 `text-lg`、`text-xl` 或更大的展示级字号来撑首屏。 +- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、创作 Tab 相关测试、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +- 验证方式:`CustomWorldCreationHub.test.tsx` 的字号快照测试和本地浏览器检查都应确认右上组件、banner、分类 Tab、模板卡标题 / 副标题 / 消耗说明没有回到大字号。 +- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-24 草稿页未读点统一使用暖棕色 + +- 背景:草稿页底部 Tab 和作品架的未读点之前仍用固定红色和红色 glow,和平台暖白/陶土橙体系不一致,也会让草稿未读态显得像危险告警。 +- 决策:`platform-nav-unread-dot` 与 `creation-work-card__unread-dot` 统一改用平台暖棕色 token,并把 glow 也切到暖棕色,不再直接写红色 literal 或红色阴影。 +- 影响范围:`src/index.css`、草稿页底部导航、草稿页作品架、相关 CSS 回归测试。 +- 验证方式:`src/index.test.ts` 需要断言两个 unread dot block 都不再包含 `#b64a35` 或 `rgba(239, 68, 68, ...)`,并且仍引用 `--platform-unread-dot-fill` / `--platform-unread-dot-glow`。 +- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-24 创作 Tab 模板卡点击直达已有玩法入口表单 + +- 背景:创作 Tab 首屏需要对齐参考图,展示赛事 banner、玩法模板分类和两列模板卡;点击模板卡时,空白入口页会让用户多走一层,占位感也会让人误以为功能未接好。 +- 决策:`/creation/` 直达对应玩法已有的入口创作表单 stage,不再保留空白创作入口页。RPG、拼图、抓大鹅、汪汪声浪、敲木鱼、视觉小说、宝贝识物等都直接进入既有工作台,继续承接草稿恢复和后续编排。创作 Tab 首屏 banner 按参考图拆成右上泥点胶囊、主体宣传封面图文、底部开始/结束时间条和分页点;玩法模板卡使用独立 `creation-template-card` 白底信息区,不复用暗图蒙版 `platform-creation-reference-card`,确保标题、描述和“预计消耗 10-20 泥点”可见。 +- 影响范围:`src/components/platform-entry/platformEntryTypes.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、创作大厅交互测试与平台入口文档。 +- 验证方式:`npm test -- src/routing/appPageRoutes.test.ts`、`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t \"create tab opens match3d entry form from the template card|create tab opens puzzle entry form from the template card|create tab opens bark battle entry form from the template card\"`、`npm run typecheck`、`npm run check:encoding` 通过;创作卡片点击后应进入对应工作台,不再出现空白入口页。 +- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-24 创作 Tab 顶栏余额与赛事奖池分离展示 + +- 背景:创作页顶部、banner 奖池和玩法卡消耗口径曾经混在一起,容易把活动奖池误认成账号余额,也让横向空间被外部边框和过大的卡片高度挤占。 +- 决策:移动端创作 Tab 顶栏与 `陶泥儿` 品牌同一行只显示真实账户泥点数,数据直接取 `profileDashboard.walletBalance`;banner 内只展示赛事奖池,新增拼图主题创作赛和抓大鹅主题创作赛,两个主题奖池各 `1000` 泥点数;玩法卡封面右下角固定展示 `10-20泥点数`,列表外框取消,卡片高度和横向间距一起收紧。 +- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、创作页相关测试和玩法链路文档。 +- 验证方式:移动端浏览器检查应看到创作顶栏余额、卡内分页点、内嵌横向 banner 和更紧凑的玩法卡;`CustomWorldCreationHub.test.tsx` 与 `RpgEntryHomeView.recharge.test.tsx` 的定向断言应保持通过。 +- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-24 发现 / 创作 / 草稿三页去掉外层全局卡片壳 + +- 背景:发现 Tab、创作 Tab 和草稿 Tab 的页面根区原本都套着 `platform-page-stage`,导致全局内容卡片壳把横向空间吃掉,也让创作页和草稿页与发现页的频道标签 / 列表卡风格拉不开。 +- 决策:这三页的根内容区不再使用 `platform-page-stage` 作为外层全局卡片壳,只保留 `platform-remap-surface` 作为主题与输入框样式钩子;草稿页顶部 `全部 / 草稿 / 已发布` 切换复用发现页的 `platform-mobile-home-channel` 频道标签样式。 +- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkTabs.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/index.css`、相关创作 / 发现 / 草稿测试。 +- 验证方式:创作 Hub 和发现页定向测试通过;浏览器里这三页的根区不再出现 `platform-page-stage`,但仍保留 `platform-remap-surface` 命中。 +- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-23 拼图生成页按后端真实进度推进阶段 + +- 背景:拼图生成页原先会按本地耗时自动推进步骤,容易在后端真实生成尚未完成时跳到后续阶段,导致页面状态和会话进度脱节。 +- 决策:拼图生成页的跨步骤推进只认后端会话 `progressPercent` 的真实里程碑,当前步骤内部再用本地耗时假进度平滑展示;总进度初始必须为 `0%`,之后按 `0-88`、`88-94`、`94-96`、`96-98` 的真实里程碑区间平滑推进。只要当前步骤生成内容未完成,就必须停留在当前步骤。页面只展示当前步骤标题和进度,不展示步骤详细描述。`生成拼图首图` 单独按 4 分钟估算,完整 AI 重绘路径约 448 秒;上传图且关闭 AI 重绘路径跳过首图生成,仍约 208 秒。 +- 影响范围:`src/services/miniGameDraftGenerationProgress.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/CustomWorldGenerationView.tsx`、拼图生成页相关测试与玩法链路文档。 +- 验证方式:拼图生成页恢复、轮询和测试都应以 `puzzleProgressPercent` 驱动阶段推进;`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts src/components/CustomWorldGenerationView.test.tsx`、`npm run typecheck`、`npm run check:encoding` 通过。 +- 关联文档:`docs/【玩法创作】拼图生成页进度口径-2026-05-23.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-23 所有玩法生成页统一圆环主视觉 + +- 背景:多个玩法生成页分别展示横向总进度条、步骤列表或三槽位列表,和最新参考图里的陶泥儿圆环等待态不一致,也让移动端信息密度偏高。 +- 决策:`media/create_bg_video.mp4` 作为固定全屏背景层循环静音播放,主进度统一改为居中大圆弧,正下方保留 90 度留空;生成页顶部只保留返回入口和状态胶囊,圆弧左右悬浮半透明“预计等待 / 已耗时”时间卡,下方保留半透明当前步骤单卡和当前作品信息卡。生成页不再列表展示每个步骤块,只显示当前步骤名称和当前步骤进度;圆弧和当前步骤卡不再被独立大面板嵌套出双层卡片感。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted`。顶部返回使用 `text-xs-sm`,右上状态使用 `11px-12px`,时间卡标签使用 `9px-10px`,时间值只展示纯时间,不重复拼“预计还需 / 已耗时”前缀;当前步骤标签使用 `10px-11px`,步骤名使用 `14px-15px`,步骤状态使用 `11px-12px`,底部玩法信息标题固定使用 `13px`,避免生成页 UI 字号大于其它页面。`CustomWorldGenerationView` 承接 RPG、拼图、抓大鹅、大鱼吃小鱼、方洞、跳一跳、敲木鱼、宝贝识物、视觉小说等共用生成页;汪汪声浪独立 `BarkBattleGeneratingView` 也对齐同一垂直布局。 +- 影响范围:`src/components/GenerationProgressHero.tsx`、`src/components/CustomWorldGenerationView.tsx`、`src/components/bark-battle-creation/BarkBattleGeneratingView.tsx` 和玩法链路文档。 +- 验证方式:执行 `npm run test -- src/components/CustomWorldGenerationView.test.tsx src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx`,并用桌面 / 移动端视口检查生成页只出现圆环和当前步骤卡。 +- 关联文档:`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-23 寓教于乐玩法入口收敛为马路街区式横向延展 + +- 背景:参考图和视频表明,寓教于乐板块的图形化入口更接近 Toca Life World 式的“中央马路串联主题小建筑群街区”,而不是乐园分区、环形岛屿或世界球体结构。 +- 决策:后续寓教于乐入口概念图统一采用“横屏 16:9、中央灰蓝色马路贯穿、建筑群沿路两侧聚集、左右边缘持续出画可接下一屏”的结构;马路必须带车道线、斑马线、路口和小汽车,区域通过水果店、画笔工坊、运动馆、音乐剧场、树屋温室等主题小建筑群暗示,不再使用乐园式分区组织。 +- 影响范围:寓教于乐入口概念图、image2 prompt 生成脚本、设计文档、后续横向世界地图探索稿。 +- 验证方式:新生成概念图必须满足“马路是主脊线、建筑群成街区聚合、左右边缘可延展、无品牌乐园元素”四项约束;若图面再跑回环形乐园或漂浮岛,需要重新收敛 prompt。 +- 关联文档:`docs/design/【前端体验】寓教于乐Toca式横向世界地图入口概念图-2026-05-23.md`、`scripts/generate-edutainment-road-town-map-concepts.mjs`、`output/imagegen/edutainment-road-town-map-concepts-20260523/`。 + +## 2026-05-22 敲木鱼图片创作采用三图 image2 链路 + +- 背景:敲木鱼自定义题材只生成中央敲击物时,运行态缺少与新主题匹配的竖屏背景和主题化返回按钮;若直接让背景 prompt 自由发挥,又容易把敲击物或木槌画进背景里。 +- 决策:敲木鱼 `compile-draft` / `regenerate-hit-object` 图片链路固定为三步 image2 edits。第一步调用 VectorEngine `/v1/images/edits` + `gpt-image-2`,以默认木鱼图作为结构和画风参考,用户上传参考图只作为同次请求的新主题参考,结合用户题材关键词或参考图主题生成 `1:1` 绿色背景主体图;`api-server` 先对这张绿幕图执行去绿背景处理并写回 `hitObjectAsset`。第二步必须以第一步抠图完成后的透明敲击物图作为参考,结合用户原始题材生成 `9:16` 背景环境图并写回 `backgroundAsset`,避免背景图继承绿幕或纯绿色画布。第三步必须以去绿后的敲击物主体图和背景环境图为参考,生成 `1:1` 绿色背景返回按钮图,服务端去绿后写回 `backButtonAsset`。三步 prompt 使用 PRD 中固定隐藏关键词,不追加额外 negative prompt;返回按钮只允许参考图约束圆形底色和箭头配色,不允许继承复杂造型、花纹、浮雕边、异形外框或装饰图案,主体视觉尺寸比当前模板再放大约 50%,并带主题色外描边;背景图不得包含敲击物本体或木槌互动物品,返回按钮图不得包含文字、数字、水印或额外 UI 面板。 +- 影响范围:`api-server` 木鱼图片生成编排、`wooden_fish_work_profile.background_asset_json`、`wooden_fish_work_profile.back_button_asset_json`、shared contracts、前端结果页 / 运行态背景与返回按钮展示、敲木鱼 PRD 和平台链路文档。 - 验证方式:执行 `cargo test -p api-server wooden_fish --manifest-path server-rs/Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs/Cargo.toml`、`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run typecheck`。 - 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +## 2026-05-22 敲木鱼敲击物暂不做服务端抠图后处理 + +- 背景:gpt-image-2 偶尔会把木鱼图直接回成带黑底或其它实底背景的 PNG,但服务端抠图后处理在玉米等主题上误伤过主体像素。 +- 决策:敲木鱼 hit object 落盘前暂不做服务端抠图后处理,当前只通过 prompt 强约束真实透明 alpha PNG、透明底、禁止黑底 / 白底 / 棋盘格 / 实底背景。后续若重启后处理,必须先有可验证的保守策略,只能清理画布边缘连通背景,不能抠掉主体内部深色结构或主题细节。 +- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、敲木鱼 PRD、平台链路文档、后续同类 image2 单图资产落盘策略。 +- 验证方式:`cargo test -p api-server wooden_fish --manifest-path server-rs/Cargo.toml`,并在试玩阶段确认主体像素未被后处理误删。 +- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 2026-05-22 敲木鱼背景中央禁主体要写成硬约束 + +- 背景:苹果等主题在试玩时,背景图中央仍可能残留主题主体,说明“外围设计”这种软描述不够。 +- 决策:敲木鱼背景 prompt 必须显式要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子、重复元素或主题主体碎片;主题元素只允许出现在外围氛围。 +- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、敲木鱼 PRD、平台链路文档、后续 image2 背景类玩法 prompt。 +- 验证方式:背景 prompt 单测应包含中央禁区硬约束,试玩图中央不再出现苹果或其它主题主体。 +- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + ## 2026-05-21 外部 API 失败必须 OTLP 上报并落库 - 背景:图片生成等外部供应商调用失败时,仅返回 502/504 或普通日志无法支持后续按 provider、阶段和重试属性聚合排障。 @@ -174,12 +262,12 @@ - 验证方式:执行敲木鱼契约 / module / facade / runtime model / platform entry 定向测试、`npm run typecheck`、`npm run check:encoding`、`npm run check:spacetime-schema`、`cargo check -p api-server --manifest-path server-rs\Cargo.toml`,本地 smoke 使用 mock 短信配置后检查 `/healthz`。 - 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。 -## 2026-05-21 敲木鱼敲击音效复用通用 Vidu 音效链路 +## 2026-05-21 敲木鱼敲击音效当前只接受上传、录音或默认音 -- 背景:敲木鱼创作需要通过“敲击音效”描述生成真实短音频,不能继续由 `spacetime-client` 合成 `/generated-wooden-fish-assets/...` 假路径;同时拼图和抓大鹅音频生成入口仍需保持关闭。 -- 决策:通用 `/api/creation/audio/sound-effect` 提交 Vidu 音效任务;`/api/creation/audio/sound-effect/{task_id}/asset` 只对木鱼 `hit_sound` 目标开放,完成查询、下载、OSS 私有对象、`asset_object` 和 entity binding 写入。木鱼 `compile-draft` / `generate-hit-sound` 在 `api-server` 内复用同一 helper 生成并注入 `hitSoundAsset`,`spacetime-client` 缺少真实 `hitSoundAsset` 时拒绝编译。拼图和抓大鹅相关目标继续返回 `410 Gone`。 +- 背景:敲木鱼按关键词生成的敲击音效约束不够稳定;当前创作阶段需要先关闭提示词生成音效,避免生成结果不符合敲击体验。 +- 决策:通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`。木鱼工作台只支持上传或麦克风录制音频;若用户未提供音频,`api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。`hitSoundPrompt` 只作为历史兼容字段保留,当前创作流程不使用;`spacetime-client` 不得合成 `/generated-wooden-fish-assets/...` 假路径。 - 影响范围:`server-rs/crates/api-server/src/vector_engine_audio_generation.rs`、`server-rs/crates/api-server/src/wooden_fish.rs`、`server-rs/crates/spacetime-client/src/wooden_fish.rs`、`shared-contracts` / `packages/shared` 的 `creationAudio` 契约、敲木鱼 PRD 与平台链路文档。 -- 验证方式:执行 `cargo test -p shared-contracts creation_audio --manifest-path server-rs\Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server disabled_creation_audio_targets_return_gone_except_wooden_fish_sound_effects --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,本地 smoke 检查 `/healthz`;真实生成需同时配置 VectorEngine 与 OSS AccessKey。 +- 验证方式:执行 `cargo test -p shared-contracts creation_audio --manifest-path server-rs\Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server disabled_creation_audio_targets_return_gone_including_wooden_fish_sound_effects --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,本地 smoke 检查 `/healthz`。 - 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。 ## 2026-05-21 敲木鱼默认敲击物使用内置透明 PNG @@ -190,6 +278,14 @@ - 验证方式:默认 `compile-draft` 返回的 `hitObjectAsset.generationProvider` 应为 `bundled-default` 且 `imageSrc=/wooden-fish/default-hit-object.png`;自定义关键词或参考图仍走 image2;前端静态资源可通过 Vite 直接访问。 - 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +## 2026-05-23 敲木鱼创作请求需要独立长超时 + +- 背景:敲木鱼 `createSession` 和 `executeAction` 都会串行等待多段 image2 生成、去绿背景处理和 OSS 落库;共享创作工厂默认 15 秒对这条链路太短,容易让前端先报 `请求超时:15000ms`。 +- 决策:敲木鱼 client 单独配置长等待窗口,同时覆盖会话创建和执行动作请求,不修改共享工厂默认值,避免影响其它轻量创作玩法。 +- 影响范围:`src/services/wooden-fish/woodenFishClient.ts`、`src/services/creation-agent/creationAgentClientFactory.ts`、敲木鱼工作台与生成页请求行为。 +- 验证方式:`npm test -- src/services/wooden-fish/woodenFishClient.test.ts`,并在本地敲木鱼创作时不再提前触发 15 秒超时。 +- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + ## 2026-05-21 RPG publish_world 设定文本以后端草稿真相派生 - 背景:RPG 结果页发布动作只保证提交 `{ action: 'publish_world' }`;旧 agent 会话可能没有 `seed_text`,但 `draft_profile_json` 已经通过 `publish_gate` 并可发布。 @@ -247,10 +343,10 @@ - 验证方式:创作 Tab 中点击汪汪声浪后直接看到内嵌表单,不应再出现单独配置页;发布进入 runtime 后退出应回到创作页的汪汪声浪模板。 - 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`。 -## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏 +## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏(历史) - 背景:拼图与抓大鹅的草稿生成页在移动端同时展示“当前批次”“预计等待”“计时”时,模型执行视角过重,信息也显得散。 -- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。 +- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。2026-05-23 起已被“所有玩法生成页统一圆环主视觉”取代,步骤列表不再作为当前口径。 - 影响范围:`CustomWorldGenerationView`、拼图与抓大鹅创作入口调用处、移动端生成页体验文档。 - 验证方式:拼图与抓大鹅生成页在手机竖屏下只显示等待与计时双栏,步骤卡按顺序滑入;其它未传入隐藏参数的生成页继续保留原批次模块。 - 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。 @@ -351,7 +447,7 @@ - 背景:拼图草稿结果页需要像抓大鹅一样支持 UI 背景生成,但首版只需要作品级/首关背景,不应为图片生成结果新增 SpacetimeDB 表结构。 - 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt`、`uiBackgroundImageSrc`、`uiBackgroundImageObjectKey`;`compile_puzzle_draft` 草稿编译阶段自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc` 或 `uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。 - 2026-05-18 追加:为缩短首版草稿等待,`compile_puzzle_draft` 在首关命名和 `uiBackgroundPrompt` 稳定后并行启动首关关卡图生成与 UI 背景生成;上传主图且关闭 AI 重绘时,并行执行上传图持久化与 UI 背景生成。生成页预计完成时间按 5 分钟展示。 -- 2026-05-21 追加:拼图结果页独立“素材配置”Tab 已移除,UI spritesheet 与关卡纯背景收口到每关图片生成资产包。每次 `gpt-image-2` 预计 90 秒;草稿完整 AI 重绘路径约 298 秒,上传图且关闭 AI 重绘路径跳过首图生成约 208 秒。结果页关卡详情继续复用 `CreativeImageInputPanel`,本次上传/历史选择图优先成为主图卡片,正式图只作为无新参考图时的预览;仅有正式图时仍允许在画面描述框上传多张参考图。 +- 2026-05-21 追加:拼图结果页独立“素材配置”Tab 已移除,UI spritesheet 与关卡纯背景收口到每关图片生成资产包。每次 `gpt-image-2` 预计 90 秒;2026-05-24 起草稿首图生成单独按 4 分钟展示,草稿完整 AI 重绘路径约 448 秒,上传图且关闭 AI 重绘路径跳过首图生成约 208 秒。结果页关卡详情继续复用 `CreativeImageInputPanel`,本次上传/历史选择图优先成为主图卡片,正式图只作为无新参考图时的预览;仅有正式图时仍允许在画面描述框上传多张参考图。 - 影响范围:拼图结果页、拼图运行态背景渲染、拼图 agent action、`module-puzzle` / `spacetime-module` / `spacetime-client` 的拼图关卡 JSON 映射、拼图流程技术文档。 - 验证方式:执行 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`、`cargo test -p api-server puzzle_ui_background --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`。 - 关联文档:`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。 @@ -832,3 +928,11 @@ - 背景:结果页承载预览、修补和发布,若继续放“一次生成”按钮会把初始生成和结果修补职责混在一起。 - 决策:初始三图生成改由 `bark-battle-generating` 独立生成页自动执行,目标槽位只有玩家形象、对手形象和竞技背景;表单术语统一为 `themeDescription`、玩家形象描述和对手形象描述,不再回退 `themePreset`、狗狗皮肤预设或“角色设定”。部分失败也进入结果页。结果页不再提供一次生成按钮,音频配置和排名配置不进入 v1 公开闭环;结果页只保留单槽重试、重新生成和上传。发布时 SpacetimeDB `bark_battle_published_config.config_json` 使用规范化后的最终 `publishedSnapshot`,`published_snapshot_json` 同步保存同一份快照。 - 验证方式:表单提交后进入 `bark-battle-generating`;结果页不会出现一次生成按钮、音频槽、皮肤预设入口或排名配置;Bark Battle 发布后正式 runtime 应读取结果页最终图片素材而不是初始草稿素材。 + +## 2026-05-24 敲木鱼结果页先补录作品信息再试玩 / 发布 + +- 背景:敲木鱼工作台只应保留生成所需输入,作品标题、简介和主题标签适合放在生成草稿后的补录阶段。 +- 决策:敲木鱼的 `workTitle`、`workDescription` 和 `themeTags` 从工作台首屏移到结果页;结果页编辑后在试玩或发布前先调用 `update-work-meta` 写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。 +- 影响范围:`WoodenFishWorkspace`、`WoodenFishResultView`、`PlatformEntryFlowShellImpl`、敲木鱼 PRD 和平台入口链路文档。 +- 验证方式:工作台首屏不再出现标题 / 简介 / 标签输入;结果页修改后点试玩或发布会先写回当前作品信息。 +- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index 3fc9e01c..682af630 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -15,6 +15,46 @@ - 关联:相关文件、文档、提交或 Issue ``` +## 创作卡片点击要直达已有入口表单,别再保留空白入口页 + +- 现象:创作 Tab 模板卡点击后如果仍然停留在创作大厅,或者先进入“X 创作入口”这种空白页,就会让用户多走一层,还可能被错误的 stage 白名单拉回平台。 +- 原因:`/creation/` 一度被接成空白创作入口页,导致 `SelectionStage`、`appPageRoutes` 和卡片点击分流被旧占位 stage 污染。 +- 处理:把 `/creation/` 重新指向已有入口表单 stage,例如 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`;平台壳层和测试同步清理空白入口页相关 helper。 +- 验证:点拼图 / 抓大鹅 / 汪汪声浪卡片后,应看到各自既有工作台内容,例如测试中的 `拼图工作区:missing-session`、`抓大鹅工作区:missing-session` 或 `汪汪声浪配置表单`,并且不再出现“X 创作入口”空白页。 +- 关联:`src/components/platform-entry/platformEntryTypes.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`。 + +## 草稿页未读点不要继续用红色 literal + +- 现象:草稿页底部 Tab 和作品架的未读点视觉上仍像红点,或 glow 仍带红色阴影,和平台暖棕体系不一致。 +- 原因:`platform-nav-unread-dot`、`creation-work-card__unread-dot` 直接写了 `#b64a35` 和 `rgba(239, 68, 68, ...)`,没有收口到统一 token。 +- 处理:未读点颜色统一走 `--platform-unread-dot-fill` / `--platform-unread-dot-glow`,桌面/移动端共用同一口径;不要把红色 literal 再写回样式。 +- 验证:`src/index.test.ts` 断言两个 unread dot block 都只引用未读点 token,不再出现红色 literal 或红色 glow。 +- 关联:`src/index.css`、`src/index.test.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 创作 Tab 模板卡不要复用暗图蒙版参考卡样式 + +- 现象:创作 Tab 两列玩法卡上图能看到,但标题、描述或预计消耗泥点在白底信息区里看不见,或只剩泥点小图标。 +- 原因:旧 `platform-creation-reference-card` 是给暗图蒙版卡用的全局样式,会把卡片及全部子元素强制成白色文字;参考图要求的是“上图 + 下方白底信息区”,继续复用旧类会让白底上的文字消失。 +- 处理:创作 Tab 首屏模板卡使用独立 `creation-template-card`、`creation-template-card__body`、`creation-template-card__title`、`creation-template-card__subtitle` 和 `creation-template-card__cost` 结构,不挂 `platform-creation-reference-card`;旧弹层如果仍是暗图蒙版卡,可以继续保留旧类。 +- 验证:浏览器创作 Tab 中每张卡都应显示标题、描述和“预计消耗 10-20 泥点”;`npm test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx -t "creation start card renders reference-aligned banner and template metadata"` 应通过。 +- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/index.css`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`。 + +## 创作首屏开放态卡片不要再显示左上状态标签 + +- 现象:创作 Tab 的开放态玩法卡左上角会重复显示“可创建”或“可创作”,视觉上比其它状态更吵,还会和封面图抢注意力。 +- 原因:卡片渲染层默认把 `badge` 当成所有状态都要展示的左上角标签,没有区分开放态与非开放态。 +- 处理:开放态卡片不渲染左上标签,仅保留标题、描述和右下角消耗信息;`敬请期待`、`即将开放` 等非开放态标签继续保留。 +- 验证:创作首屏 HTML 中不应包含 `可创建` / `可创作`,但仍应包含 `即将开放` 等非开放态状态。 +- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 发现 / 创作 / 草稿页不要把根内容区再包成全局卡片壳 + +- 现象:发现页、创作页或草稿页根区一旦套回 `platform-page-stage`,页面边缘会立刻变得更厚,频道标签、列表和模板卡的横向空间都被挤窄,看起来像回到了旧全局卡片壳。 +- 原因:`platform-page-stage` 本身是全局内容卡片壳,适合推荐页、我的页和其它页面,但这三页已经有自己的视觉结构;草稿页顶部筛选若继续用旧 `platform-tab`,还会和发现页频道标签不一致。 +- 处理:这三页的根内容区只保留 `platform-remap-surface`,不要再加 `platform-page-stage`;草稿页顶部筛选复用发现页的 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`。 +- 验证:浏览器里这三页的根区应仍保留 `platform-remap-surface`,但不再出现 `platform-page-stage`;草稿页顶部筛选样式应和发现页频道标签一致。 +- 关联:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkTabs.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/index.css`。 + ## SpacetimeDB 入口迁移 helper 合并时不要只保留调用 - 现象:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 或 Jenkins `Genarrative-Stdb-Module-Build` 报 `E0425 cannot find function migrate_rpg_entry_from_old_hidden_default in this scope`,位置在 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 的默认入口配置播种流程。 @@ -39,6 +79,46 @@ - 验证:`cargo test -p api-server puzzle_ui_spritesheet_postprocess_turns_green_screen_transparent --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server puzzle_level_scene_spritesheet_and_background_requests_use_references --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_derived_asset_prompts_match_three_sheet_pipeline --manifest-path server-rs\Cargo.toml`。 - 关联:`server-rs/crates/api-server/src/puzzle/generation.rs`、`server-rs/crates/api-server/src/match3d/works.rs`、`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +## 敲木鱼 hit object 不要只相信透明底 prompt + +- 现象:苹果等主题试玩时,中央敲击物图带明显黑底;背景图中央还可能出现苹果主体,或背景环境图偶发变成纯绿色底,和“中央只叠加 hitObjectAsset”的运行态设定冲突。 +- 原因:gpt-image-2 对“透明底”和“背景只做外围氛围”的遵循不稳定。若 hit object 直接入库,黑底会被当成真实像素展示;若背景 prompt 只有软描述,模型会把主题主体画进中央。第一步为了去背刻意要求绿幕图时,如果第二步参考图或 prompt 没有切断绿幕语义,背景图也可能继承纯绿色画布。 +- 处理:敲木鱼 hit object prompt 固定要求先输出 `1:1` 绿色背景主体图(纯绿色绿幕、单一 `#00FF00` 背景),再由 `api-server` 只对绿幕背景做去绿透明化;不要回到黑底 / 白底 / 透明底 prompt 后再做泛抠图。背景生成必须使用第一步抠图完成后的透明图作为参考图,并在 prompt 中显式禁止继承绿色底色、绿幕底色或纯绿色画布;背景 prompt 还要固定要求中央 40% 主体预留区干净,禁止主题主体、局部特写、轮廓影子、重复元素和主题碎片,只允许外围氛围。 +- 验证:`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`,并用花朵 / 苹果 / 玉米主题跑试玩图确认绿幕被去除、主体未被抠除、背景中央不出现主题主体,背景环境图不再出现纯绿色底。 +- 关联:`server-rs/crates/api-server/src/wooden_fish.rs`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 敲木鱼返回按钮不要让模型自由发挥外圈花纹 + +- 现象:返回按钮试玩图有时会被画成徽章、花盘、浮雕圆牌,甚至出现复杂外圈和装饰花纹,左箭头反而不够突出。 +- 原因:prompt 只说“主题化返回按钮”时,image2 会把参考图里的装饰语言一起学进去;如果没有把形状收束到“标准圆形 + 单个居中左箭头”,模型会优先补造型而不是补图标。 +- 处理:返回按钮生成 prompt 必须只允许参考图约束圆形底色与箭头配色,明确禁止复杂造型、花纹、浮雕边、异形外框和装饰图案,按钮本体固定为标准圆形,视觉尺寸比当前模板再放大约 50%,圆形外沿需要一圈与主题色搭配的干净外描边。 +- 验证:`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`,并重新试玩确认返回按钮只剩圆形底色和中央左箭头。 +- 关联:`server-rs/crates/api-server/src/wooden_fish.rs`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`. + +## 敲木鱼创作生成不要沿用 15 秒会话超时 + +- 现象:敲木鱼工作台点击“生成”后,前端直接提示 `请求超时:15000ms`,但后端和 VectorEngine 未必已经失败。 +- 原因:`createCreationAgentClient` 的 `createSessionTimeoutMs` 默认是 15 秒;敲木鱼创作链路会继续进入生成页并执行多次 image2 edits、去绿背景处理和 OSS 写入,单次请求窗口如果继承共享默认值,会早于业务生成完成被前端中断。 +- 处理:敲木鱼 client 必须单独配置长等待窗口,同时覆盖 `createSessionTimeoutMs` 与 `executeActionTimeoutMs`;不要修改共享默认值影响其它轻量创作 Agent。 +- 验证:`npm run test -- src/services/wooden-fish/woodenFishClient.test.ts`,并在本地触发一次木鱼创作确认不再出现 15 秒前端超时。 +- 关联:`src/services/wooden-fish/woodenFishClient.ts`、`src/services/creation-agent/creationAgentClientFactory.ts`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`。 + +## 敲木鱼创作“卡住”先查 2xx 慢请求 + +- 现象:敲木鱼工作台点击生成后长时间停留在生成页,看起来像卡住;`api-server` 日志可能出现 `/api/creation/wooden-fish/sessions/{sessionId}/actions` 的 `2xx` 慢请求,耗时可达数分钟,例如 `latency_ms=525473`。 +- 原因:当前 `compile-draft` 是同步 action,会串行等待敲击物、背景环境图、返回按钮图三次 image2 edits、去绿处理、OSS 写入和 SpacetimeDB 草稿写回;提示词生成音效已关闭,不应作为生成阶段。 +- 处理:先确认日志中该 action 是不是最终 200;若是 200 慢请求,不要优先排查 WebSocket 或 SpacetimeDB procedure。前端生成页进度必须按“整理草稿 -> 生成敲击物 -> 生成背景环境图 -> 生成返回按钮图 -> 写入正式草稿”展示,并在未收到 action 回包前保持等待态,不宣称完成。 +- 验证:`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts -t "wooden fish"`,并观察木鱼生成页在 5 分钟以上等待时仍停留在合理阶段。 +- 关联:`src/services/miniGameDraftGenerationProgress.ts`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + +## 敲木鱼点击生成出现 SpacetimeDB procedure 超时先查版本错配 + +- 现象:敲木鱼创作时点击“生成”,前端提示 `SpacetimeDB procedure 调用超时`,但服务端日志更早出现 `Failed to BSATN deserialize procedure return value` 或类似反序列化错误。 +- 原因:本机 `spacetime` CLI / standalone 版本与 `server-rs/Cargo.toml` 锁定的 `spacetimedb` 版本不一致时,procedure 返回值会在宿主侧反序列化失败,api-server 继续等待就表现成调用超时。若旧 standalone 进程还在复用,也会把这个错配继续带进新一轮创作。 +- 处理:先用 `spacetime --version` 确认 `spacetimedb tool version`,再和 `server-rs/Cargo.toml` 的 `spacetimedb = "..."` 对齐;必要时执行 `spacetime version install && spacetime version use `,然后重启 `npm run dev:spacetime`。当前 dev 脚本会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免继续复用旧宿主。 +- 验证:`spacetime --version` 输出与 `server-rs/Cargo.toml` 一致,`http://127.0.0.1:3101/v1/ping` 正常,`npm run test -- scripts/dev.test.ts` 通过,敲木鱼创作点击生成不再卡在 procedure timeout。 +- 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`server-rs/Cargo.toml`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 + ## 拼图 UI spritesheet 运行态不要二次包圆底或拉伸比例 - 现象:拼图运行态左上返回和右上设置按钮外面出现白色圆圈;底部“提示 / 原图 / 冻结”三枚素材被压扁、拉宽或拉成正圆,和图集原始按钮比例不一致。 @@ -1252,6 +1332,14 @@ - 验证:Jenkins 目标机日志不再出现 `unexpected argument '-y'`、`unknown command name for spacetimedb-update multicall binary`,后续应继续检查 `bin/current/spacetimedb-cli` 和 `bin/current/spacetimedb-standalone` 是否生成。 - 关联:`scripts/prepare-server-provision-tools.sh`、`jenkins/Jenkinsfile.production-server-provision`。 +## 清库重建后先查 schema 兼容再重启 + +- 现象:`npm run dev -- --clear-database --no-interactive` 之后,api-server 仍在 `GET /api/creation-entry/config` 或订阅恢复阶段报 `No such procedure` / schema guard 失败。 +- 原因:本地重建只会重发当前 `spacetime-module`,不会自动修正旧迁移 JSON 的字段兼容;如果 `migration.rs` 没把新字段补成 `None` / 默认值,清库后重建仍会卡在 schema 同步。 +- 处理:先让 `server-rs/crates/spacetime-module/src/migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和生成绑定对齐,再执行清库重建。 +- 验证:`npm run check:spacetime-schema` 先通过,再重启 `npm run dev -- --clear-database --no-interactive`,最后检查 `/v1/ping`、`/healthz` 和 `GET /api/creation-entry/config`。 +- 关联:`server-rs/crates/spacetime-module/src/migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`scripts/dev.mjs`。 + ## QQ 浏览器发现页推荐封面全不显示先查 aspect-ratio 兜底 - 现象:发现页的“推荐”子频道作品卡标题、作者和数据正常,但所有封面图不显示,常见于 QQ 浏览器 / X5 等旧移动内核。 @@ -1260,13 +1348,13 @@ - 验证:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`、`npm run check:encoding`。 - 关联:`src/index.css`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/rpgEntryWorldPresentation.ts`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 -## 生成中草稿刷新后不要只恢复作品架遮罩 +## 生成中草稿刷新后不要复用旧 updatedAt 当展示起点 -- 现象:拼图或抓大鹅草稿生成中刷新网页后,作品架卡片能显示等待遮罩,但点击卡片会走普通草稿恢复,可能进入空白结果页或未完成工作区。 -- 原因:前端只把内存 notice 当作“生成中点击恢复”的判断条件,没有把后端摘要里的 `generationStatus=generating` 纳入同一路径。 -- 处理:打开草稿时把持久化 `generationStatus=generating` 等同于生成中 notice,恢复对应玩法生成进度页;恢复计时使用作品摘要 `updatedAt` 推导 `startedAtMs`。 -- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"`。 -- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +- 现象:拼图或抓大鹅草稿生成中刷新网页后,作品架卡片能显示等待遮罩,但进入生成页时总进度首帧直接跳到 80%+,看起来像已经跑了一大半。 +- 原因:前端只把持久化 `generationStatus=generating` 当作恢复生成页的条件,但恢复展示时仍沿用了作品摘要 `updatedAt` 作为伪 `startedAtMs`;同时拼图总进度又把后端 `progressPercent` 直接当作 floor,导致 `86%` 之类未到首个里程碑的会话一进页就抬到 80%+。 +- 处理:恢复生成中的草稿时,展示起点改用“进入生成页的当前时间”;`updatedAt` 只保留给作品架排序和摘要,不再参与生成页假进度起算。拼图总进度还要忽略 `88` 以下的后端进度 floor,拼图保留后端里程碑推进,抓大鹅等非拼图玩法则从 `0%` 平滑起步,避免刚进页就看到 4% / 88% / 80%+。 +- 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"`、`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts -t "match3d draft generation starts total progress from zero"`。 +- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`src/services/miniGameDraftGenerationProgress.ts`、`docs/【玩法创作】拼图生成页进度口径-2026-05-23.md`。 ## 汪汪声浪草稿试玩不要写正式 run @@ -1324,6 +1412,18 @@ - 验证:`cargo check -p api-server --manifest-path server-rs/Cargo.toml`;契约测试应断言前端 JSON 自带的 `hitObjectAsset` 会被忽略,spacetime-client 定向测试应断言缺少服务端注入的真实 `hitObjectAsset` 时不能编译;浏览器 Network 中 generated 图片应先换签,签名 URL 指向已存在对象。 - 关联:`server-rs/crates/api-server/src/wooden_fish.rs`、`server-rs/crates/spacetime-client/src/wooden_fish.rs`、`src/components/ResolvedAssetImage.tsx`、`src/services/assetReadUrlService.ts`。 +## 生成页背景视频要固定全屏并显式触发播放 + +- 现象:生成页明明带了 `media/create_bg_video.mp4`,但移动端或某些内核里只看到静态首帧,或视频层跟着局部容器滚动,被白色面板压住后看起来像没加载。 +- 原因:仅靠 `autoPlay/loop/muted/playsInline` 并不稳定;视频如果仍挂在局部容器里,还会被页面面板和遮罩吞掉。某些浏览器初始化后也会停在 `paused=true`。 +- 处理:背景视频必须放到 `fixed inset-0` 的全屏底层容器里,外层页面用 `isolate` / 透明底控制叠层;挂载后显式尝试 `play()`,并在 `loadeddata`、`canplay` 和页面聚焦时再次触发,避免只停首帧。 +- 验证:移动端视口检查视频 `rect` 应覆盖整个视口,`paused` 应最终变为 `false`,`currentTime` 应持续前进。 +- 关联:`src/components/GenerationProgressHero.tsx`、`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`。 + +2026-05-24 补充:`GenerationPageBackdrop` 不要通过 portal 挂到 `document.body`。body 级 fixed 背景会逃离生成页自己的 stacking context,即使业务内容有局部 `z-10`,真实浏览器里也可能把整页 UI 压住。背景视频应作为生成页根容器子节点保留 `fixed inset-0 z-0`,生成页内容保持 `relative z-10`;相关测试应同时断言背景容器低层级、生成页根容器高层级,以及视频节点仍在生成页 DOM 内部。视觉调整时还要记住:空心圆环的中心块要抽掉,时间卡与总进度标题都应缩小,不要让生成页再回到“纯色底 + 大字号说明卡”的状态。顶部返回和右上状态也不能沿用 `text-lg` / `sm:text-2xl` 这类展示级字号;当前步骤名、步骤状态和底部玩法信息标题要维持普通 UI 字号档位,优先保持 `text-xs` 到 `text-sm` 区间。 + +2026-05-24 补充:生成页“预计等待 / 已耗时”卡片本身已经有标签,传给 `GenerationProgressHero` 的值只能是纯时间,例如 `4 分钟`、`1 分 15 秒`,不要再拼接“预计还需”或“已耗时”;两张时间卡也要和当前步骤卡一样保持半透明。拼图总进度初始帧必须允许显示 `0%`,不要再用 `Math.max(1, nextProgress)` 之类的保护把启动态抬到 `1%`。 + ## `dev:spacetime` 启动后 3101 又断开先查 publish 是否被 spacetime.json 干扰 - 现象:浏览器报 `Failed to initiate WebSocket connection`,目标为 `ws://127.0.0.1:3101/v1/database//subscribe`,端口检查发现 `3101` 没有长期监听;手动运行 `npm run dev:spacetime` 可看到 standalone 短暂启动后退出,发布阶段报 `No database target matches ''`。 @@ -1340,6 +1440,14 @@ - 验证:`npm run test -- scripts/dev.test.ts`;重新运行 `npm run dev` 后 api-server 启动日志不再出现上述 subscribe 401,`/healthz` 返回 200。 - 关联:`scripts/dev.mjs`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 +## 创作作品架或公开列表异常先查本地 SpacetimeDB schema 漂移 + +- 现象:本地 `http://127.0.0.1:3000/` 启动后,`api-server` 日志反复出现 `Host returned error when processing subscription query: no such table: puzzle_gallery_card_view`;或创作中心草稿 / 已发布作品整块消失,`GET /api/creation-entry/config` 返回 `502` 且 details 为 `No such procedure`。 +- 原因:本地 `.env.local` 或 `spacetime.local.json` 指向的 SpacetimeDB 库没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库;例如旧 `xushi-p4wfr` 库缺 `get_creation_entry_config` / `puzzle_gallery_card_view`,但当前代码的 `spacetime-client` 启动时会长期订阅这些公开 read model。 +- 处理:先用 `spacetime sql "SELECT * FROM puzzle_gallery_card_view LIMIT 1" --server http://127.0.0.1:3101` 确认目标库是否有当前 view;若只是本地验证,可用 gitignored 的 `spacetime.local.json` 指向可发布且已包含当前 schema 的库,例如 `{"database":"genarrative-dev-codex"}`。该 JSON 必须无 UTF-8 BOM,否则 `scripts/dev.mjs` 会忽略它。修改后用 `npm run dev:api-server -- --database --spacetime-port 3101 --api-port 8082 --no-interactive` 重启。 +- 验证:`curl.exe -i http://127.0.0.1:8082/healthz` 返回 `200`;`curl.exe -i http://127.0.0.1:8082/api/runtime/puzzle/gallery` 返回 `200`;浏览器打开 `http://127.0.0.1:3000/` 无 `puzzle_gallery_card_view` 控制台或后端日志错误。 +- 关联:`scripts/dev.mjs`、`server-rs/crates/spacetime-client/src/lib.rs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 + ## 创作作品架消失先查入口配置 procedure 与本地库权限 - 现象:寓教于乐或创作中心下草稿 / 已发布作品突然整块消失,`GET /api/creation-entry/config` 返回 `502`,details 中为 `No such procedure`。 diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts index 43bc2e03..00a2a4df 100644 --- a/apps/admin-web/src/api/adminApiTypes.ts +++ b/apps/admin-web/src/api/adminApiTypes.ts @@ -157,6 +157,9 @@ export interface AdminCreationEntryTypeConfigPayload { visible: boolean; open: boolean; sortOrder: number; + categoryId: string; + categoryLabel: string; + categorySortOrder: number; updatedAtMicros: number; } @@ -169,6 +172,9 @@ export interface AdminUpsertCreationEntryTypeConfigRequest { visible: boolean; open: boolean; sortOrder: number; + categoryId: string; + categoryLabel: string; + categorySortOrder: number; } export interface AdminUpsertProfileRedeemCodeRequest { diff --git a/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx b/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx index 2b1b2995..fb817c65 100644 --- a/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx +++ b/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx @@ -27,6 +27,9 @@ export function AdminCreationEntrySwitchPage({ const [visible, setVisible] = useState(true); const [open, setOpen] = useState(true); const [sortOrder, setSortOrder] = useState('30'); + const [categoryId, setCategoryId] = useState('recent'); + const [categoryLabel, setCategoryLabel] = useState('最近创作'); + const [categorySortOrder, setCategorySortOrder] = useState('10'); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [listErrorMessage, setListErrorMessage] = useState(''); @@ -82,6 +85,9 @@ export function AdminCreationEntrySwitchPage({ visible, open, sortOrder: parseInteger(sortOrder), + categoryId: categoryId.trim(), + categoryLabel: categoryLabel.trim(), + categorySortOrder: parseInteger(categorySortOrder), }); const nextEntries = sortEntries(response.entries); setEntries(nextEntries); @@ -105,6 +111,9 @@ export function AdminCreationEntrySwitchPage({ setVisible(entry.visible); setOpen(entry.open); setSortOrder(String(entry.sortOrder)); + setCategoryId(entry.categoryId); + setCategoryLabel(entry.categoryLabel); + setCategorySortOrder(String(entry.categorySortOrder)); } return ( @@ -189,6 +198,32 @@ export function AdminCreationEntrySwitchPage({ /> +
+ + +
+ + + {errorMessage ? (
{errorMessage} @@ -211,6 +246,7 @@ export function AdminCreationEntrySwitchPage({ 入口 展示 开放 + 分类 排序 @@ -228,6 +264,7 @@ export function AdminCreationEntrySwitchPage({ {entry.visible ? '是' : '否'} {entry.open ? '是' : '否'} + {entry.categoryLabel || entry.categoryId} {entry.sortOrder} ))} diff --git a/docs/README.md b/docs/README.md index 36689c78..030744a9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,8 @@ - [运营查询](./operations/README.md):任务、领奖、钱包对账等后台核查查询。 - [PRD](./prd/README.md):产品需求与阶段计划;参考 MOKU / 幕间类 AI 文游的陶泥儿 `text-game` 模板口径见 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md),视觉小说模板 TXT 玩法平台化接入口径见 [AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md](./prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md),创意互动内容 Agent Phase 1 的 LangChain-Rust PoC、拼图闭环和并行任务拆分见 [CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md](./prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md),幸存者类模板闭环见 [AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md](./prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md),后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md),方洞挑战创作、发布与试玩闭环见 [AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md](./prd/AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md),跳一跳俯视角玩法模板 PRD 见 [【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md](./prd/%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E8%B7%B3%E4%B8%80%E8%B7%B3%E4%BF%AF%E8%A7%86%E8%A7%92%E7%8E%A9%E6%B3%95%E6%A8%A1%E6%9D%BFPRD-2026-05-19.md)。 +拼图生成页步骤真进度、步骤内假进度和精简展示口径见 [【玩法创作】拼图生成页进度口径-2026-05-23.md](./%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E6%8B%BC%E5%9B%BE%E7%94%9F%E6%88%90%E9%A1%B5%E8%BF%9B%E5%BA%A6%E5%8F%A3%E5%BE%84-2026-05-23.md)。 + 生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md)。 SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。 diff --git a/docs/design/【前端体验】寓教于乐Toca式横向世界地图入口概念图-2026-05-23.md b/docs/design/【前端体验】寓教于乐Toca式横向世界地图入口概念图-2026-05-23.md new file mode 100644 index 00000000..fa0de027 --- /dev/null +++ b/docs/design/【前端体验】寓教于乐Toca式横向世界地图入口概念图-2026-05-23.md @@ -0,0 +1,48 @@ +# 寓教于乐 马路街区式横向世界入口概念图 + +更新时间:`2026-05-23` + +## 背景 + +寓教于乐板块需要继续探索图形化玩法入口。前一轮“乐园地图 / 世界地图”方向已被证明不够贴近参考图,本轮进一步收敛为“中央马路串联主题小建筑群街区”的结构,让每一屏都像一个可横向滑动的儿童小镇街区。 + +## 参考结论 + +从参考图和视频里提炼出的关键结构是: + +1. 每一屏都是一组主题小建筑群,不是稀疏乐园点位。 +2. 中央马路是主路径,汽车沿路通过,边缘继续延伸到下一屏。 +3. 建筑群贴着道路两侧聚合,形成清晰街区,而不是围绕中央广场或环形路径展开。 +4. 左右两边都要有明确“可继续探索”的出画感,方便后续做横向滑动世界。 + +## 目标 + +- 视觉上像可滑动的儿童街区地图。 +- 每一屏都能作为独立探索单元,并且能自然接到前后相邻屏。 +- 保持寓教于乐既有的卡通绘本风,不借用真实品牌乐园或现成 IP。 +- 适合后续叠加入口按钮、焦点框和中文标题。 + +## 本次概念方向 + +1. 识物认知主街 +2. 绘画创作工坊街 +3. 运动音乐街区 +4. 自然探索实验大道 + +## 推荐方向 + +优先推荐 `绘画创作工坊街` 与 `运动音乐街区`。这两条在当前批次里最接近“中央马路 + 两侧主题小建筑群 + 左右可延展”的参考结构。 + +## 生图脚本 + +- 生成脚本:`scripts/generate-edutainment-road-town-map-concepts.mjs` +- 输出目录:`output/imagegen/edutainment-road-town-map-concepts-20260523/` +- 风格参考:`public/child-motion-demo/picture-book-grass-stage.png` + +## 说明 + +本次产物是设计概念稿,不直接进入正式资源目录。后续如果继续收敛,可以以 `城市公园脊` 为母版,向左、向右补相邻屏幕的区域内容。 + +## 当前结论 + +乐园分区结构可以抛弃,后续所有概念都优先按“马路街区式一屏一屏延展”的结构继续迭代。 diff --git a/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md b/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md index a660755c..238a200b 100644 --- a/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md +++ b/docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md @@ -14,7 +14,7 @@ 创作入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态 -> 公开详情/分享 ``` -首版默认屏幕中央展示内置卡通透明敲击物图案 `/wooden-fish/default-hit-object.png`。玩家点击运行态非功能区时触发一次敲击:播放敲击音效、敲击物图案执行被敲击动画,并在敲击物上方随机飘出一条祝福词。顶部计数器只在某条祝福词首次出现时创建,之后该词每次出现都累加。计数仅属于当前单次 run,不进入账号长期账本。 +首版默认屏幕中央展示内置卡通透明敲击物图案 `/wooden-fish/default-hit-object.png`。玩家点击运行态非功能区时触发一次敲击:播放敲击音效、敲击物图案执行被敲击动画,并在敲击物上方随机飘出一条祝福词。顶部只展示总数记录;子项计数收纳到总数卡片下方的折叠面板中,总数卡片点击后展开各子项计数,词条在面板中预置显示,未出现时初始值为 0,点击面板外收起。计数仅属于当前单次 run,不进入账号长期账本。 ## 2. 模板定位 @@ -86,9 +86,9 @@ WF-* - `slotId=hit-sound` - `slotType=hit-sound-audio` - `slotName=敲击音效` - - 来源:`hitSoundPrompt` 生成,或上传/麦克风录制音频 + - 来源:用户上传/麦克风录制音频,或使用默认木鱼音 - 写回字段:`hitSoundAsset` - - 描述生成能力:复用通用创作音频接口 `/api/creation/audio/sound-effect` 的 VectorEngine Vidu 音效生成与 OSS 持久化链路 + - 默认兜底:`/wooden-fish/default-hit-sound.mp3` - API 命名空间: - `/api/creation/wooden-fish/...` - `/api/runtime/wooden-fish/...` @@ -113,68 +113,81 @@ WF-* 必填字段: 1. `templateId = "wooden-fish"`; -2. `workTitle`:作品标题; -3. `hitObjectPrompt`:用户想敲的对象关键词或描述,默认“默认敲击物图案,圆润木质质感,透明背景”; -4. `floatingWords[]`:祝福词,最多 8 条,不填或清空时使用默认祝福词。 +2. `hitObjectPrompt`:用户想敲的对象关键词或描述,默认“默认敲击物图案,圆润木质质感,透明背景”; +3. `floatingWords[]`:祝福词,最多 8 条,不填或清空时使用默认祝福词。 可选字段: -1. `workDescription`:作品简介; -2. `themeTags[]`:最多 6 个标签; -3. `hitObjectReferenceImageSrc`:上传或历史图引用,只能作为 image2 参考,不可直接进入运行态; -4. `hitSoundPrompt`:生成音效描述; -5. `hitSoundAsset`:用户上传或录音产生的音频资产。 +1. `hitObjectReferenceImageSrc`:上传或历史图引用,只能作为 image2 参考,不可直接进入运行态; +2. `hitSoundPrompt`:历史兼容字段,当前创作流程不再使用; +3. `hitSoundAsset`:用户上传、录音或默认音频资产。 -默认祝福词: +结果页补录字段: + +1. `workTitle`:作品标题,默认值在结果页可编辑; +2. `workDescription`:作品简介; +3. `themeTags[]`:最多 6 个标签,样式对齐拼图结果页标签编辑器。 + +创作界面默认祝福词: ```text 幸运 -健康 -财富 -姻缘 -幸福 -事业 -成功 -功德 ``` +用户可通过加号继续新增 7 个词条,总数最多 8 条。新增词条右侧提供减号 / 删除小按钮;默认的第一个词条保留为普通输入格。 + `floatingWords[]` 保存词条名本身,不保存 `+1` 后缀;运行态每次敲击时再把飘字展示为“词条+1”。 ## 6. 生成规则 -### 6.1 敲击物图案与背景环境图 +### 6.1 敲击物图案、背景环境图与返回按钮图 -默认模板在用户未自定义关键词且未上传参考图时,`compile-draft` 使用内置透明 PNG `/wooden-fish/default-hit-object.png` 写回 `hitObjectAsset`,`generationProvider="bundled-default"`。这张图来自 image2 对原始参考图的卡通风格化重绘,固定为模板默认资源,避免默认关键词在每次生成时改变造型。即使使用内置默认敲击物,首版仍需要生成 `backgroundAsset`,背景环境图使用默认敲击物作为主题和画风参考。 +默认模板在用户未自定义关键词且未上传参考图时,`compile-draft` 使用内置透明 PNG `/wooden-fish/default-hit-object.png` 写回 `hitObjectAsset`,`generationProvider="bundled-default"`。这张图来自 image2 对原始参考图的卡通风格化重绘,固定为模板默认资源,避免默认关键词在每次生成时改变造型。即使使用内置默认敲击物,首版仍需要生成 `backgroundAsset` 与 `backButtonAsset`,背景环境图和主题返回按钮图都使用默认敲击物作为主题和画风参考。 -用户输入自定义关键词、上传参考图,或在结果页主动重生成敲击物时,`compile-draft` 与 `regenerate-hit-object` 必须先为敲击物图案生成 image2 单图资产,再基于新敲击物图案生成背景环境图,并由 `api-server` 注入写回 `hitObjectAsset` 与 `backgroundAsset`。前端 action 请求不得自带 `hitObjectAsset` 或 `backgroundAsset` 短路生成。如果用户上传参考图,后端只能把该图作为 image2 参考图或主题参考;运行态不得直接使用上传图。 +用户输入自定义关键词、上传参考图,或在结果页主动重生成敲击物时,`compile-draft` 与 `regenerate-hit-object` 必须先为敲击物图案生成 image2 单图资产,再基于新敲击物图案生成背景环境图,最后基于去绿后的敲击物主体和背景环境图生成主题返回按钮图,并由 `api-server` 注入写回 `hitObjectAsset`、`backgroundAsset` 与 `backButtonAsset`。前端 action 请求不得自带 `hitObjectAsset`、`backgroundAsset` 或 `backButtonAsset` 短路生成。如果用户上传参考图,后端只能把该图作为 image2 参考图或主题参考;运行态不得直接使用上传图。 敲击物图案生成流程固定为: 1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`; 2. multipart 参考图固定包含默认木鱼图 `/wooden-fish/default-hit-object.png`,作为基础结构和画风参考; 3. 若用户上传参考图,该图只作为新主题参考追加到同一次 image2 edits 请求,不直接进入运行态; -4. 尺寸固定 `1:1`,透明底; +4. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),背景为单一纯绿色 `#00FF00`,并显式禁止黑底、白底、棋盘格、纸板底或任何其它实底背景; 5. 提示词严格使用: ```text -生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。 +生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。尺寸1:1,先输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,主体边缘必须干净,不要直接输出透明底。随后由服务端对绿色背景主体图做抠图去除绿色背景。最终结果只保留单个敲击物图案,禁止黑底、白底、棋盘格、纸板底或任何实底背景;主体本身不要使用与绿幕接近的纯绿色,若新主题天然包含绿色,请改用偏深、偏黄或偏蓝的绿色并与绿幕清晰区分。 新主题为:(用户提供参考图或用户输入关键词) ``` +敲击物图案落盘前,`api-server` 必须只对第一步生成的纯绿色绿幕背景执行去绿处理,把绿色背景转成真实透明 alpha PNG;不得对黑底、白底或其它未知实底执行泛抠图,避免误伤玉米等主体像素。去绿处理必须保留主体内部深色结构和主题细节。 + 背景环境图生成流程固定为: 1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`; -2. multipart 参考图固定为第一步新生成的敲击物图案;默认未生成新敲击物时使用内置默认敲击物图案; +2. multipart 参考图固定为第一步敲击物图案抠图完成后的透明图;默认未生成新敲击物时使用内置默认敲击物图案的透明兜底图; 3. 尺寸固定竖屏 `9:16`; -4. 背景环境图只适配新敲击物主题和画风,背景中不得包含新敲击物本体,也不得增加木槌互动物品; +4. 背景环境图只适配新敲击物主题和画风,背景中不得包含新敲击物本体,也不得增加木槌互动物品;中央主体预留区必须保持干净,画面中央 40% 区域禁止出现主题主体、主体局部特写、主体轮廓影子、重复元素或主题主体的局部碎片; 5. 提示词严格使用: ```text -生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。 +生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。尺寸竖屏9:16。参考图必须是第一步敲击物抠图完成后的透明图,不继承任何绿色底色、绿幕底色或纯绿色画布,并要求最终输出完整不透明的背景环境图。中央主体预留区必须保持干净,画面中央 40% 区域禁止出现主题主体、主体局部特写、主体轮廓影子、重复元素或主题主体的局部碎片;主题元素只允许出现在外围氛围,不得把主题物品画在画面中央,也不要把主题物品作为背景中心装饰。 主题为:(用户提供参考图或用户输入关键词) ``` -落库链路固定为:`api-server` 调用 VectorEngine `/v1/images/edits` -> 服务端上传 OSS 私有对象 -> `confirm_asset_object` 登记资产对象 -> `bind_asset_object_to_entity` 绑定到 `entityKind='wooden_fish_work'`。敲击物绑定 `slot='hit_object'`、`assetKind='wooden_fish_hit_object'`,背景绑定 `slot='background'`、`assetKind='wooden_fish_background'`。写回时把 `legacyPublicPath` 分别写入 `hitObjectAsset.imageSrc` 与 `backgroundAsset.imageSrc`。不得只拼 `/generated-wooden-fish-assets/...` 占位路径;前端会对 generated legacy path 走 `/api/assets/read-url` 换签,OSS 中没有真实对象时图片无法显示。 +返回按钮图生成流程固定为: + +1. 调用 VectorEngine `/v1/images/edits`,模型固定为 `gpt-image-2`; +2. multipart 参考图固定包含第一步去除绿色背景后的敲击物主体图,以及第二步生成的背景环境图; +3. 尺寸固定 `1:1`,必须输出绿色背景主体图(纯绿色绿幕),后端落库前执行同一套去绿背景处理; +4. 按主题、画风、材质和配色生成左上角返回按钮图,但参考图只用于约束圆形底色和中央左箭头的颜色搭配,不得借鉴复杂造型、花纹、浮雕边、异形外框或装饰图案;按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个清晰左箭头或返回箭头,不得包含文字、数字、水印、额外 UI 面板、木槌或敲击道具; +5. 提示词严格使用: + +```text +生成敲木鱼左上角返回按钮图。要求以参考图-去除绿色背景后的敲击物主体和背景环境图为主题、画风、材质和配色参考,但参考图只用来约束圆形底色和中央左箭头的颜色搭配,不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案。按钮必须始终是标准圆形,整体像单个圆形图标,按钮主体在画布中的视觉尺寸比当前模板再放大约 50%,圆心居中,圆形外沿加一圈和主题色搭配的干净外描边,让它更像一个按钮,但仍然只保留一个清晰、简洁、居中的向左返回箭头,不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具。尺寸1:1,输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影。按钮主体边缘干净,后续由服务端扣除绿色背景;按钮底色不要使用与绿幕接近的纯绿色,若主题天然包含绿色,请仅在圆形底色上使用偏深、偏黄或偏蓝的主题绿色,并用更高对比的箭头颜色区分。 +主题为:(用户提供参考图或用户输入关键词) +``` + +落库链路固定为:`api-server` 调用 VectorEngine `/v1/images/edits` -> 服务端上传 OSS 私有对象 -> `confirm_asset_object` 登记资产对象 -> `bind_asset_object_to_entity` 绑定到 `entityKind='wooden_fish_work'`。敲击物绑定 `slot='hit_object'`、`assetKind='wooden_fish_hit_object'`,背景绑定 `slot='background'`、`assetKind='wooden_fish_background'`,返回按钮绑定 `slot='back_button'`、`assetKind='wooden_fish_back_button'`。写回时把 `legacyPublicPath` 分别写入 `hitObjectAsset.imageSrc`、`backgroundAsset.imageSrc` 与 `backButtonAsset.imageSrc`。不得只拼 `/generated-wooden-fish-assets/...` 占位路径;前端会对 generated legacy path 走 `/api/assets/read-url` 换签,OSS 中没有真实对象时图片无法显示。 默认图案要求: @@ -188,18 +201,18 @@ WF-* 音效统一写回 `hitSoundAsset`。 -生成或写回规则: +写回规则: 1. 若 payload 已包含上传/录音音频资产,`compile-draft` 跳过音效生成,直接持久化该资产; -2. 若 payload 只包含 `hitSoundPrompt`,`api-server` 复用通用创作音频能力提交 VectorEngine Vidu `sound_effect` 任务,轮询生成结果,下载音频,写入 OSS 私有对象,登记 `asset_object`,并绑定到 `entityKind='wooden_fish_work'`、`slot='hit_sound'`、`assetKind='wooden_fish_hit_sound'`,最后注入 `hitSoundAsset`; -3. 若两者都没有,后端生成默认“清脆短促木鱼敲击声”; +2. 若 payload 已上传或录制音频,则直接写回 `hitSoundAsset`; +3. 若两者都没有,后端写回默认木鱼音 `/wooden-fish/default-hit-sound.mp3`; 4. 音效资产必须包含可播放地址、对象键、asset object id、来源和可选时长; -5. 通用创作音频接口对 `wooden_fish` 的 `hit_sound` 目标不得返回 `410 Gone`,对应 `storagePrefix='wooden_fish_assets'`; -6. `spacetime-client` 不得自行合成 `/generated-wooden-fish-assets/...` 音效占位路径,缺少真实 `hitSoundAsset` 时必须拒绝编译。 +5. 通用创作音频接口当前对 `wooden_fish` 的 `hit_sound` 目标返回 `410 Gone`,不得在创作流程中按提示词生成音效; +6. `spacetime-client` 不得自行合成 `/generated-wooden-fish-assets/...` 音效占位路径;缺少真实 `hitSoundAsset` 时应使用默认木鱼音兜底展示与播放。 ### 6.3 封面 -首版封面使用 `hitObjectAsset.imageSrc` 作为 `coverImageSrc`。背景环境图不作为封面图,不单独新增第三次图片生成。 +首版封面使用 `hitObjectAsset.imageSrc` 作为 `coverImageSrc`。背景环境图与返回按钮图不作为封面图。 ## 7. 契约草案 @@ -213,13 +226,14 @@ WF-* 6. `themeTags[]`; 7. `hitObjectPrompt`; 8. `hitObjectReferenceImageSrc`; -9. `hitSoundPrompt`; +9. `hitSoundPrompt`,历史兼容字段,当前创作流程恒为 `null`; 10. `floatingWords[]`; 11. `hitObjectAsset`; 12. `backgroundAsset`; -13. `hitSoundAsset`; -14. `coverImageSrc`; -15. `generationStatus`。 +13. `backButtonAsset`; +14. `hitSoundAsset`; +15. `coverImageSrc`; +16. `generationStatus`。 `WoodenFishImageAsset` 至少包含: @@ -238,7 +252,7 @@ WF-* 2. `audioSrc`; 3. `audioObjectKey`; 4. `assetObjectId`; -5. `source = generated | uploaded | recorded | placeholder`; +5. `source = uploaded | recorded | bundled-default`; 6. `prompt`; 7. `durationMs`。 @@ -277,7 +291,6 @@ GET /api/runtime/wooden-fish/gallery/{publicWorkCode} ```text compile-draft regenerate-hit-object -generate-hit-sound replace-hit-sound update-work-meta update-floating-words @@ -289,19 +302,21 @@ finish `compile-draft` 是长耗时动作。前端进入生成页后应展示可恢复进度;如果请求失败,标记失败前必须复读 session,确认后端是否已经生成并写回草稿。 +敲木鱼创作请求在前端必须使用长等待窗口,避免 `createSession` 或 `executeAction` 仍沿用共享创作工厂默认的 15 秒超时。因为 `compile-draft` 会串行等待敲击物、背景、返回按钮三次 image2 和 OSS 落库,木鱼 client 需要单独配置与整条 image2 链路匹配的超时。本地测试中该 action 可能达到数分钟级;生成页进度必须按“整理草稿 -> 生成敲击物 -> 生成背景环境图 -> 生成返回按钮图 -> 写入正式草稿”展示,不展示“提示词生成音效”阶段,因为当前木鱼音效只支持上传、录音或默认音。 + ## 9. SpacetimeDB 表和 view 新增表: 1. `wooden_fish_agent_session`; -2. `wooden_fish_work_profile`,其中 `background_asset_json` 保存背景环境图资产快照; +2. `wooden_fish_work_profile`,其中 `background_asset_json` 保存背景环境图资产快照,`back_button_asset_json` 保存主题返回按钮图资产快照; 3. `wooden_fish_runtime_run`; 4. `wooden_fish_event`。 新增 view: 1. `wooden_fish_gallery_card_view`:公开列表卡片投影,只暴露已发布作品; -2. `wooden_fish_gallery_view`:公开详情兼容投影,包含图案、音效和祝福词配置。 +2. `wooden_fish_gallery_view`:公开详情兼容投影,包含图案、背景、返回按钮、音效和祝福词配置。 新增或调整表、procedure、view 后必须同步 `migration.rs`、后端表目录、生成 bindings,并执行 `npm run check:spacetime-schema`。 @@ -322,11 +337,11 @@ finish 结果页必须支持: 1. 重生成敲击物图案; -2. 生成、上传或替换敲击音效; -3. 修改标题、简介和标签; +2. 上传、录制或替换敲击音效;未提供时使用默认木鱼音; +3. 修改标题、简介和标签,并在试玩或发布前写回当前作品信息; 4. 修改祝福词,最多 8 条。 -图案重生成和音效生成是独立局部生成态,不得把已有可查看结果重新变成不可打开的全局生成中。 +图案重生成是独立局部生成态,不得把已有可查看结果重新变成不可打开的全局生成中。音效替换只接受上传或录音资产,不触发提示词音效生成。 ## 11. 运行态规则 @@ -334,7 +349,7 @@ finish 功能区: -1. 顶部计数器; +1. 顶部总数记录卡和其下拉的子项计数器面板; 2. 设置、暂停、返回、发布分享等按钮; 3. 结果弹层和音频授权提示。 @@ -343,11 +358,13 @@ finish 1. 点击非功能区才算一次敲击; 2. 每次敲击立即本地累加 `totalTapCount`; 3. 随机等概率从 `floatingWords[]` 中取一个词条; -4. 若词条首次出现,顶部创建对应计数器; +4. 子项计数面板中预置展示所有词条,未出现词条初始值为 0; 5. 后续同词条出现时对应计数器 +1; 6. 播放敲击音效; 7. 敲击物图案执行压缩、回弹或轻微震动动画; -8. 木鱼上方飘出“词条+1”并淡出。 +8. 木鱼上方飘出“词条+1”并淡出,飘字只显示文字本体,不加底板、胶囊背景或说明面板。 + +运行态左上角返回按钮必须优先使用 `backButtonAsset` 渲染主题化按钮图;缺失时才回退通用图标按钮。运行态不提供右上角重开按钮。 音频播放: @@ -367,18 +384,20 @@ finish ## 13. 验收 1. 创作入口能看到 `敲木鱼` 模板; -2. 工作台可以填写敲击物描述、上传参考图、配置音效和祝福词; +2. 工作台可以填写敲击物描述、上传参考图、上传或录制音效、配置祝福词; 3. 提交后按默认木鱼参考图生成 image2 敲击物图案; 4. 提交后按新敲击物图案参考图生成 9:16 背景环境图; -5. 上传图不会直接进入运行态; -6. 用户上传或录制音效时跳过音效生成并持久化该资产; -7. 结果页能看到背景、图案、试听音效、编辑祝福词并试玩; -8. 运行态功能区点击不触发敲击; -9. 非功能区点击会计数、播放音效、播放敲击动画并飘字; -10. 顶部计数器只在词条首次出现时创建; -11. 连点不丢计数; -12. `checkpoint` 和 `finish` 只保存单次 run 摘要; -13. 作品可以发布、进入公开列表和公开详情; -14. `WF-*` 公开作品号能进入分享和运行态; -15. `npm run check:encoding` 通过; -16. schema 变更后 `npm run check:spacetime-schema` 通过。 +5. 提交后按去绿后的敲击物主体和背景环境图生成主题返回按钮图; +6. 上传图不会直接进入运行态; +7. 用户上传或录制音效时直接持久化该资产,未提供时使用默认木鱼音; +8. 结果页能看到背景、图案、试听音效、编辑祝福词并试玩; +9. 运行态功能区点击不触发敲击; +10. 运行态左上角使用主题返回按钮图,右上角不出现重开按钮; +11. 非功能区点击会计数、播放音效、播放敲击动画并飘出无底板大号文字; +12. 顶部总数卡点击后展开子项计数器面板,面板内预置全部词条且未出现词条初始值为 0,面板外点击可收起; +13. 连点不丢计数; +14. `checkpoint` 和 `finish` 只保存单次 run 摘要; +15. 作品可以发布、进入公开列表和公开详情; +16. `WF-*` 公开作品号能进入分享和运行态; +17. `npm run check:encoding` 通过; +18. schema 变更后 `npm run check:spacetime-schema` 通过。 diff --git a/docs/superpowers/plans/2026-05-24-create-tab-blank-entry-page.md b/docs/superpowers/plans/2026-05-24-create-tab-blank-entry-page.md new file mode 100644 index 00000000..582d8ed6 --- /dev/null +++ b/docs/superpowers/plans/2026-05-24-create-tab-blank-entry-page.md @@ -0,0 +1,33 @@ +# 创作 Tab 入口表单回切记录 + +> 说明:本文件原本记录“空白入口页”实施计划。2026-05-24 已按产品反馈回切为点击玩法模板卡后直达既有入口创作表单,保留此文件作为历史回顾,避免后续误按旧计划继续实现空白页。 + +**Goal:** 创作 Tab 只展示赛事 banner、玩法模板分类和两列玩法卡;点击卡片后直接进入对应玩法已有的入口创作表单,而不是继续展示空白占位页。 + +**Architecture:** 保留现有创作大厅和入口配置事实源。创作大厅负责参考图 banner、分类 tabs 和入口卡片;平台壳层负责把卡片点击路由到对应玩法的既有入口表单 stage。入口表单继续承接各玩法自己的表单、草稿恢复和后续编排,不再多套一层空白入口页。 + +**Current Route Mapping:** + +- `/creation/rpg` -> `agent-workspace` +- `/creation/big-fish` -> `big-fish-agent-workspace` +- `/creation/match3d` -> `match3d-agent-workspace` +- `/creation/square-hole` -> `square-hole-agent-workspace` +- `/creation/jump-hop` -> `jump-hop-workspace` +- `/creation/wooden-fish` -> `wooden-fish-workspace` +- `/creation/puzzle` -> `puzzle-agent-workspace` +- `/creation/bark-battle` -> `bark-battle-workspace` +- `/creation/visual-novel` -> `visual-novel-agent-workspace` +- `/creation/baby-object-match` -> `baby-object-match-workspace` + +**Verification:** + +- `npm test -- src/routing/appPageRoutes.test.ts` +- `npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "create tab opens match3d entry form from the template card|create tab opens puzzle entry form from the template card|create tab opens bark battle entry form from the template card"` +- `npm run typecheck` +- `npm run check:encoding` + +**Notes:** + +- 不再新增或保留 `*-workspace-entry` 空白 stage。 +- `/creation/` 是用户可直达的入口表单 URL。 +- 旧的空白入口页方案已废弃;如需重新引入,必须先更新平台入口文档和本记录。 diff --git a/docs/superpowers/plans/2026-05-24-profile-tab-mobile-layout.md b/docs/superpowers/plans/2026-05-24-profile-tab-mobile-layout.md new file mode 100644 index 00000000..bd2864e6 --- /dev/null +++ b/docs/superpowers/plans/2026-05-24-profile-tab-mobile-layout.md @@ -0,0 +1,59 @@ +# Profile Tab Mobile Layout Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Tighten the mobile `我的` Tab layout so typography, spacing, and fixed-height controls align with the rest of the platform UI and do not overlap the bottom dock. + +**Architecture:** Keep the existing `RpgEntryHomeView` structure and update only page-specific class names plus CSS rules. Treat `src/index.css` as the platform token surface; document the mobile profile Tab acceptance criteria in the existing product / play-flow docs instead of creating a parallel doc. + +**Tech Stack:** React, TypeScript, Tailwind utility classes, project CSS in `src/index.css`, Vitest, Vite local browser smoke. + +--- + +### Task 1: Add Mobile Profile Layout Assertions + +**Files:** +- Modify: `src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` + +- [ ] Add expectations in `mobile profile page matches the reference layout sections` that the profile page, header, stats grid, daily task card, shortcut grid, and bottom dock expose the stable class hooks used by the mobile CSS. + +- [ ] Run `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile profile page matches the reference layout sections"` and verify the test covers the hooks before CSS edits. + +### Task 2: Tighten Profile Tab Markup Hooks + +**Files:** +- Modify: `src/components/rpg-entry/RpgEntryHomeView.tsx` + +- [ ] Add focused class hooks for profile identity text, stat values, shortcut labels, membership copy, and daily task copy without changing user-facing Chinese text. + +- [ ] Keep the current page order: profile header, membership card, three stats, daily task, five shortcut buttons, settings, secondary shortcuts, legal strip. + +### Task 3: Implement Mobile CSS + +**Files:** +- Modify: `src/index.css` + +- [ ] Under the existing `@media (max-width: 639px)` block, reduce `我的` Tab fixed sizes to ordinary UI scale: profile title 16-17px, body copy 11-13px, stats values 13-14px, shortcut labels 11-12px. + +- [ ] Make narrow screens robust: stats grid uses three min-width-safe columns, shortcut grid becomes `repeat(5, minmax(0, 1fr))` above 360px and a stable `repeat(3, minmax(0, 1fr))` below 360px, identity text wraps safely, legal links wrap when needed. + +- [ ] Keep the bottom dock fixed but add enough profile page bottom padding so the final legal / secondary section can scroll above it. + +### Task 4: Update Docs + +**Files:** +- Modify: `docs/【项目基线】当前产品与工程约束-2026-05-15.md` +- Modify: `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` + +- [ ] Add the acceptance rule that mobile `我的` Tab typography stays within normal UI sizes and all functional blocks must scroll clear of the bottom dock on narrow screens. + +### Task 5: Verify + +**Files:** +- No new files. + +- [ ] Run `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile profile page matches the reference layout sections"`. + +- [ ] Run `npm run check:encoding`. + +- [ ] Run a local mobile viewport smoke check for the profile tab if the dev server starts cleanly. diff --git a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md index f5a3cd42..afd5b8d8 100644 --- a/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md +++ b/docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md @@ -160,9 +160,9 @@ npm run check:server-rs-ddd - Match3D 物品 sheet:关卡整图完成后走 VectorEngine `/v1/images/edits` multipart `image`,模型为 `gpt-image-2`,`2K 1:1` 输出 `10*10` spritesheet;物品 sheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG,并把透明整图写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。后端固定从该 sheet 解析并持久化 20 个物品、每个 5 个形态;通用系列素材图集的行列索引按每行 2 个物品计算,必须落在 `1..=10`,难度只决定运行态加载 3 / 9 / 15 / 20 种。 - Match3D UI spritesheet 和背景派生图:关卡整图作为参考图并发生成 `1K 1:1` UI spritesheet 与 `1K 9:16` 背景图,模型均为 `gpt-image-2`。UI spritesheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。 - Match3D 1:1 容器 UI:VectorEngine `/v1/images/edits` multipart 参考图。该容器参考图是后端生图协议输入,必须通过 `include_bytes!` 随 `api-server` 编译进二进制,避免 API 单独发布或运行目录缺少 `public/` 时生成失败。 -- 敲木鱼敲击物和背景环境图:VectorEngine `/v1/images/edits`,模型固定 `gpt-image-2`。敲击物支持 multipart 多参考图,第一张固定为后端内嵌默认木鱼图,用户上传图只作为新主题参考;背景环境图只使用新敲击物图作为参考。 +- 敲木鱼敲击物和背景环境图:VectorEngine `/v1/images/edits`,模型固定 `gpt-image-2`。敲击物支持 multipart 多参考图,第一张固定为后端内嵌默认木鱼图,用户上传图只作为新主题参考;prompt 必须要求 `1:1` 真实透明 alpha PNG 并禁止黑底、白底、棋盘格和任何实底背景。当前敲击物上传 OSS 前不做服务端抠图后处理,避免误伤玉米等主体像素。背景环境图只使用第一步抠图完成后的透明敲击物图作为参考,prompt 必须要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围,且必须显式声明不继承任何绿色底色、绿幕底色或纯绿色画布。 - Hyper3D / Rodin:只保留后端安全代理和旧数据兼容;新 Match3D 草稿和批量新增不再生成 GLB。 -- 音频:视觉小说专用音频路由保留;拼图和抓大鹅生成入口暂时关闭,通用 `/api/creation/audio/*` 对相关目标返回 `410 Gone`;敲木鱼 `hit_sound` 目标例外开放,复用 VectorEngine Vidu 音效生成、OSS 私有对象、`asset_object` 和 entity binding 链路,目标字段固定为 `entityKind='wooden_fish_work'`、`slot='hit_sound'`、`assetKind='wooden_fish_hit_sound'`、`storagePrefix='wooden_fish_assets'`。 +- 音频:视觉小说专用音频路由保留;拼图、抓大鹅和敲木鱼提示词生成音效入口暂时关闭,通用 `/api/creation/audio/*` 对这些目标返回 `410 Gone`。敲木鱼创作只接收上传 / 录音音频资产;未提供时由 `api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。 - OSS:私有 generated legacy path 进入浏览器前必须通过 `/api/assets/read-url` 换签;不要裸请求 `/generated-*`。 - 外部 API 失败审计:外部供应商调用未成功时,`api-server` 必须发送 OTLP 失败事件并写入 `tracking_event`。当前通用 VectorEngine `gpt-image-2-all` 图片生成 / 编辑适配器在 `request_send`、`response_body`、`upstream_status`、`response_parse`、`missing_image` 和 `image_download` 阶段失败时记录 `external_api_call_failure`,`scope_kind = module`、`scope_id = provider`、`module_key = external-api`;metadata 固定包含 provider、endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount 和 imageModel。入库优先复用 tracking outbox,outbox 不可写或保护阈值拒绝时回退同步写 SpacetimeDB;不得新增前端兜底或在 SpacetimeDB reducer 内做外部 I/O。 @@ -312,11 +312,15 @@ npm run check:server-rs-ddd - Rust 结构体:`CreationEntryConfig` - 源码:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` +- 字段:`config_id`、`start_title`、`start_description`、`start_idle_badge`、`start_busy_badge`、`modal_title`、`modal_description`、`updated_at`、`event_title`、`event_description`、`event_cover_image_src`、`event_prize_pool_mud_points`、`event_starts_at_text`、`event_ends_at_text`。 +- 迁移兼容:旧迁移包缺少活动横幅字段时,由 `migration.rs` 写入 `None` / `58000` 默认值;运行态读取层再按 `module-runtime` 默认横幅归一,不覆盖后台已保存配置。 ### `creation_entry_type_config` - Rust 结构体:`CreationEntryTypeConfig` - 源码:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` +- 字段:`id`、`title`、`subtitle`、`badge`、`image_src`、`visible`、`open`、`sort_order`、`updated_at`、`category_id`、`category_label`、`category_sort_order`。 +- 迁移兼容:旧迁移包缺少入口分类字段时,由 `migration.rs` 写入 `None` / `0` 默认值;入口分组展示由 `module-runtime` 和前端展示派生消费。 ### `custom_world_agent_message` @@ -423,7 +427,7 @@ npm run check:server-rs-ddd - Rust 结构体:`WoodenFishWorkProfileRow` - 源码:`server-rs/crates/spacetime-module/src/wooden_fish/tables.rs` -- 说明:敲木鱼作品 profile 真相,包含敲击物图案、背景环境图、敲击音效、飘字配置、发布状态和公开计数;`background_asset_json` 是后加入字段,保存 image2 生成的 9:16 背景环境图资产快照,旧迁移数据按 `None` 兼容。 +- 说明:敲木鱼作品 profile 真相,包含敲击物图案、背景环境图、主题返回按钮图、敲击音效、飘字配置、发布状态和公开计数;`background_asset_json` 保存 image2 生成的 9:16 背景环境图资产快照,`back_button_asset_json` 保存 image2 生成并去绿后的 1:1 返回按钮图资产快照,旧迁移数据按 `None` 兼容。 ### SpacetimeDB view:`wooden_fish_gallery_card_view` @@ -437,7 +441,7 @@ npm run check:server-rs-ddd - Rust view:`wooden_fish_gallery_view` - 返回类型:`Vec` - 源码:`server-rs/crates/spacetime-module/src/wooden_fish.rs` -- 说明:敲木鱼公开详情兼容投影,包含敲击物图案、敲击音效和飘字配置;公开列表主路径优先使用 `wooden_fish_gallery_card_view`。 +- 说明:敲木鱼公开详情兼容投影,包含敲击物图案、背景环境图、主题返回按钮图、敲击音效和飘字配置;公开列表主路径优先使用 `wooden_fish_gallery_card_view`。 ### `match3d_agent_message` @@ -639,6 +643,7 @@ npm run check:server-rs-ddd 拼图、自定义世界、抓大鹅、方洞挑战、视觉小说和大鱼吃小鱼的公开列表 HTTP 路由都从订阅 cache 读取公开 read model / view。各玩法的个人作品列表、详情、发布、点赞、游玩记录、Remix 和其它需要鉴权或写入副作用的路径继续走 procedure / reducer;不要为了公开列表性能把这些 owner-specific 或 mutation 语义混进 public view。 `GET /api/creation-entry/config` 和入口熔断优先从订阅 cache 读取创作入口配置;cache 缺失时使用最近一次成功读取的内存快照,再兜底调用 `get_creation_entry_config` procedure 完成空库种子或旧库兼容。 +入口配置快照包含 start card、类型弹窗、活动横幅和入口类型列表;入口类型列表新增 `category_id`、`category_label`、`category_sort_order` 后,后台 upsert、`shared-contracts`、`module-runtime` 和 `spacetime-client` binding 必须同步,旧迁移 JSON 通过 `migration.rs` 默认值兼容。 RPG 创作入口的配置 ID 是 `rpg`,当前 `visible=true`、`open=true`;历史 `custom-world` 路由仍是 RPG 的工程域与运行态源类型。入口熔断把 `/api/runtime/custom-world*`、`/api/story/*` 和 `/api/runtime/chat/*` 统一映射到 `rpg`,不要新增平行 `airp` 路由或用 `airp` 接管当前文字冒险链路。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index 41f80d9c..71cd5ff4 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -45,10 +45,20 @@ npm run dev:api-server 后端日志默认写入 `logs/api-server/`。后端 API smoke 使用 `npm run dev:api-server` 并检查 `/healthz`;不要使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。 -如果本地 `GET /api/creation-entry/config` 返回 `No such procedure`,通常是 `.env.local` 指向的 SpacetimeDB 库还没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库。debug 构建的 `api-server` 会临时使用后端默认入口配置兜底,避免创作作品架整块消失;正式修复仍应切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布,或用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。 +如果本地 `GET /api/creation-entry/config` 返回 `No such procedure`,或 `api-server` 日志出现 `no such table: puzzle_gallery_card_view` / `no such table: wooden_fish_gallery_card_view` 这类公开 view 缺失,通常是 `.env.local` 指向的 SpacetimeDB 库还没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库。debug 构建的 `api-server` 会临时使用后端默认入口配置兜底,避免创作作品架整块消失;正式修复仍应切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布,或用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。 + +本地排查 schema 漂移时,先用当前 dev server 显式查询目标库,例如: + +```bash +spacetime sql "SELECT * FROM puzzle_gallery_card_view LIMIT 1" --server http://127.0.0.1:3101 +``` + +如果旧 `.env.local` 仍指向缺少当前 view 的库,例如 `xushi-p4wfr`,而当前可发布库已经包含这些 view,可在 gitignored 的 `spacetime.local.json` 写入 `{"database":"genarrative-dev-codex"}` 作为本机覆盖;写入时不要带 UTF-8 BOM,否则 `scripts/dev.mjs` 会忽略该文件。修改后重启 `api-server`,再检查 `/healthz` 和 `/api/runtime/puzzle/gallery`。 本地 `npm run dev:spacetime` 发布模块时必须显式忽略仓库根目录的 `spacetime.json`,由脚本固定追加 `--no-config` 并使用命令参数里传入的数据库名和 `--server http://127.0.0.1:3101`。否则 CLI 可能把发布目标改写到配置文件里的其他数据库,导致 `dev:spacetime` 启动后又因发布失败自动退出,浏览器随后会在 `ws://127.0.0.1:3101/v1/database/.../subscribe` 看到连接拒绝。 +本地 `spacetime` CLI / standalone 版本必须和 `server-rs/Cargo.toml` 里锁定的 `spacetimedb` 版本一致。若版本错配,procedure 返回值可能在宿主侧触发 `Failed to BSATN deserialize procedure return value`,api-server 最终表现为敲木鱼等创作动作的 `SpacetimeDB procedure 调用超时`。排障时先运行 `spacetime --version`,再对照 `server-rs/Cargo.toml` 的 `spacetimedb = "..."`;需要切版本时执行 `spacetime version install && spacetime version use `,然后重新启动 `npm run dev:spacetime`。当前 `scripts/dev.mjs` 会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免把旧 standalone 继续带进新一轮创作。 + 本地 `.env`、`.env.local` 或 `.env.secrets.local` 修改后必须重启 `api-server` 才会生效;若已经通过 `npm run dev` 启动完整联调,可在该终端输入 `rs api-server`。排查 RPG / 拼图 / 抓大鹅等 VectorEngine 生图链路时,确认 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 只在本地或服务器密钥文件中配置,不能写入 Git。开局 CG 故事板、首图、背景和图集都属于长耗时图片请求;后端默认会把 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 下限收口到 `1000000`,旧进程仍可能沿用重启前的短超时。若开局 CG 故事板在 `send()` 阶段失败且日志显示 `SendRequest`,先看同一 request_id 的 `request_body_bytes`、`reference_data_url_bytes`、`sourceChain` 和 `rootSource`;当前开局 CG 会把角色图与首幕背景图压到单边 768 的 JPEG 后再作为 generations `image` 数组发送,`/v1/images/generations` 使用默认 HTTP 协商,只有 multipart `/v1/images/edits` 单独强制 HTTP/1.1。 查看本地 Rust / SpacetimeDB 日志: diff --git a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md index 7652708c..6a5029af 100644 --- a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md +++ b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md @@ -6,7 +6,7 @@ 创作入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;后台通过 `/admin/api/creation-entry/config` 管理。前端只在展示层派生可见卡片和入口状态,`api-server` 路由熔断也使用同一份配置。不要恢复前端硬编码入口配置文件。 -当前创作 Tab 固定为智能创作首页与模板入口,草稿 Tab 承接作品架。点击独立入口后应切换到对应内嵌创作表单或生成页;不要额外做一张平行配置页,除非玩法本身需要完整独立工作台。 +当前创作 Tab 只承载赛事 banner、玩法模板分类和两列模板卡;点击模板卡后直接进入对应玩法已有的入口创作表单 stage,不再经过空白占位页,也不把旧表单嵌进创作 Tab 首屏。移动端创作 Tab 顶栏在 `陶泥儿` 品牌同一行显示真实账户泥点数,数据来自 `profileDashboard.walletBalance`,不得再把活动奖池当作账号余额展示。首屏 banner 结构按参考图拆成横向可滑动赛事卡、主体宣传图文区、奖池胶囊、开始 / 结束时间条和卡片内分页点;轮播只保留 `拼图主题创作赛` 和 `抓大鹅主题创作赛`,两个主题赛事奖池均为 `1000` 泥点数。玩法列表不再套外部边框卡片,移动端需要压缩横向边距和两列间距;玩法卡统一按“上图、左上状态标签(仅非开放态显示)、封面右下 `10-20泥点数`、下方白底标题/描述”结构展示,卡片高度保持紧凑但标题、描述和预估消耗点数都必须可见。创作 Tab 根容器不再使用 `platform-page-stage` 这类全局内容卡片壳,但继续保留 `platform-remap-surface` 作为主题和输入框样式命中钩子。创作首屏字号需要对齐平台普通 UI 档位:顶栏泥点组件、banner 正文、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,不使用 `text-lg`、`text-xl` 或更大的展示级字号。草稿 Tab 继续承接作品架。RPG、RPG 之外的各玩法入口分别落到既有的 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`,这些入口继续承接各玩法自己的表单、草稿恢复和后续编排,不作为创作 Tab 首屏内容。 `PlatformEntryFlowShellImpl.tsx` 仍是平台入口编排壳,后续维护时应优先把独立 UI 片段、公开作品映射、草稿生成 notice 和运行态状态 helper 拆到 `src/components/platform-entry/PlatformEntryFlowShellImpl/` 或同目录紧邻 helper 文件。拆分只允许改变文件组织,不改变入口配置事实源、默认导出、props、页面阶段、UI 文案或现有交互;其中拼图首访 onboarding 已拆为 `PlatformEntryFlowShellImpl/PuzzleOnboardingView.tsx`。 @@ -26,16 +26,20 @@ `api-server` 的 `generated_asset_sheets` 是当前通用系列素材图集模块:`n` 是必选参数,模块负责组装 `n*n` sheet prompt、按 `n*n` 切片、绿幕 / 近白底透明化、导出 PNG 和 OSS 持久化请求。物品名称 prompt 和特殊设定 prompt 是可选输入;调用方可传入类似“每个物品生成五个不同视图”的视角约束,通用模块会把 sheet prompt、物品行 prompt、特殊设定 prompt 编码写入 OSS 元数据。玩法仍负责计费、物品规划、slot 映射、失败回写和把通用切片结果映射回自己的草稿 / profile / runtime 字段。 +当前所有玩法生成页 UI 统一收敛为圆环主视觉:`media/create_bg_video.mp4` 作为生成页固定全屏背景层循环静音播放,主进度圆环居中覆盖在背景之上,围绕陶泥儿视觉展示;页面只保留当前步骤名称和当前步骤进度,不再渲染步骤列表块。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。共用生成页 `CustomWorldGenerationView` 和汪汪声浪生成页都必须遵循这一口径。 + ## 草稿与作品架 1. 草稿页作品卡对齐发现页列表卡风格:左侧信息,右侧封面图,移动端单列,桌面两到三列。 -2. 草稿 / 已发布状态尽量图标化,不使用大段状态文案。 -3. 草稿卡常态不外露低频动作;已发布作品卡右上角可直接显示无边框分享 icon,删除等破坏性动作继续收口到左滑或长按操作层。 +2. 草稿页顶部 `全部 / 草稿 / 已发布` 筛选与发现页 `推荐 / 今日 / 分类 / 排行` 频道标签复用同一选中 / 未选中视觉,即 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`,不再使用旧 `platform-tab` 胶囊样式。 +3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示无边框分享 icon。删除等破坏性动作继续收口到左滑或长按操作层。 4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。 5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈。 -6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用作品摘要 `updatedAt` 推导。 +6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用进入生成页的当前时间,作品摘要 `updatedAt` 只用于排序和摘要展示,不参与假进度起算。 7. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。 +发现 Tab、创作 Tab 与草稿 Tab 的页面根内容区不再套 `platform-page-stage` 外层全局卡片壳,让列表、筛选和玩法卡获得更宽的横向空间;推荐页和我的页仍按各自页面设计保留原有全局卡片口径。移动端“我的”页仍按顶部头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口、次级入口带和法律信息组织,但字号必须维持平台普通 UI 档位,不能因为窄屏把卡片标题、功能 label 或法律信息撑成展示级字号;最后一屏内容必须能在底部 dock 上方完整滚动露出,不得被固定底部导航遮挡。 + ## RPG / 自定义世界 当前 RPG 创作入口使用 `playId = rpg`,工程域和运行态源类型沿用历史 `custom-world`。默认入口状态为 `visible=true`、`open=true`,对外展示为“文字冒险”;`airp` 仍是独立的“AI RPG”占位入口,保持 `open=false`,不要把它当作当前 RPG 创作链路开放。 @@ -62,7 +66,7 @@ RPG 从作品架、广场详情或作品号搜索点击“启动”前,入口 RPG 运行态的战斗终局、继续冒险、继续探索和切场景都属于服务端 runtime 快照真相:`module-runtime-story` 必须在终局战斗 action 后调用 post-battle finalization,持久写入 `story_continue_adventure`、`deferredOptions`、`deferredRuntimeState.storyEngineMemory.currentSceneActState` 和清理后的战斗状态;`idle_travel_next_scene` / `camp_travel_home_scene` 必须由后端写入新的 `currentScenePreset`、`currentSceneActState`、`currentEncounter` 和 `runtimeStats.scenesTraveled`。前端只播放退场、进场和继续按钮表现,不能用默认 `观察/试探/调息` fallback 或本地动画假装推进剧情。旧 bootstrap 快照可能只有 `connectedSceneIds` / `forwardSceneId` 而没有 `connections`,后端生成战后旅行选项时必须兼容这些字段。 -RPG / 拼图等运行态存档选择入口统一在个人中心 `常用功能 > 存档` 暴露为独立弹窗;“玩过”弹窗可以继续合并展示可继续存档,但不能成为唯一入口。前端只展示 `/api/profile/save-archives` 返回的列表并在用户选择后调用对应恢复接口,不能本地拼装或筛选正式存档真相。 +RPG / 拼图等运行态存档选择入口统一在个人中心 `次级入口 > 存档` 和设置入口区保留为独立弹窗;“玩过”弹窗可以继续合并展示可继续存档,但不能成为唯一入口。移动端“我的”页的五项常用功能宫格只放泥点充值、邀请好友、兑换码、玩家社区、反馈与建议,避免把存档挤入主宫格破坏参考图布局。前端只展示 `/api/profile/save-archives` 返回的列表并在用户选择后调用对应恢复接口,不能本地拼装或筛选正式存档真相。 ## 拼图 @@ -76,10 +80,10 @@ RPG / 拼图等运行态存档选择入口统一在个人中心 `常用功能 > - 图像输入复用 `CreativeImageInputPanel`。 - 结果页每关画面编辑复用 `CreativeImageInputPanel`;入口页和关卡画面只共享受控 UI 模块,不共享数据源、状态、action 或存储位置:入口页继续写 `formDraft` 与草稿编译 payload,关卡画面写 `levels[].pictureReference/pictureDescription` 并触发 `generate_puzzle_images`。结果页删除独立“素材配置”Tab,不再提供单独 UI 背景生成入口。通用图片面板的展示图和 AI 重绘参考图能力必须分开控制:结果页正式关卡图只作为预览图,不因存在正式图自动暴露 AI 重绘开关;只有本地上传、历史选择或已保存 `pictureReference` 可作为重绘参考图时,才显示 AI 重绘开关并把状态带入 `generate_puzzle_images`。用户在本次编辑中上传或选择历史图后,该图优先占据主图卡片,可删除、切换 AI 重绘,也可关闭 AI 重绘直用;仅有正式图预览时,画面描述框仍可上传多张参考图。关卡详情弹窗应使用加宽面板,关卡名称、画面图和画面描述合并在同一个纵向列表中,名称输入和画面编辑模块外层不再包独立 `platform-subpanel`;画面图卡仍必须保留稳定最小高度,避免弹窗内 `flex-1` 布局坍缩后只剩标题、描述输入和操作按钮。 -- 支持画面描述生图、多参考图生图、上传或历史生成主图后 AI 重绘、上传或历史生成主图后不重绘;入口页与结果页新增关卡本地上传都不走浏览器直传 OSS,前端读取为 Data URL 后随创作 action 提交给 `api-server`,并在图像输入区明确展示“图片≤6MB”。参考图上传前后统一限制为 6MB:前端读取前做文件大小校验并提示“参考图过大,请压缩后再上传(当前 X,最多 6MB)”,后端对 Data URL / asset object 的实际字节数再次检测并返回 400。历史图片仍提交 `referenceImageAssetObjectId(s)`,由后端校验 owner / bucket / kind / MIME / size 后签发 OSS 只读 URL并下载为 VectorEngine `/v1/images/edits` 的 multipart `image` part;本地上传 Data URL 与历史 `/generated-*` 路径继续由后端统一解析。关闭 AI 重绘时,后端统一解析为首关或当前关卡正式图后再持久化,不调用第一段拼图首图生成。 -- 草稿生成会先持久化 `generationStatus=generating` 的作品摘要,生成完成并回写关卡拼图画面、关卡画面参考图、UI spritesheet 和关卡背景图后再变为 `ready`;当前不自动生成背景音乐。生成页进度不再按固定 5 分钟展示,而按实际开始时间和当前路径的分步骤预计时长推进;任一同步 action 回包到达时立即以真实完成/失败结果冻结进度。 +- 支持画面描述生图、多参考图生图、上传或历史生成主图后 AI 重绘、上传或历史生成主图后不重绘;主链要求浏览器先经 `/api/assets/direct-upload-tickets` 直传 OSS 并确认 `asset_object`,创作 action 只提交 `referenceImageAssetObjectId(s)`,由后端校验 owner / bucket / kind / MIME / size 后签发 OSS 只读 URL 并下载为 VectorEngine `/v1/images/edits` 的 multipart `image` part。本地上传 Data URL 与历史 `/generated-*` 图片路径仅保留为旧草稿、旧入口或未迁移客户端的兼容输入;关闭 AI 重绘时,后端统一解析为首关或当前关卡正式图后再持久化,不调用第一段拼图首图生成。 +- 草稿生成会先持久化 `generationStatus=generating` 的作品摘要,生成完成并回写关卡拼图画面、关卡画面参考图、UI spritesheet 和关卡背景图后再变为 `ready`;当前不自动生成背景音乐。生成页步骤推进必须跟随后端 session `progressPercent` 的真实里程碑:`88` 表示草稿编译完成并进入出图步骤,`94` 表示生成图已保存并进入 UI / 背景步骤,`96` 表示正式图与 UI 背景已确认并进入写入步骤,最终 action 成功或发布才进入完成态;每个步骤内部可以按实际等待时间使用假进度平滑推进,总进度按 `0-88`、`88-94`、`94-96`、`96-98` 的真实里程碑区间平滑推进。任一同步 action 回包到达时立即以真实完成/失败结果冻结进度。 - 作品架拼图草稿的“生成中”遮罩只表示初始草稿还没有可查看结果;只要作品摘要、首关封面或任一关卡候选图已经可用,后续 UI 背景重生成和追加关卡生图都必须作为结果页局部生成态处理,不能阻止打开草稿结果页。 -- 拼图草稿编译是长耗时 action,前端 action 请求默认等待 `1_800_000ms`(30 分钟)且不自动重试。每次 `gpt-image-2` 调用的预期用时按 90 秒计算;完整 AI 重绘路径为 `编译首关草稿` 8 秒、`生成关卡名称` 10 秒、`生成拼图首图` 90 秒、`生成关卡画面` 90 秒、`生成UI与背景` 90 秒、`写入正式草稿` 10 秒,合计约 298 秒。上传图且关闭 AI 重绘时必须跳过 `生成拼图首图`,直接进入 `生成关卡画面` 和 `生成UI与背景`,合计约 208 秒。生成页恢复时必须沿用作品摘要 `updatedAt` 作为原始 `startedAtMs`,失败/完成态用 `finishedAtMs` 冻结耗时,不能在锁屏或返回草稿页后重新从 0 计时。未收到 action 回包前,总进度仍最多停在 98%,但当预计写入时长耗尽且仍处于 `写入正式草稿` 时,该步骤自身应显示已完成,不能出现“进行中 100%”。 +- 拼图草稿编译是长耗时 action,前端 action 请求默认等待 `1_800_000ms`(30 分钟)且不自动重试。每次 `gpt-image-2` 调用的预期用时按 90 秒计算,但 `生成拼图首图` 单独按 4 分钟展示;完整 AI 重绘路径为 `编译首关草稿` 8 秒、`生成关卡名称` 10 秒、`生成拼图首图` 4 分钟、`生成关卡画面` 90 秒、`生成UI与背景` 90 秒、`写入正式草稿` 10 秒,合计约 448 秒。上传图且关闭 AI 重绘时必须跳过 `生成拼图首图`,直接进入 `生成关卡画面` 和 `生成UI与背景`,合计约 208 秒。生成页恢复时必须使用进入生成页的当前时间作为原始 `startedAtMs`;失败/完成态用 `finishedAtMs` 冻结耗时。未收到对应后端里程碑前,后续步骤保持待处理;即使当前步骤预计时长耗尽,也只能让当前步骤内部进度停在 `98%` 内,不能自动完成当前步骤或跳到后续步骤。生成页每个步骤只展示标题和进度,不展示步骤详细描述。 - 若浏览器锁屏、息屏或网络切换导致 compile 请求失败,前端在标记失败前必须先复读 `getPuzzleAgentSession(sessionId)`;只有最新 session 仍缺 `draft.coverImageSrc`、首关 `coverImageSrc` 或候选图时才展示失败,复读到已生成草稿时按成功收尾、刷新作品架并继续自动试玩/结果页链路。 - 拼图参考图 AI 重绘走 VectorEngine `/v1/images/edits`;无参考图时走 `/v1/images/generations`。两者模型都使用 `gpt-image-2`,参考图由后端作为 multipart `image` part 传入编辑接口。 - 每次新建关卡生成或重新生成关卡图都必须由 `api-server` 串起当前关卡资产包:AI 重绘开启时第一段沿用草稿生成第一关的拼图主图提示词配置和模型 / 尺寸 / 参考图规则生成 `coverImageSrc/coverAssetId` 作为关卡拼图画面和结果页预览图,提示词来源同样按显式画面描述、关卡画面描述、草稿摘要顺序回退,且固定要求输出画面比例为 `1:1`;上传图且关闭 AI 重绘时跳过这一段,把上传图或历史图持久化为 `sourceType=uploaded` 的正式候选。随后用正式候选图作为参考,`9:16` 生成完整拼图游戏关卡画面并写入 `levelSceneImageSrc/levelSceneImageObjectKey`,提示词必须要求道具按钮上不要显示次数标注,且返回按钮和设置按钮旁禁止标注文字;UI spritesheet 与关卡纯背景在关卡画面完成后并发生成,spritesheet 用 `1:1`、`1k` 先生成纯绿色绿幕背景图,后端上传 OSS 前必须把绿幕扣成透明 PNG,再写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`,按钮顺序固定为返回、设置、下一关、提示、原图、冻结,按钮素材自身保留对应中文文字,返回和设置按钮不得额外生成白色外圈、白底圆环或浮雕外框;纯背景用 `9:16`、`1k` 写入 `levelBackgroundImageSrc/levelBackgroundImageObjectKey`,提示词必须包含“禁止在背景中出现人像或和拼图画面中主体一致的内容”。运行态不直接使用第二段完整关卡画面,但必须持久化它用于追踪和后续再生成。结果页局部关卡生成进度按 AI 重绘开启约 270 秒、关闭 AI 重绘约 180 秒展示。 @@ -139,12 +143,15 @@ RPG / 拼图等运行态存档选择入口统一在个人中心 `常用功能 > 创作输入固定为: 1. `敲什么`:敲击物单图资产槽位。默认模板使用内置透明 PNG `/wooden-fish/default-hit-object.png` 作为 `bundled-default` 敲击物资产,避免默认关键词被重新语义化改形;用户输入自定义关键词或上传参考图时,后端必须以默认木鱼图作为基础结构和画风参考,使用 image2 生成最终敲击物图案,上传图只作为新主题参考,不直接进入运行态。自定义 `compile-draft` / `regenerate-hit-object` 必须完成 image2 -> OSS 私有对象 -> asset object 登记和绑定后,再由 `api-server` 注入真实 `hitObjectAsset.imageSrc`,不能只写 `/generated-wooden-fish-assets/...` 占位路径,也不能接受前端请求自带的 `hitObjectAsset` 短路生成。 -2. `敲击音效`:音频资产槽位,支持描述生成、上传和麦克风录制,统一写回 `hitSoundAsset`。描述生成复用通用 `/api/creation/audio/sound-effect` 的 VectorEngine Vidu 音效生成、下载、OSS 私有对象、asset object 登记和 entity binding 链路;木鱼目标固定为 `entityKind='wooden_fish_work'`、`slot='hit_sound'`、`assetKind='wooden_fish_hit_sound'`、`storagePrefix='wooden_fish_assets'`,不得再返回 `410 Gone`,也不得由 `spacetime-client` 合成假音频路径。 -3. `功德有什么`:最多 8 条飘字,默认 `幸运、健康、财富、姻缘、幸福、事业、成功、功德`;创作态只保存词条名,运行态飘字展示时再追加 `+1`。 +2. `敲击音效`:音频资产槽位,当前创作阶段只支持用户上传或麦克风录制;未提供音频时统一写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。提示词生成音效入口临时关闭,通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`;`hitSoundPrompt` 只作为历史兼容字段保留,不参与当前创作流程,也不得由 `spacetime-client` 合成假音频路径。 +3. `功德有什么`:最多 8 条飘字,创作态首屏只保留一个默认词条 `幸运`,其下提供加号格继续追加词条;创作态只保存词条名,运行态飘字展示时再追加 `+1`。运行态顶部总数卡采用品牌化徽标样式,子项计数器预置展示在可展开面板中,未出现词条初始值为 0。 +4. `作品标题 / 作品简介 / 主题标签`:不再放在创作工作台首屏,改为生成草稿后的结果页补录区,提交试玩或发布前必须先写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。 -图片生成链路固定为双图 image2 流程:第一步用默认木鱼图作为结构和画风参考,按用户题材关键词或参考图主题生成 `1:1` 透明底新敲击物;第二步用新敲击物作为主题和画风参考生成 `9:16` 背景环境图,背景图只适配主题和画风,不能包含新敲击物本体,也不能增加木槌互动物品。两个资产分别写回 `hitObjectAsset` 与 `backgroundAsset`,并绑定到 `wooden_fish_work` 的 `hit_object` / `background` 槽位。运行态和结果页消费 `backgroundAsset` 做竖屏背景,中央再叠加 `hitObjectAsset`。 +图片生成链路固定为三图 image2 流程:第一步用默认木鱼图作为结构和画风参考,按用户题材关键词或参考图主题生成 `1:1` 绿色背景主体图(纯绿色绿幕),prompt 必须显式要求背景为单一纯绿色 `#00FF00` 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,且禁止黑底、白底、棋盘格和任何实底背景;后端在落库前只对这张绿幕主体图执行去绿背景处理,不做泛抠图,避免误伤玉米等主体像素。第二步必须使用第一步抠图完成后的透明图作为参考图,再用新敲击物作为主题和画风参考生成 `9:16` 背景环境图,背景图只适配主题和画风,不能包含新敲击物本体,也不能增加木槌互动物品;画面中央主体预留区必须干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围。第三步必须使用去绿后的敲击物主体图和背景环境图作为参考图生成 `1:1` 返回按钮图,返回按钮必须始终是标准圆形,主体视觉尺寸比当前模板再放大约 50%,圆形外沿必须有与主题色搭配的干净外描边,中央只保留单个左箭头,参考图只约束圆形底色和箭头配色,不得延伸到复杂造型和花纹;按钮不得出现文字、数字、水印、额外 UI 面板或木槌物品。三个资产分别写回 `hitObjectAsset`、`backgroundAsset` 与 `backButtonAsset`,并绑定到 `wooden_fish_work` 的 `hit_object` / `background` / `back_button` 槽位。运行态和结果页消费 `backgroundAsset` 做竖屏背景,中央再叠加 `hitObjectAsset`,左上角返回按钮消费 `backButtonAsset`。 -运行态规则真相以后端 run 摘要为准,前端只做点击低延迟表现、敲击动画、音频播放和飘字渲染。每次非功能区点击在当前 run 内累计 `totalTapCount` 和 `wordCounters`;计数不进入账号长期账本,不做排行榜。顶部计数器仅在词条首次出现时创建,后续同词条继续累加。 +木鱼初始 `compile-draft` 是长耗时同步 action,生成页必须按上述三图 image2 链路展示进度:整理草稿、生成敲击物、生成背景环境图、生成返回按钮图、写入正式草稿。本地或供应商慢时一次 action 可能持续数分钟;前端不得把已关闭的提示词生成音效当成进度阶段,也不得在未收到 action 回包前宣称生成完成。 + +运行态规则真相以后端 run 摘要为准,前端只做点击低延迟表现、敲击动画、音频播放和飘字渲染。每次非功能区点击在当前 run 内累计 `totalTapCount` 和 `wordCounters`;计数不进入账号长期账本,不做排行榜。顶部总数卡点击后展开子项计数器面板,子项计数在面板中按词条纵列预置展示,未出现词条初始值为 0,后续同词条继续累加;运行态左上角使用主题化返回按钮图,不提供右上角重开按钮。 平台首页推荐、精选、最新、公开详情、搜索、已玩作品和公开试玩统一按 `sourceType='wooden-fish'` 与 `WF-*` 公开作品号识别敲木鱼作品;公开列表应走 `wooden_fish_gallery_card_view` 订阅缓存,公开详情或运行态启动时卡片摘要不足则补读完整 work profile。 @@ -238,10 +245,10 @@ RPG / 拼图等运行态存档选择入口统一在个人中心 `常用功能 > - 后端裁决结果:后端根据 start run 与 finish 派生指标校验后的正式单局结果。 - 基础统计:只记录正式 `published` run 的开始、结算和派生指标,草稿试玩不写正式统计。 - 公开广场:统一读取 `bark_battle_gallery_view` 这类 read model,不再由前端自己拼公开列表。 -- 创作者信息:草稿架、已发布作品架、统一作品详情和公开广场都必须展示后端返回的 `authorDisplayName`,不得只在详情页内层可见。 +- 创作者信息:统一作品详情和公开广场都必须展示后端返回的 `authorDisplayName`,不得只在详情页内层可见;草稿 Tab 作品架遵循平台作品架统一口径,无论草稿 / 已发布都不外露作者信息。 - 拟声词:配置字段为 `onomatopoeia`。创作者未手动编辑时,前端根据主题 / 竞技背景描述、玩家形象描述和对手形象描述生成高能词池;创作者手动编辑后按自定义词池发布。默认词池只在命中狗相关主题时加入狗叫词,不能把非狗主题强行带回狗语义。 -当前入口默认开放:`visible=true`、`open=true`、`badge=可创建`,入口参考图使用 `/creation-type-references/bark-battle.webp`。创作入口使用 7 字段表单(作品标题、简介、主题 / 竞技背景描述 `themeDescription`、玩家形象描述、对手形象描述、拟声词、难度);提交后先进入 `bark-battle-generating` 独立生成页,自动生成玩家形象、对手形象和竞技背景三图。生成页即使部分槽位失败也要继续落到结果页,失败槽位保留错误态和单槽重试入口,不在生成页停留。结果页只保留单槽重试、重新生成和上传,不再展示一次生成按钮、音频配置入口、皮肤预设入口或排名配置。发布成功后先跳统一作品详情页 `/works/detail?work=BB-xxxxxxxx`,正式 `published` runtime 从作品详情页进入并必须使用真实麦克风;`draft` 可试玩,可使用 mock 输入,且不写正式统计。草稿与已发布作品在外部卡片、作品架和广场列表都展示创作者名称。 +当前入口默认开放:`visible=true`、`open=true`、`badge=可创建`,入口参考图使用 `/creation-type-references/bark-battle.webp`。创作入口使用 7 字段表单(作品标题、简介、主题 / 竞技背景描述 `themeDescription`、玩家形象描述、对手形象描述、拟声词、难度);提交后先进入 `bark-battle-generating` 独立生成页,自动生成玩家形象、对手形象和竞技背景三图。生成页即使部分槽位失败也要继续落到结果页,失败槽位保留错误态和单槽重试入口,不在生成页停留。结果页只保留单槽重试、重新生成和上传,不再展示一次生成按钮、音频配置入口、皮肤预设入口或排名配置。发布成功后先跳统一作品详情页 `/works/detail?work=BB-xxxxxxxx`,正式 `published` runtime 从作品详情页进入并必须使用真实麦克风;`draft` 可试玩,可使用 mock 输入,且不写正式统计。统一作品详情和广场列表展示创作者名称;草稿 Tab 作品架不外露创作者名称,已发布作品只在右上角常驻分享入口。 移动端创作 Tab 内嵌 Bark Battle 表单时,只保留外层 Tab 面板承担纵向滚动;表单自身移动端不再创建独立纵向滚动容器,底部“生成草稿”按钮作为普通表单尾部并保留 safe-area 底部间距,避免与最后一组输入框、移动端键盘或底部 TabBar 形成套滚动 / 遮挡。 @@ -249,7 +256,7 @@ RPG / 拼图等运行态存档选择入口统一在个人中心 `常用功能 > - 创作 Tab 表单:填写作品标题、简介、主题 / 竞技背景描述、玩家形象描述、对手形象描述、拟声词和难度。拟声词支持换行、逗号、顿号、斜杠或竖线分隔;未手动编辑时随主题 / 形象描述自动重算,手动编辑后保持创作者自定义。 - 草稿编译:`POST /api/creation/bark-battle/drafts` 写入配置 JSON,返回包含 `draftId`、稳定 `workId`、`configVersion` 和 `rulesetVersion` 的草稿结果。 -- 生成页:`bark-battle-generating` 自动并行产出玩家形象、对手形象和竞技背景三图;前端按槽位实时显示生成中 / 已生成 / 失败状态,三图都走 Bark Battle 专用后端生图接口 `POST /api/creation/bark-battle/images/generate`,由后端按 `player-character`、`opponent-character`、`ui-background` 分别拼装正式提示词、写入 `generated-bark-battle-assets` 私有资产前缀并返回实际 prompt。玩家 / 对手形象提示词必须保持用户形象描述,不强行注入狗相关主体,并要求正面、单个完整形象和透明背景。部分失败也继续进入结果页。 +- 生成页:`bark-battle-generating` 自动并行产出玩家形象、对手形象和竞技背景三图;前端生成页 UI 和其它玩法保持同一圆环主视觉,`media/create_bg_video.mp4` 作为固定全屏页面背景层循环静音播放,主进度圆环居中展示总进度,只保留当前步骤名称和当前步骤进度,不再渲染三行槽位列表。视频层需要显式触发播放。三图都走 Bark Battle 专用后端生图接口 `POST /api/creation/bark-battle/images/generate`,由后端按 `player-character`、`opponent-character`、`ui-background` 分别拼装正式提示词、写入 `generated-bark-battle-assets` 私有资产前缀并返回实际 prompt。玩家 / 对手形象提示词必须保持用户形象描述,不强行注入狗相关主体,并要求正面、单个完整形象和透明背景。部分失败也继续进入结果页。 - 结果页:围绕三图槽位展示错误态与已生成结果,只保留单槽重试、重新生成和上传,不再提供一次生成按钮、音频配置入口或排名配置。 - 手动上传:结果页通过平台资产直传 `/api/assets/direct-upload-tickets` 与 `/api/assets/objects/confirm` 写入私有资产,再把返回的历史 generated 路径写回草稿配置。 - 发布:结果页确认后必须携带草稿返回的同一个 `workId` 和结果页最终 `publishedSnapshot` 调用 `POST /api/creation/bark-battle/works/publish`;SpacetimeDB 发布态的 `config_json` 必须使用该最终快照,works summary 若拿到 `publishedSnapshotJson` 也优先使用最终快照映射封面三图。发布成功后先进入统一作品详情页,再由详情页进入正式 runtime;缺少 `workId` 的旧草稿状态需要重新生成草稿。 diff --git a/docs/【玩法创作】拼图生成页进度口径-2026-05-23.md b/docs/【玩法创作】拼图生成页进度口径-2026-05-23.md new file mode 100644 index 00000000..bc0ae2a0 --- /dev/null +++ b/docs/【玩法创作】拼图生成页进度口径-2026-05-23.md @@ -0,0 +1,29 @@ +# 拼图生成页进度口径 + +更新时间:`2026-05-24` + +## 目标 + +拼图草稿生成页的步骤推进只跟随真实生成结果;步骤内部允许使用本地假进度增强等待反馈。未收到当前步骤完成信号前,生成页必须停留在当前步骤,不得按预计耗时自动跳到后续步骤。 + +## 落地口径 + +- 总进度和当前步骤内百分比可以按已耗时平滑增长,但进入生成页的初始帧必须从 `0%` 开始,非完成态最多停在 `98%`。 +- 未收到首个真实里程碑前,页面仍停留在当前步骤,总进度在 `0-88` 区间内平滑推进;收到 `88/94/96` 里程碑后,分别在 `88-94`、`94-96`、`96-98` 区间内推进,避免步骤不跳时总进度也停死。 +- 后端 `progressPercent` 低于 `88` 只作为当前会话状态记录,不得把生成页阶段推到首个图片里程碑;低于首个里程碑时页面仍按当前视图进入时间从 `0%` 平滑展示。 +- 步骤状态以真实阶段为准:`phase` / 后端会话进度 / 最终完成或失败回包才允许跨步。 +- 拼图生成页恢复持久化 `generationStatus=generating` 草稿时,展示进度使用“进入生成页的当前时间”作为 `startedAtMs`;不得再用作品摘要 `updatedAt` 推导展示起点,避免刷新后首帧直接跳到 `80%+`。 +- 拼图和抓大鹅等生成页从作品架 / 刷新恢复进入时,前端应把展示态生成状态重基准到进入页面的当前时间;后台 session 的 `progressPercent` 与历史里程碑只保留为状态事实,不得直接作为首帧总进度。 +- 当前步骤未完成时,后续步骤保持待处理;即使预计时间耗尽,也只能让当前步骤内部进度接近或达到上限,不能自动完成后续步骤。 +- 抓大鹅等非拼图小游戏的生成页也遵守初始帧 `0%`:没有后端资产计数时,当前步骤内假进度按玩法预计等待总时长从 `0` 平滑推进,不使用固定 `0.5` 这类常量起步。 +- 步骤卡片只展示标题和进度,不展示详细描述。 +- 生成拼图首图步骤按 4 分钟预估;完整 AI 重绘路径总预计时长为 448 秒,上传图且关闭 AI 重绘时跳过首图生成,仍为 208 秒。 + +## 验收 + +- `src/services/miniGameDraftGenerationProgress.test.ts` 覆盖拼图步骤不会单纯按时间推进。 +- `src/services/miniGameDraftGenerationProgress.test.ts` 覆盖后端 `progressPercent < 88` 时不会抬高进入生成页的初始总进度。 +- `src/services/miniGameDraftGenerationProgress.test.ts` 覆盖抓大鹅等非拼图生成页初始总进度为 `0%`。 +- `src/components/CustomWorldGenerationView.test.tsx` 覆盖步骤详情不在生成页渲染。 +- `src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating"` 覆盖刷新后继续生成中拼图 / 抓大鹅草稿不会继承旧 `updatedAt` 导致总进度首帧过高。 +- 文档主图谱的拼图章节同步保留该口径。 diff --git a/docs/【玩法创作】生成页圆环布局口径-2026-05-23.md b/docs/【玩法创作】生成页圆环布局口径-2026-05-23.md new file mode 100644 index 00000000..8b5730a9 --- /dev/null +++ b/docs/【玩法创作】生成页圆环布局口径-2026-05-23.md @@ -0,0 +1,28 @@ +# 生成页圆环布局口径 + +更新时间:`2026-05-24` + +## 目标 + +所有玩法的生成页统一收敛为参考图同款等待态:顶部只保留返回入口和生成状态胶囊,页面中段用大圆环展示总进度,圆环左右悬浮“预计等待 / 已耗时”,下方只保留当前步骤单卡和当前作品信息卡,不再渲染步骤列表块。 + +## 落地口径 + +- 共用生成页 `CustomWorldGenerationView` 的主进度条使用居中 SVG 大圆弧,默认保留正下方 90 度留空,`media/create_bg_video.mp4` 作为固定全屏背景层循环静音播放;圆弧覆盖在背景之上展示总进度。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。 +- 生成页背景视频必须留在生成页容器内部,直接作为 `fixed inset-0` 的底层背景,不要再通过 portal 挂到 `document.body`;页面根容器使用 `z-[1]`、背景容器使用 `z-0`,确保顶部导航、圆环和当前步骤卡都稳定覆盖在视频之上。 +- 预计等待 / 已耗时信息卡要压缩为更轻的半透明窄卡,标签使用 `9px-10px`,数值使用 `12px-13px`,字号对齐其他生成页 UI 的小字号,不再使用偏大的提示文本;卡片标题和时间值都居中显示,两个数值只展示时间本身,调用侧不要再拼接“预计还需”或“已耗时”前缀。圆环中心不再保留独立白底块,空心圆环只保留条状进度,圆弧半径继续加大,进度数字与“总进度”标题整体上移,靠近圆环上半区。 +- 顶部导航区采用“返回创作中心 / 状态胶囊”结构,返回按钮使用左箭头图标,字号使用 `text-xs-sm`,状态胶囊使用 `11px-12px`,展示 `素材生成中`、`草稿生成中` 等调用侧传入文案。 +- 圆弧区域不再包独立大卡片,左右悬浮信息卡只展示“预计等待”和“已耗时”;总进度数值放在圆弧内侧偏上的位置并保持更小字号。当前圆环外径以 `w-[min(35rem,94vw)] sm:w-[52rem]` 为基准,圆弧使用 `r=166`、`strokeWidth=18` 的 SVG 描边,不再使用 `conic-gradient + mask`,避免进度条边缘模糊。 +- 从作品架或刷新后的持久化生成中草稿进入生成页时,前端必须重置“展示态 startedAtMs”为进入生成页的当前时间;后端 `progressPercent` 只用于后续真实步骤推进,不得参与首帧总进度展示,避免恢复生成页首帧直接显示 `80%+`。 +- 生成页只展示半透明“当前步骤”单卡,卡片内只保留步骤名称、步骤状态、步骤进度条和轻量加载指示;“当前步骤”标签使用 `10px-11px`,步骤名称使用 `14px-15px`,状态使用 `11px-12px`,不再渲染步骤列表或步骤详情。 +- 当前作品信息放在圆角信息卡中,标题固定使用 `13px`;有结构化字段时以两列信息块展示,例如“题材 / 素材数量”,无结构化字段时才展示纯文本设定。 +- 汪汪声浪生成页 `BarkBattleGeneratingView` 也必须对齐同一垂直布局,不再继续展示三行槽位列表或左右分栏抢占主视觉。 +- 汪汪声浪的总进度按三槽位已完成数量换算;当前步骤只显示第一个未完成槽位的名称与进度。 + +## 验收 + +- `src/components/CustomWorldGenerationView.test.tsx` 覆盖圆环主视觉和单步卡片。 +- `src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx` 覆盖汪汪声浪生成页对齐后的圆环布局。 +- 两个生成页都应在测试里断言页面根容器层级高于背景视频容器,且背景视频确实是页面子节点,避免 portal 背景把业务 UI 压住。 +- 还应断言圆弧正下方留空、圆环中心没有独立底色块,时间卡和总进度字号缩小后仍能在桌面与移动端正常排版;同时断言时间卡 `text-center`、标题行 `justify-center`、总进度内容区上移到 `pt-[4%]`,圆弧 DOM 为 SVG,包含清晰的 track/fill circle 描边。 +- 页面在桌面和移动端都不应再出现生成步骤列表块,圆环和当前步骤卡不能被外层卡片嵌套出双层面板感。 diff --git a/docs/【项目基线】当前产品与工程约束-2026-05-15.md b/docs/【项目基线】当前产品与工程约束-2026-05-15.md index e19cc841..8ea8b30f 100644 --- a/docs/【项目基线】当前产品与工程约束-2026-05-15.md +++ b/docs/【项目基线】当前产品与工程约束-2026-05-15.md @@ -93,9 +93,11 @@ server-rs + Axum + SpacetimeDB 7. 主站入口已锁定移动端页面级缩放;单个游戏页面不要再重复实现整页缩放锁定。 8. 图像输入通用 UI 统一走 `src/components/common/CreativeImageInputPanel.tsx`。外层页面持有业务状态,组件只承担上传卡、预览、参考图缩略图、AI 重绘开关、错误展示和提交按钮。 9. 发现页 `分类` 子频道的筛选必须打开独立 dialog / drawer / modal,至少支持玩法类型过滤与排序切换;筛选结果为空时显示空状态,不把筛选内容展开在当前列表下方。 -10. “我的”页泥点、游戏时长、玩过三张统计卡只展示各自标签和值,内容居中且不换行,不在统计区底部展示“更新于”时间。 -11. RPG 等运行态的战斗飘字、血量变化和即时反馈必须在暗色、噪声高的场景背景上保持可读:使用高亮文字、深色描边、强阴影或小面积半透明底,不只依赖红/绿文字本身表达伤害或治疗。 -11. 平台亮色 UI 配色以陶泥儿主视觉为准:暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;新增界面优先复用 `src/index.css` 的 `--platform-*` 主题变量和 `apps/admin-web/src/styles/admin.css` 的同系色值,不再引入粉红、蓝绿等独立主色方案。 +10. 移动端“我的”页按参考图顺序组织为顶部头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口、次级入口带和法律信息;`media/profile/` 中的陶泥素材作为该页图形资产。常用功能宫格固定承载泥点充值、邀请好友、兑换码、玩家社区、反馈与建议;存档和填邀请码保留在次级入口带,不挤入五宫格。 +11. “我的”页泥点、游戏时长、已玩游戏数量三张统计卡只展示各自标签和值,内容不换行,不在统计区底部展示“更新于”时间,字号维持平台普通 UI 档位;移动端昵称、会员卡、每日任务、常用功能和法律信息也应保持 `10px` 到 `14px` 的普通 UI 字号区间,避免展示级字号挤压内容。 +12. 移动端“我的”页需要兼容窄屏:头像 / 昵称 / 陶泥号、三张统计卡、每日任务、五项常用功能、次级入口和法律信息都必须能在底部固定 TabBar 上方完整滚动露出,不得与底部 dock、刘海 safe-area 或相邻 UI 元素遮挡重叠。 +13. RPG 等运行态的战斗飘字、血量变化和即时反馈必须在暗色、噪声高的场景背景上保持可读:使用高亮文字、深色描边、强阴影或小面积半透明底,不只依赖红/绿文字本身表达伤害或治疗。 +14. 平台亮色 UI 配色以陶泥儿主视觉为准:暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;新增界面优先复用 `src/index.css` 的 `--platform-*` 主题变量和 `apps/admin-web/src/styles/admin.css` 的同系色值,不再引入粉红、蓝绿等独立主色方案。 ## 文案与编码 diff --git a/media/create_bg_video.mp4 b/media/create_bg_video.mp4 new file mode 100644 index 00000000..d0c2095c Binary files /dev/null and b/media/create_bg_video.mp4 differ diff --git a/media/profile/_Image (1).png b/media/profile/_Image (1).png new file mode 100644 index 00000000..f86816b9 Binary files /dev/null and b/media/profile/_Image (1).png differ diff --git a/media/profile/_Image (2).png b/media/profile/_Image (2).png new file mode 100644 index 00000000..93dd45a3 Binary files /dev/null and b/media/profile/_Image (2).png differ diff --git a/media/profile/_Image (3).png b/media/profile/_Image (3).png new file mode 100644 index 00000000..33d971f2 Binary files /dev/null and b/media/profile/_Image (3).png differ diff --git a/media/profile/_Image (4).png b/media/profile/_Image (4).png new file mode 100644 index 00000000..7e62a727 Binary files /dev/null and b/media/profile/_Image (4).png differ diff --git a/media/profile/_Image (5).png b/media/profile/_Image (5).png new file mode 100644 index 00000000..deb8e0b5 Binary files /dev/null and b/media/profile/_Image (5).png differ diff --git a/media/profile/_Image (6).png b/media/profile/_Image (6).png new file mode 100644 index 00000000..d4d81c1d Binary files /dev/null and b/media/profile/_Image (6).png differ diff --git a/media/profile/_Image (7).png b/media/profile/_Image (7).png new file mode 100644 index 00000000..e806455b Binary files /dev/null and b/media/profile/_Image (7).png differ diff --git a/media/profile/_Image (8).png b/media/profile/_Image (8).png new file mode 100644 index 00000000..8348e99b Binary files /dev/null and b/media/profile/_Image (8).png differ diff --git a/media/profile/_Image (9).png b/media/profile/_Image (9).png new file mode 100644 index 00000000..4b22f1cf Binary files /dev/null and b/media/profile/_Image (9).png differ diff --git a/media/profile/_Image.png b/media/profile/_Image.png new file mode 100644 index 00000000..0b9d446c Binary files /dev/null and b/media/profile/_Image.png differ diff --git a/output/generation-page-mobile-check.png b/output/generation-page-mobile-check.png new file mode 100644 index 00000000..698572e9 Binary files /dev/null and b/output/generation-page-mobile-check.png differ diff --git a/output/rpg-profile-test-report.json b/output/rpg-profile-test-report.json new file mode 100644 index 00000000..485b4cec --- /dev/null +++ b/output/rpg-profile-test-report.json @@ -0,0 +1,652 @@ +{ + "numTotalTestSuites": 1, + "numPassedTestSuites": 1, + "numFailedTestSuites": 0, + "numPendingTestSuites": 0, + "numTotalTests": 58, + "numPassedTests": 50, + "numFailedTests": 8, + "numPendingTests": 0, + "numTodoTests": 0, + "startTime": 1779633396424, + "success": false, + "testResults": [ + { + "assertionResults": [ + { + "ancestorTitles": [ + "" + ], + "fullName": " opens wallet ledger modal from narrative coin card", + "status": "passed", + "title": "opens wallet ledger modal from narrative coin card", + "duration": 157, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile recharge modal shows native qr code on desktop web by default", + "status": "passed", + "title": "profile recharge modal shows native qr code on desktop web by default", + "duration": 183, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile recharge modal jumps to h5 payment on mobile web by default", + "status": "passed", + "title": "profile recharge modal jumps to h5 payment on mobile web by default", + "duration": 203, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile recharge modal trusts per-product first bonus display after points recharge", + "status": "passed", + "title": "profile recharge modal trusts per-product first bonus display after points recharge", + "duration": 120, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile recharge modal posts requestPayment params in mini program web-view", + "status": "passed", + "title": "profile recharge modal posts requestPayment params in mini program web-view", + "duration": 237, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile recharge modal waits for paid confirmation before refreshing dashboard", + "status": "passed", + "title": "profile recharge modal waits for paid confirmation before refreshing dashboard", + "duration": 1059, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile recharge modal loads wechat js sdk before mini program payment bridge", + "status": "passed", + "title": "profile recharge modal loads wechat js sdk before mini program payment bridge", + "duration": 374, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile recharge modal releases submitting state after cancelled wechat pay result", + "status": "passed", + "title": "profile recharge modal releases submitting state after cancelled wechat pay result", + "duration": 388, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile native qr confirmation refreshes only after server reports paid", + "status": "passed", + "title": "profile native qr confirmation refreshes only after server reports paid", + "duration": 421, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " non-wechat profile shows reward code instead of recharge entry", + "status": "failed", + "title": "non-wechat profile shows reward code instead of recharge entry", + "duration": 63, + "failureMessages": [ + "expected to be null" + ], + "location": { + "line": 1805, + "column": 5 + } + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile daily task shortcut opens task center and claims reward", + "status": "passed", + "title": "profile daily task shortcut opens task center and claims reward", + "duration": 392, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile total play time card always uses hours", + "status": "passed", + "title": "profile total play time card always uses hours", + "duration": 83, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile played works card shows count unit", + "status": "passed", + "title": "profile played works card shows count unit", + "duration": 73, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile stats cards are centered without update timestamp", + "status": "passed", + "title": "profile stats cards are centered without update timestamp", + "duration": 113, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " mobile profile page matches the reference layout sections", + "status": "failed", + "title": "mobile profile page matches the reference layout sections", + "duration": 1045, + "failureMessages": [ + "expected \"spy\" to be called 1 times, but got 0 times\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m陶泥\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[0m儿\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGENARRATIVE\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m搜索\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m测\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m测试玩家\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m100001\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m陶泥\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[0m儿\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGENARRATIVE\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m陶泥\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[0m儿\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGENARRATIVE\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m邀请好友\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m双方得 30 泥点\u001b[0m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m" + ], + "location": { + "line": 37, + "column": 19 + } + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile redeem invite shortcut sits between invite and community for fresh accounts", + "status": "failed", + "title": "profile redeem invite shortcut sits between invite and community for fresh accounts", + "duration": 209, + "failureMessages": [ + "expected +0 to be truthy" + ], + "location": { + "line": 2058, + "column": 5 + } + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile redeem invite shortcut hides after redeemed or one day old", + "status": "passed", + "title": "profile redeem invite shortcut hides after redeemed or one day old", + "duration": 226, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " invite query opens login modal for logged out users", + "status": "passed", + "title": "invite query opens login modal for logged out users", + "duration": 20, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " invite query opens redeem modal directly for logged in users", + "status": "passed", + "title": "invite query opens redeem modal directly for logged in users", + "duration": 154, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile redeem invite modal reads query invite code after login", + "status": "passed", + "title": "profile redeem invite modal reads query invite code after login", + "duration": 71, + "failureMessages": [] + }, + { + "ancestorTitles": [ + "" + ], + "fullName": " profile redeem invite modal submits code and hides shortcut after success", + "status": "failed", + "title": "profile redeem invite modal submits code and hides shortcut after success", + "duration": 1054, + "failureMessages": [ + "Unable to find role=\"button\" and name `/填邀请码/u`\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m陶泥\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[0m儿\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGENARRATIVE\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m陶泥\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[0m儿\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGENARRATIVE\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m陶泥\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[0m儿\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGENARRATIVE\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m搜索\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m测\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m测试玩家\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m100001\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-clay-seed.png b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-clay-seed.png deleted file mode 100644 index b204cdcb..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-clay-seed.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-clay-seed.svg b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-clay-seed.svg deleted file mode 100644 index 83afaa50..00000000 --- a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-clay-seed.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-dot-face.png b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-dot-face.png deleted file mode 100644 index 8c2a2c71..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-dot-face.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-dot-face.svg b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-dot-face.svg deleted file mode 100644 index 28dfbae7..00000000 --- a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-dot-face.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-mold-baby.png b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-mold-baby.png deleted file mode 100644 index 232354cd..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-mold-baby.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-mold-baby.svg b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-mold-baby.svg deleted file mode 100644 index 8e8663cc..00000000 --- a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-mold-baby.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-soft-totem.png b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-soft-totem.png deleted file mode 100644 index fa6b72cd..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-soft-totem.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-soft-totem.svg b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-soft-totem.svg deleted file mode 100644 index 12507cfc..00000000 --- a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-soft-totem.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-work-puppet.png b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-work-puppet.png deleted file mode 100644 index e066e780..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-work-puppet.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-work-puppet.svg b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-work-puppet.svg deleted file mode 100644 index cdbce26f..00000000 --- a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-abstract-mascot-work-puppet.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-logo-abstract-mascot-contact-sheet.png b/public/branding/taonier-logo-abstract-mascot-concepts/taonier-logo-abstract-mascot-contact-sheet.png deleted file mode 100644 index ed90840b..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-concepts/taonier-logo-abstract-mascot-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-clay-pocket-token.png b/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-clay-pocket-token.png deleted file mode 100644 index 8d5c2341..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-clay-pocket-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-clay-spirit-glyph.png b/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-clay-spirit-glyph.png deleted file mode 100644 index cc632ad9..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-clay-spirit-glyph.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-mold-blob-companion.png b/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-mold-blob-companion.png deleted file mode 100644 index c94a4883..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-mold-blob-companion.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-pinched-seed-mascot.png b/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-pinched-seed-mascot.png deleted file mode 100644 index aa532f78..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-pinched-seed-mascot.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-soft-totem-creature.png b/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-soft-totem-creature.png deleted file mode 100644 index eff2d606..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-soft-totem-creature.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-work-core-puppet.png b/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-work-core-puppet.png deleted file mode 100644 index 15c01392..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-image2-work-core-puppet.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-logo-abstract-mascot-image2-contact-sheet.png b/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-logo-abstract-mascot-image2-contact-sheet.png deleted file mode 100644 index fd7e6242..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-image2-concepts/taonier-logo-abstract-mascot-image2-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-logo-abstract-mascot-minimal-contact-sheet.png b/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-logo-abstract-mascot-minimal-contact-sheet.png deleted file mode 100644 index 64265b7b..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-logo-abstract-mascot-minimal-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-clay-core.png b/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-clay-core.png deleted file mode 100644 index ebdb8271..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-clay-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-clay-token.png b/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-clay-token.png deleted file mode 100644 index ec9e5b81..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-clay-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-mold-bud.png b/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-mold-bud.png deleted file mode 100644 index c4821821..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-mold-bud.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-seed-glyph.png b/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-seed-glyph.png deleted file mode 100644 index 02382e2a..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-minimal-concepts/taonier-minimal-seed-glyph.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-orb.png b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-orb.png deleted file mode 100644 index d7f955b9..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-orb.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-orb.svg b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-orb.svg deleted file mode 100644 index e750a4f6..00000000 --- a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-orb.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-sprite.png b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-sprite.png deleted file mode 100644 index d30d257f..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-sprite.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-sprite.svg b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-sprite.svg deleted file mode 100644 index 7cccc846..00000000 --- a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-clay-sprite.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-pinch-orbit.png b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-pinch-orbit.png deleted file mode 100644 index 2ebd29f3..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-pinch-orbit.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-pinch-orbit.svg b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-pinch-orbit.svg deleted file mode 100644 index 1868c073..00000000 --- a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-pinch-orbit.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-seed-totem.png b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-seed-totem.png deleted file mode 100644 index 64130ad6..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-seed-totem.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-seed-totem.svg b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-seed-totem.svg deleted file mode 100644 index 2c2a9848..00000000 --- a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-seed-totem.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-soft-mold.png b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-soft-mold.png deleted file mode 100644 index de237d1b..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-soft-mold.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-soft-mold.svg b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-soft-mold.svg deleted file mode 100644 index b5f0bc73..00000000 --- a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-soft-mold.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-work-glyph.png b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-work-glyph.png deleted file mode 100644 index 18ee0410..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-work-glyph.png and /dev/null differ diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-work-glyph.svg b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-work-glyph.svg deleted file mode 100644 index b403b89f..00000000 --- a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-abstract-mascot-v2-work-glyph.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-logo-abstract-mascot-v2-contact-sheet.png b/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-logo-abstract-mascot-v2-contact-sheet.png deleted file mode 100644 index aef75e68..00000000 Binary files a/public/branding/taonier-logo-abstract-mascot-v2-concepts/taonier-logo-abstract-mascot-v2-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-app-token.png b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-app-token.png deleted file mode 100644 index 189c193d..00000000 Binary files a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-app-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-app-token.svg b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-app-token.svg deleted file mode 100644 index 60c5380f..00000000 --- a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-app-token.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-clay-drop.png b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-clay-drop.png deleted file mode 100644 index 75427ba3..00000000 Binary files a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-clay-drop.png and /dev/null differ diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-clay-drop.svg b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-clay-drop.svg deleted file mode 100644 index 85836184..00000000 --- a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-clay-drop.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-core.png b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-core.png deleted file mode 100644 index 221ab801..00000000 Binary files a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-core.svg b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-core.svg deleted file mode 100644 index fa315c43..00000000 --- a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-core.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-creation-base.png b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-creation-base.png deleted file mode 100644 index f6455307..00000000 Binary files a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-creation-base.png and /dev/null differ diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-creation-base.svg b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-creation-base.svg deleted file mode 100644 index 2a526a39..00000000 --- a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-creation-base.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-soft-slab.png b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-soft-slab.png deleted file mode 100644 index b95f6d77..00000000 Binary files a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-soft-slab.png and /dev/null differ diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-soft-slab.svg b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-soft-slab.svg deleted file mode 100644 index 945b0e32..00000000 --- a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-soft-slab.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-work-stack.png b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-work-stack.png deleted file mode 100644 index 799de34c..00000000 Binary files a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-work-stack.png and /dev/null differ diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-work-stack.svg b/public/branding/taonier-logo-anchor-concepts/taonier-anchor-work-stack.svg deleted file mode 100644 index 78fde2f0..00000000 --- a/public/branding/taonier-logo-anchor-concepts/taonier-anchor-work-stack.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-anchor-concepts/taonier-logo-anchor-contact-sheet.png b/public/branding/taonier-logo-anchor-concepts/taonier-logo-anchor-contact-sheet.png deleted file mode 100644 index 1a9f0f77..00000000 Binary files a/public/branding/taonier-logo-anchor-concepts/taonier-logo-anchor-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-01-matte-clay-stamp.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-01-matte-clay-stamp.png deleted file mode 100644 index 0209b888..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-01-matte-clay-stamp.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-02-kiln-mark-core.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-02-kiln-mark-core.png deleted file mode 100644 index c7030159..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-02-kiln-mark-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-03-cutout-negative-star.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-03-cutout-negative-star.png deleted file mode 100644 index c0e800de..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-03-cutout-negative-star.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-04-dry-clay-grain.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-04-dry-clay-grain.png deleted file mode 100644 index c98ccf3a..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-04-dry-clay-grain.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-05-hand-pressed-token.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-05-hand-pressed-token.png deleted file mode 100644 index afffd04d..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-05-hand-pressed-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-06-digital-clay-glyph.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-06-digital-clay-glyph.png deleted file mode 100644 index 47c8e465..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-06-digital-clay-glyph.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-07-premium-flat-mark.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-07-premium-flat-mark.png deleted file mode 100644 index 39db1d7e..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-07-premium-flat-mark.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-08-monochrome-proof.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-08-monochrome-proof.png deleted file mode 100644 index 285a1828..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-anti-candy-08-monochrome-proof.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-logo-anti-candy-contact-sheet.png b/public/branding/taonier-logo-anti-candy-concepts/taonier-logo-anti-candy-contact-sheet.png deleted file mode 100644 index fa9cfd8f..00000000 Binary files a/public/branding/taonier-logo-anti-candy-concepts/taonier-logo-anti-candy-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-anti-candy-concepts/taonier-logo-anti-candy-manifest.json b/public/branding/taonier-logo-anti-candy-concepts/taonier-logo-anti-candy-manifest.json deleted file mode 100644 index 0b4c3d76..00000000 --- a/public/branding/taonier-logo-anti-candy-concepts/taonier-logo-anti-candy-manifest.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T18:03:02.694Z", - "creativeDirection": { - "name": "陶泥儿反糖果化脑洞泥印图形标", - "textPolicy": "no Chinese, no English, no wordmark", - "palette": "灰米白、陶土白、陶土褐、深泥灰、少量暗金土黄", - "motif": "哑光软方圆陶泥印章 + 星核凹印/负形 + 极少量刻点", - "antiCandyRules": "no glossy highlight, no cream filling, no jelly, no cookie, no chocolate, no candy star" - }, - "variants": [ - { - "id": "01-matte-clay-stamp", - "title": "哑光陶泥印章", - "file": "taonier-anti-candy-01-matte-clay-stamp.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:最克制的陶泥印章。灰米白软方圆主体,中间是压进去的暗陶土星核凹印,只有 2 个微小刻点。几乎无高光。" - }, - { - "id": "02-kiln-mark-core", - "title": "窑印星核", - "file": "taonier-anti-candy-02-kiln-mark-core.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:窑印感。中间星核像烧陶后的浅浮雕窑印,用深泥灰边缘和陶土褐阴影表现,不要任何金属或糖果光泽。" - }, - { - "id": "03-cutout-negative-star", - "title": "负形星核", - "file": "taonier-anti-candy-03-cutout-negative-star.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:负形。星核用干净的镂空负形或深泥灰内孔表达,主体是单块哑光陶泥,整体更像可注册商标图形。" - }, - { - "id": "04-dry-clay-grain", - "title": "干陶颗粒", - "file": "taonier-anti-candy-04-dry-clay-grain.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:干陶质感。加入非常细微的陶土颗粒和粉陶纹理,但保持扁平图标,不要照片写实,不要脏乱。" - }, - { - "id": "05-hand-pressed-token", - "title": "手压泥币", - "file": "taonier-anti-candy-05-hand-pressed-token.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:手压泥币。像一枚被手工压平的陶泥代币,边缘不完全对称,中间星核为凹刻符号,但不要出现手或工具。" - }, - { - "id": "06-digital-clay-glyph", - "title": "数字泥符", - "file": "taonier-anti-candy-06-digital-clay-glyph.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:AI 与 UGC 暗示更强。用 3 个极小方形刻点围绕星核,像生成节点,但必须像刻在陶泥上的小孔。" - }, - { - "id": "07-premium-flat-mark", - "title": "精品扁平标", - "file": "taonier-anti-candy-07-premium-flat-mark.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:更互联网精品。减少纹理,强化几何平衡和负形,灰米白主体、深泥灰星核、陶土褐小刻痕,适合 App 图标。" - }, - { - "id": "08-monochrome-proof", - "title": "单色验证版", - "file": "taonier-anti-candy-08-monochrome-proof.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须反糖果化:第一眼必须像哑光陶泥、泥章、窑印、创作印记,绝对不能像糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力或食品包装。\n核心隐喻:脑洞泥印。一块被轻轻捏平的不规则软方圆陶泥印章,中间有一个抽象星核凹印或镂空星形泥印,表达灵感被压印成作品。\n风格:扁平矢量商标为主,低高光、低饱和、哑光粉陶质感;轮廓清楚,可后续矢量化,适合商标、App 图标、社区头像。\n主色:灰米白、未经上釉的陶土白、陶土褐、深泥灰、少量暗金土黄;颜色必须干燥、克制、低甜度。不使用亮金、糖果黄、奶油黄、粉色、青色、蓝色、紫色、荧光色或彩虹色。\n形态:保留不规则软方圆,但不要鼓胀、不要胶状、不要可食用的圆润光泽。边缘可以有少量粗糙泥料纹理、压痕、手捏不均匀。\n数字感:只允许 2 到 3 个很小的深泥灰或暗土黄刻点,像生成节点或 UGC 扩散点;不要闪亮星星,不要糖珠。\n构图:正方形画布,居中图形标,干净浅灰米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和中间星核凹印。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、旋涡环形旧稿、三色花瓣旧稿。\n强禁止食品感:不要 glossy 高光、不要果冻质感、不要奶油夹心、不要糖霜、不要撒糖粒、不要饼干边、不要巧克力流心、不要金色膨胀星糖。\n本张重点:黑白商标验证。尽量用单色深浅关系表达软方圆和星核凹印,减少装饰,确保黑白化后轮廓仍成立。" - } - ] -} diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-01-minimal-braincore.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-01-minimal-braincore.png deleted file mode 100644 index 99674899..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-01-minimal-braincore.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-02-soft-square-clay-seal.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-02-soft-square-clay-seal.png deleted file mode 100644 index d8f385df..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-02-soft-square-clay-seal.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-03-warm-brown-embedded-core.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-03-warm-brown-embedded-core.png deleted file mode 100644 index f821f583..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-03-warm-brown-embedded-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-04-subtle-pinch-marks.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-04-subtle-pinch-marks.png deleted file mode 100644 index 0b106a60..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-04-subtle-pinch-marks.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-05-premium-geometric-balance.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-05-premium-geometric-balance.png deleted file mode 100644 index 9292cbe7..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-05-premium-geometric-balance.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-06-soft-clay-texture.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-06-soft-clay-texture.png deleted file mode 100644 index aa717de5..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-06-soft-clay-texture.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-07-app-icon-ready.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-07-app-icon-ready.png deleted file mode 100644 index 9994dc61..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-07-app-icon-ready.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-08-trademark-monochrome-ready.png b/public/branding/taonier-logo-braincore-concepts/taonier-braincore-08-trademark-monochrome-ready.png deleted file mode 100644 index d9758eba..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-braincore-08-trademark-monochrome-ready.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-logo-braincore-contact-sheet.png b/public/branding/taonier-logo-braincore-concepts/taonier-logo-braincore-contact-sheet.png deleted file mode 100644 index 6bdf198a..00000000 Binary files a/public/branding/taonier-logo-braincore-concepts/taonier-logo-braincore-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-braincore-concepts/taonier-logo-braincore-manifest.json b/public/branding/taonier-logo-braincore-concepts/taonier-logo-braincore-manifest.json deleted file mode 100644 index 5fba5f9c..00000000 --- a/public/branding/taonier-logo-braincore-concepts/taonier-logo-braincore-manifest.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T17:45:55.245Z", - "creativeDirection": { - "name": "陶泥儿脑洞星核图形标", - "textPolicy": "no Chinese, no English, no wordmark", - "palette": "奶白、米白、暖棕、陶土棕、少量暖金", - "motif": "不规则软方圆陶泥团 + 脑洞星核 + 极少量星点" - }, - "variants": [ - { - "id": "01-minimal-braincore", - "title": "极简脑洞星核", - "file": "taonier-braincore-01-minimal-braincore.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:极简。只保留一个奶白不规则软方圆陶泥主体、一个居中的暖金四角星核、2 个极小暖金星点。不要额外装饰。" - }, - { - "id": "02-soft-square-clay-seal", - "title": "软方圆陶泥印记", - "file": "taonier-braincore-02-soft-square-clay-seal.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:陶泥印记。主体像被轻轻按压成型的软方圆印章,边缘有自然手捏起伏,但不能像儿童玩具。星核略微偏心。" - }, - { - "id": "03-warm-brown-embedded-core", - "title": "暖棕星核嵌入", - "file": "taonier-braincore-03-warm-brown-embedded-core.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:嵌入感。用暖棕内凹形或陶土棕阴影承托暖金星核,像灵感被嵌进陶泥里,仍保持扁平商标质感。" - }, - { - "id": "04-subtle-pinch-marks", - "title": "轻微捏痕版本", - "file": "taonier-braincore-04-subtle-pinch-marks.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:捏痕。在陶泥主体边缘加入 2 到 3 个极轻微暖棕捏痕或凹口,表现可塑性;捏痕必须抽象、克制、可矢量化。" - }, - { - "id": "05-premium-geometric-balance", - "title": "精品几何比例", - "file": "taonier-braincore-05-premium-geometric-balance.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:精品比例。整体更接近高级互联网 App 图标,几何平衡、负形干净、软方圆轮廓稳定,陶泥质感只保留一点点。" - }, - { - "id": "06-soft-clay-texture", - "title": "柔软陶泥质感", - "file": "taonier-braincore-06-soft-clay-texture.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:柔软质感。在不破坏扁平矢量感的前提下,加入细腻奶油陶泥的微妙高光和暖棕渐层,不能变成 3D 玩具。" - }, - { - "id": "07-app-icon-ready", - "title": "App 图标优先", - "file": "taonier-braincore-07-app-icon-ready.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:App 图标。图形占画面约 72%,轮廓饱满有记忆点,星核清晰醒目,适合放入圆角方形 App icon。" - }, - { - "id": "08-trademark-monochrome-ready", - "title": "商标黑白提炼", - "file": "taonier-braincore-08-trademark-monochrome-ready.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n核心隐喻:脑洞星核。一团奶白色、暖棕色调的不规则软方圆陶泥包裹一枚暖金色创意星核,表达灵感被捏造成型。\n风格:扁平矢量商标为主,带非常轻微的软陶质感;结构简洁、边缘柔和、轮廓清晰,适合商标、App 图标、社区头像。\n主色:奶白、米白、暖棕、陶土棕、少量暖金;整体温暖高级,不使用粉色、青色、蓝色、紫色、荧光色或彩虹色。\n数字感:只允许在星核周围出现 2 到 4 个极小暖金星点或像素点,暗示 AI 生成、UGC 传播和轻游戏趣味。\n构图:正方形画布,居中图形标,干净浅米白背景,留足安全边距;缩小到 64px 时仍能看清软方圆轮廓和星核。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、儿童黏土玩具感、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标、拟物甜品感。\n必须保持全新构图,不参考任何既有陶泥儿 logo 方案,不做旋涡环形旧稿,不做三色花瓣旧稿。\n本张重点:商标注册。优先保证黑白化后仍清楚,减少渐变和细节,用奶白主体、暖棕负形和暖金星核形成强轮廓。" - } - ] -} diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-01-closed-curve-mark.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-01-closed-curve-mark.png deleted file mode 100644 index 35ef5de9..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-01-closed-curve-mark.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-02-friendly-geo-seed.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-02-friendly-geo-seed.png deleted file mode 100644 index 9678ad88..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-02-friendly-geo-seed.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-03-premium-soft-contour.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-03-premium-soft-contour.png deleted file mode 100644 index bfbeedbc..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-03-premium-soft-contour.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-04-playful-closed-tile.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-04-playful-closed-tile.png deleted file mode 100644 index 513ff218..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-04-playful-closed-tile.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-05-monochrome-first.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-05-monochrome-first.png deleted file mode 100644 index 0f3fac18..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-05-monochrome-first.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-06-digital-clay-accent.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-06-digital-clay-accent.png deleted file mode 100644 index f096d762..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-06-digital-clay-accent.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-07-compact-avatar-symbol.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-07-compact-avatar-symbol.png deleted file mode 100644 index 77a78203..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-07-compact-avatar-symbol.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-08-designer-vector-ready.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-08-designer-vector-ready.png deleted file mode 100644 index 982814f4..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-08-designer-vector-ready.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-contact-sheet.png b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-contact-sheet.png deleted file mode 100644 index 10d4d76a..00000000 Binary files a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-manifest.json b/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-manifest.json deleted file mode 100644 index 6a013631..00000000 --- a/public/branding/taonier-logo-brief-concepts/taonier-logo-brief-manifest.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T20:18:49.419Z", - "logoSkillSummary": { - "requiredReview": "visual inspection, 32px readability, black-white viability", - "outputStatus": "AI concept only; final logo needs vector cleanup" - }, - "brief": { - "brand": "陶泥儿", - "logoType": "symbol/icon-only mark, no wordmark", - "product": "AI UGC 轻休闲小游戏创作与传播平台,用户像捏陶泥一样把脑洞、梗和灵感塑造成作品", - "personality": [ - "亲和", - "精品", - "创作感", - "轻松", - "年轻", - "有传播记忆点" - ], - "mustHave": [ - "闭合不规则几何底盘", - "外轮廓由流畅曲线组成", - "整体是一个完整符号而不是自由飘带", - "32px 仍能识别", - "黑白化后仍成立", - "无中文、无英文、无字标" - ], - "avoid": [ - "整体方形或圆角方块", - "中心星星或任何星形", - "自由飘带、旋涡、S/G 字母感", - "巧克力面包、甜点、饼干、糖果等食物感", - "砖块、土块、泥饼、陶片、考古印章", - "脸、表情、吉祥物、手、工具" - ] - }, - "variants": [ - { - "id": "01-closed-curve-mark", - "title": "闭合曲线标", - "file": "taonier-logo-brief-01-closed-curve-mark.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: the cleanest closed irregular curve mark. Use two large color areas separated by one smooth internal curve. Maximize 32px readability." - }, - { - "id": "02-friendly-geo-seed", - "title": "亲和几何种", - "file": "taonier-logo-brief-02-friendly-geo-seed.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: a friendly seed-like closed geometric base, but not a literal seed, not food. Rounded and approachable with one teal accent curve." - }, - { - "id": "03-premium-soft-contour", - "title": "精品软轮廓", - "file": "taonier-logo-brief-03-premium-soft-contour.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: premium, calm, fewer colors. Strong outer contour with a dark mud-gray internal negative curve. Very logo-like, not illustrative." - }, - { - "id": "04-playful-closed-tile", - "title": "轻玩闭合片", - "file": "taonier-logo-brief-04-playful-closed-tile.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: a more playful closed irregular tile with warm terracotta and ceramic white. The internal curve should suggest creation flow, not filling." - }, - { - "id": "05-monochrome-first", - "title": "黑白优先", - "file": "taonier-logo-brief-05-monochrome-first.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: design as if it will be converted to black and white. Use bold positive and negative shapes; color only supports the structure." - }, - { - "id": "06-digital-clay-accent", - "title": "数字陶泥点", - "file": "taonier-logo-brief-06-digital-clay-accent.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: include at most two tiny geometric accent dots or notches that imply AI/UGC, but they must not look like candy sprinkles or decorative confetti." - }, - { - "id": "07-compact-avatar-symbol", - "title": "头像紧凑标", - "file": "taonier-logo-brief-07-compact-avatar-symbol.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: compact social-avatar readability. The closed contour should be slightly fuller and more iconic, but not a rounded-square app background." - }, - { - "id": "08-designer-vector-ready", - "title": "矢量定稿感", - "file": "taonier-logo-brief-08-designer-vector-ready.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand personality: friendly, premium, creative, lightweight, young, memorable, suitable for an AI UGC casual game creation platform.\nCore metaphor: users shape imagination like clay. The logo should communicate soft creative material becoming a refined digital product symbol.\nLogo type: abstract symbol/icon only. Not an emblem with text, not a mascot, not an app-icon rounded-square background.\nMain element: one closed irregular geometric base shape made from smooth flowing curves. The outer contour must be closed, continuous, recognizable, organic, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, or loose strip. It should feel like a refined custom symbol with 5-7 soft curve turns.\nInternal design: use only 1-2 broad smooth curve partitions or negative-shape cuts to make the mark memorable. No center icon inserted. No star, no spark, no hole shaped like a star.\nStyle: modern minimalist vector logo with very subtle matte clay warmth. Clean edges, broad shapes, high contrast, no tiny details. It must look good and recognizable at 32px favicon size.\nColor: warm ceramic white, light terracotta, clay orange, warm brown, with optional small low-saturation teal, indigo-gray, or dark mud gray accent. Avoid sweet candy colors. No glossy highlights.\nFood avoidance is critical: the mark must not look like bread, chocolate bread, croissant, pastry, cookie, candy, donut, cream filling, sauce, baked dough, dessert, or food packaging.\nMaterial avoidance: do not make it look like brick, dirt clod, mud pie, pottery shard, stone, archaeological stamp, or rough handmade craft class object.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the large shape should be recognizable at 32px.\nVariant focus: make it look like a designer-ready vector concept: 2-3 flat shapes, crisp boundaries, distinctive closed outer contour, minimal material texture." - } - ] -} diff --git a/public/branding/taonier-logo-broad-concepts/taonier-broad-creation-spark.png b/public/branding/taonier-logo-broad-concepts/taonier-broad-creation-spark.png deleted file mode 100644 index 163fc3b3..00000000 Binary files a/public/branding/taonier-logo-broad-concepts/taonier-broad-creation-spark.png and /dev/null differ diff --git a/public/branding/taonier-logo-broad-concepts/taonier-broad-game-mold.png b/public/branding/taonier-logo-broad-concepts/taonier-broad-game-mold.png deleted file mode 100644 index 619b54f1..00000000 Binary files a/public/branding/taonier-logo-broad-concepts/taonier-broad-game-mold.png and /dev/null differ diff --git a/public/branding/taonier-logo-broad-concepts/taonier-broad-soft-portal.png b/public/branding/taonier-logo-broad-concepts/taonier-broad-soft-portal.png deleted file mode 100644 index 6442e7c4..00000000 Binary files a/public/branding/taonier-logo-broad-concepts/taonier-broad-soft-portal.png and /dev/null differ diff --git a/public/branding/taonier-logo-broad-concepts/taonier-broad-soft-totem.png b/public/branding/taonier-logo-broad-concepts/taonier-broad-soft-totem.png deleted file mode 100644 index b1ec5c3f..00000000 Binary files a/public/branding/taonier-logo-broad-concepts/taonier-broad-soft-totem.png and /dev/null differ diff --git a/public/branding/taonier-logo-broad-concepts/taonier-broad-work-embryo.png b/public/branding/taonier-logo-broad-concepts/taonier-broad-work-embryo.png deleted file mode 100644 index 6239fcc2..00000000 Binary files a/public/branding/taonier-logo-broad-concepts/taonier-broad-work-embryo.png and /dev/null differ diff --git a/public/branding/taonier-logo-broad-concepts/taonier-logo-broad-contact-sheet.png b/public/branding/taonier-logo-broad-concepts/taonier-logo-broad-contact-sheet.png deleted file mode 100644 index f57edfe7..00000000 Binary files a/public/branding/taonier-logo-broad-concepts/taonier-logo-broad-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-creator-totem.png b/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-creator-totem.png deleted file mode 100644 index 99c54fc5..00000000 Binary files a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-creator-totem.png and /dev/null differ diff --git a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-figurine-token.png b/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-figurine-token.png deleted file mode 100644 index 6b340f6f..00000000 Binary files a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-figurine-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-idol-mask.png b/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-idol-mask.png deleted file mode 100644 index 8f79f462..00000000 Binary files a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-idol-mask.png and /dev/null differ diff --git a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-little-maker.png b/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-little-maker.png deleted file mode 100644 index 889e2ba1..00000000 Binary files a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-little-maker.png and /dev/null differ diff --git a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-pocket-figure.png b/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-pocket-figure.png deleted file mode 100644 index fdc93722..00000000 Binary files a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-pocket-figure.png and /dev/null differ diff --git a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-soft-doll.png b/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-soft-doll.png deleted file mode 100644 index cd1dc043..00000000 Binary files a/public/branding/taonier-logo-clay-mascot-concepts/taonier-clay-mascot-soft-doll.png and /dev/null differ diff --git a/public/branding/taonier-logo-clay-mascot-concepts/taonier-logo-clay-mascot-contact-sheet.png b/public/branding/taonier-logo-clay-mascot-concepts/taonier-logo-clay-mascot-contact-sheet.png deleted file mode 100644 index c5372711..00000000 Binary files a/public/branding/taonier-logo-clay-mascot-concepts/taonier-logo-clay-mascot-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-01-organic-closed-badge.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-01-organic-closed-badge.png deleted file mode 100644 index c0c2efd3..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-01-organic-closed-badge.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-02-smooth-clay-shield.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-02-smooth-clay-shield.png deleted file mode 100644 index f25bea89..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-02-smooth-clay-shield.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-03-asymmetric-pebble-glyph.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-03-asymmetric-pebble-glyph.png deleted file mode 100644 index 9a2fe06a..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-03-asymmetric-pebble-glyph.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-04-inlaid-curve-plate.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-04-inlaid-curve-plate.png deleted file mode 100644 index 9337dc00..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-04-inlaid-curve-plate.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-05-flat-vector-symbol.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-05-flat-vector-symbol.png deleted file mode 100644 index 31a79304..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-05-flat-vector-symbol.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-06-friendly-solid-form.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-06-friendly-solid-form.png deleted file mode 100644 index 8e90a44e..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-06-friendly-solid-form.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-07-digital-clay-panel.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-07-digital-clay-panel.png deleted file mode 100644 index 26c06170..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-07-digital-clay-panel.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-08-trademark-ready-contour.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-08-trademark-ready-contour.png deleted file mode 100644 index 2c250fe3..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-closed-geo-08-trademark-ready-contour.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-logo-closed-geo-contact-sheet.png b/public/branding/taonier-logo-closed-geo-concepts/taonier-logo-closed-geo-contact-sheet.png deleted file mode 100644 index e1f291be..00000000 Binary files a/public/branding/taonier-logo-closed-geo-concepts/taonier-logo-closed-geo-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-closed-geo-concepts/taonier-logo-closed-geo-manifest.json b/public/branding/taonier-logo-closed-geo-concepts/taonier-logo-closed-geo-manifest.json deleted file mode 100644 index a7ef4ecd..00000000 --- a/public/branding/taonier-logo-closed-geo-concepts/taonier-logo-closed-geo-manifest.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T18:57:38.302Z", - "creativeDirection": { - "name": "陶泥儿闭合不规则几何底盘图形标", - "textPolicy": "no Chinese, no English, no wordmark", - "correction": "closed irregular smooth geometry, not free ribbon, not food, not square base", - "motif": "闭合曲线几何底盘 + 内部曲线分区 + 轻陶泥温度" - }, - "variants": [ - { - "id": "01-organic-closed-badge", - "title": "有机闭合徽形", - "file": "taonier-closed-geo-01-organic-closed-badge.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:最基础的闭合不规则几何体。暖陶白主体,陶土橙内部曲线切分,外轮廓像柔和抽象鹅卵石但不是圆形。" - }, - { - "id": "02-smooth-clay-shield", - "title": "柔曲陶盾", - "file": "taonier-closed-geo-02-smooth-clay-shield.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:更稳定的品牌底盘。外轮廓略像柔化盾形或种子形,但没有尖角;内部一条孔雀青曲线增加识别度。" - }, - { - "id": "03-asymmetric-pebble-glyph", - "title": "非对称陶符", - "file": "taonier-closed-geo-03-asymmetric-pebble-glyph.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:非对称但平衡。闭合外轮廓左右不一样,有 6 个柔和转折点,内部用深泥灰负形曲线形成品牌记忆。" - }, - { - "id": "04-inlaid-curve-plate", - "title": "嵌曲陶牌", - "file": "taonier-closed-geo-04-inlaid-curve-plate.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:内部嵌色曲线。像一块闭合陶牌上嵌入一条平滑色带,色带不能像馅料、巧克力或奶油夹心。" - }, - { - "id": "05-flat-vector-symbol", - "title": "扁平矢量符", - "file": "taonier-closed-geo-05-flat-vector-symbol.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:最扁平、最商标化。减少材质,只用 2 到 3 个大色块形成闭合不规则几何符号,线条极简。" - }, - { - "id": "06-friendly-solid-form", - "title": "亲和实体形", - "file": "taonier-closed-geo-06-friendly-solid-form.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:亲和力。闭合底盘像一个柔软、温和、完整的小世界,但不是角色、不是脸、不是食物。" - }, - { - "id": "07-digital-clay-panel", - "title": "数字陶泥面", - "file": "taonier-closed-geo-07-digital-clay-panel.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:AI/UGC 暗示。闭合底盘内有 2 到 3 个很小的几何刻点或短曲线节点,但不能像电路板,也不能像撒糖。" - }, - { - "id": "08-trademark-ready-contour", - "title": "商标轮廓款", - "file": "taonier-closed-geo-08-trademark-ready-contour.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次的核心要求:底盘必须是由流畅曲线组成的「闭合不规则几何体图案」。它要像一个完整的品牌徽形轮廓,而不是自由飘带、旋涡、S 形、G 形、开放环或散开的泥条。\n外轮廓:闭合、连续、平滑、非方形、非圆形、非椭圆、非圆角方块。可以有 5 到 7 个柔和转折点,像自然捏出来的抽象几何石/软陶徽形,但必须干净、可矢量化。\n内部结构:可以用 1 到 2 条平滑曲线切分色块或形成负形,但内部结构必须服务整体闭合底盘,不能出现中心星星、中心洞、脸、眼睛、嘴巴或角色感。\n识别方向:通过闭合外轮廓和内部曲线分区形成记忆点,不靠星形、不靠文字、不靠食物质感。远看要像互联网产品 logo。\n材质与风格:现代扁平矢量商标,极轻微陶泥温度;低高光、低拟物、干净边缘。不要 3D 面包感,不要巧克力、面团、糕点、糖果、饼干或烘焙质感。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主体;允许少量低饱和孔雀青、靛蓝灰或深泥灰作为内部曲线色块,占比 6% 到 15%。整体要温暖、有吸引力,但不甜、不像食品。\n构图:正方形画布,居中图形标,图形占画面约 68% 到 76%,干净浅色背景,留足安全边距。缩小到 64px 时仍能看出闭合外轮廓和内部曲线分区。\n强禁止:自由飘带、开口环、旋涡、S 形、G 形、云朵、面包、巧克力面包、可颂、甜甜圈、糖果、饼干、糕点、奶油、夹心、亮油高光、烘焙纹理、食物摄影感。\n强禁止:整体方形底、圆角方块、中心星星、任何星形、闪光、徽章文字、印章文字、脸、表情、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:可注册轮廓。优先保证黑白化后的闭合外轮廓和内部曲线仍有辨识度,避免渐变和复杂纹理。" - } - ] -} diff --git a/public/branding/taonier-logo-concepts/taonier-clay-spark.png b/public/branding/taonier-logo-concepts/taonier-clay-spark.png deleted file mode 100644 index dddc5be4..00000000 Binary files a/public/branding/taonier-logo-concepts/taonier-clay-spark.png and /dev/null differ diff --git a/public/branding/taonier-logo-concepts/taonier-creation-loop.png b/public/branding/taonier-logo-concepts/taonier-creation-loop.png deleted file mode 100644 index fa4a1ad3..00000000 Binary files a/public/branding/taonier-logo-concepts/taonier-creation-loop.png and /dev/null differ diff --git a/public/branding/taonier-logo-concepts/taonier-logo-contact-sheet.png b/public/branding/taonier-logo-concepts/taonier-logo-contact-sheet.png deleted file mode 100644 index 39379c44..00000000 Binary files a/public/branding/taonier-logo-concepts/taonier-logo-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-concepts/taonier-meme-bubble.png b/public/branding/taonier-logo-concepts/taonier-meme-bubble.png deleted file mode 100644 index 4ac66ce1..00000000 Binary files a/public/branding/taonier-logo-concepts/taonier-meme-bubble.png and /dev/null differ diff --git a/public/branding/taonier-logo-concepts/taonier-play-mold.png b/public/branding/taonier-logo-concepts/taonier-play-mold.png deleted file mode 100644 index 05abdcc8..00000000 Binary files a/public/branding/taonier-logo-concepts/taonier-play-mold.png and /dev/null differ diff --git a/public/branding/taonier-logo-concepts/taonier-premium-seal.png b/public/branding/taonier-logo-concepts/taonier-premium-seal.png deleted file mode 100644 index 80634e3b..00000000 Binary files a/public/branding/taonier-logo-concepts/taonier-premium-seal.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-01-teal-core-pop.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-01-teal-core-pop.png deleted file mode 100644 index 963d4d1a..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-01-teal-core-pop.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-02-indigo-cut-mark.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-02-indigo-cut-mark.png deleted file mode 100644 index a6a10d20..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-02-indigo-cut-mark.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-03-cinnabar-clay-spark.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-03-cinnabar-clay-spark.png deleted file mode 100644 index 3ca851d9..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-03-cinnabar-clay-spark.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-04-bold-outline-token.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-04-bold-outline-token.png deleted file mode 100644 index a6657d8a..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-04-bold-outline-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-05-clay-pixel-seed.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-05-clay-pixel-seed.png deleted file mode 100644 index 0f952fe9..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-05-clay-pixel-seed.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-06-dynamic-squircle.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-06-dynamic-squircle.png deleted file mode 100644 index a607f183..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-06-dynamic-squircle.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-07-app-store-icon.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-07-app-store-icon.png deleted file mode 100644 index b770f607..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-07-app-store-icon.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-08-trademark-flat-glyph.png b/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-08-trademark-flat-glyph.png deleted file mode 100644 index 29b08dac..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-distinctive-08-trademark-flat-glyph.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-logo-distinctive-contact-sheet.png b/public/branding/taonier-logo-distinctive-concepts/taonier-logo-distinctive-contact-sheet.png deleted file mode 100644 index 65a8389f..00000000 Binary files a/public/branding/taonier-logo-distinctive-concepts/taonier-logo-distinctive-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-distinctive-concepts/taonier-logo-distinctive-manifest.json b/public/branding/taonier-logo-distinctive-concepts/taonier-logo-distinctive-manifest.json deleted file mode 100644 index 53148e50..00000000 --- a/public/branding/taonier-logo-distinctive-concepts/taonier-logo-distinctive-manifest.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T18:18:06.003Z", - "creativeDirection": { - "name": "陶泥儿高辨识可塑星核图形标", - "textPolicy": "no Chinese, no English, no wordmark", - "palette": "暖陶白/浅陶土主体 + 8%-18% 孔雀青、靛蓝灰、朱砂橙或暗金土黄点缀", - "motif": "强轮廓软方圆 + 独特可塑星核 + 少量 AI/UGC 刻点", - "correction": "avoid candy, avoid brick, avoid plain mud stamp, increase brand recognition" - }, - "variants": [ - { - "id": "01-teal-core-pop", - "title": "孔雀青星核", - "file": "taonier-distinctive-01-teal-core-pop.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:孔雀青识别点。暖陶白软方圆主体,中间是孔雀青负形可塑星核,少量陶土褐压痕,整体清爽年轻。" - }, - { - "id": "02-indigo-cut-mark", - "title": "靛蓝切口", - "file": "taonier-distinctive-02-indigo-cut-mark.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:靛蓝灰切口。用一条干净的靛蓝灰泥痕切出中心星核,让图形远看有强剪影,不能像旋涡旧稿。" - }, - { - "id": "03-cinnabar-clay-spark", - "title": "朱砂陶火", - "file": "taonier-distinctive-03-cinnabar-clay-spark.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:朱砂橙活力。中心星核或侧边小泥片使用低饱和朱砂橙,像创作火花,但整体保持陶泥材质和精品克制。" - }, - { - "id": "04-bold-outline-token", - "title": "强轮廓泥符", - "file": "taonier-distinctive-04-bold-outline-token.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:强轮廓。用深泥灰细描边或深色负形强化外轮廓和中心符号,确保黑白化后仍有高辨识度。" - }, - { - "id": "05-clay-pixel-seed", - "title": "像素创作种", - "file": "taonier-distinctive-05-clay-pixel-seed.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:AI/UGC 暗示。中心星核周围有 3 个小方形刻点,像生成像素从陶泥里浮现,但不要复杂电路线。" - }, - { - "id": "06-dynamic-squircle", - "title": "动态软方圆", - "file": "taonier-distinctive-06-dynamic-squircle.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:动态轮廓。外形不是静态泥块,而像正在被捏动的软方圆,有一个明显但简洁的非对称记忆点。" - }, - { - "id": "07-app-store-icon", - "title": "应用图标款", - "file": "taonier-distinctive-07-app-store-icon.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:App Store 图标。构图饱满、中心符号强、背景干净,视觉冲击比泥章更强,但不出现文字和脸。" - }, - { - "id": "08-trademark-flat-glyph", - "title": "商标扁平符", - "file": "taonier-distinctive-08-trademark-flat-glyph.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次目标是高辨识度和传播吸引力:第一眼要像一个有记忆点的互联网产品 logo,而不是普通泥块、砖块、陶片、印章、饼干或糖果。\n核心隐喻:可塑星核。一个独特的「软方圆陶泥外轮廓 + 中心可塑星核」符号,星核像脑洞被捏出的一瞬间,造型要有专属轮廓,不能是普通五角星或普通四角闪光。\n风格:现代扁平矢量商标,带轻微哑光陶泥质感;边缘干净、对比清楚、轮廓强,适合 App 图标、社区头像、启动页和商标注册前期筛选。\n材质:要能看出陶泥温度,但不要过度粗糙、不要砖、不要土块、不要考古泥片。质感只做轻量表面颗粒和柔和压痕。\n配色:以暖陶白或浅陶土色为主体,加入一个高识别点缀色。允许使用低饱和孔雀青、靛蓝灰、朱砂橙、暗金土黄中的一种;点缀色只占 8% 到 18%。整体要更醒目、更年轻,但不要糖果色。\n图案:中心星核必须是品牌记忆点,可用负形、嵌色、切口、泥痕、像素刻点形成独特轮廓;周围最多 2 到 3 个小刻点暗示 AI/UGC 扩散。\n构图:正方形画布,居中图形标,图形占画面约 70% 到 78%,干净浅色背景,留足安全边距。缩小到 64px 时仍然能认出外轮廓和中心符号。\n禁止:脸、眼睛、嘴巴、表情、角色身体、吉祥物立绘、陶艺工具、手、笔刷、复杂场景、按钮、UI、边框、水印、文字、商标字标。\n强禁止低辨识:不要普通圆泥饼、不要普通砖块、不要石头、不要考古印章、不要单纯凹星孔、不要灰扑扑单色泥片。\n强禁止食品感:不要糖果、软糖、奶油、饼干、夹心甜点、月饼、巧克力、果冻高光、亮金膨胀星糖。\n本张重点:最终商标潜力。减少材质和阴影,以 2 到 3 个大色块形成独特符号,保留陶泥可塑感和中心可塑星核。" - } - ] -} diff --git a/public/branding/taonier-logo-flat-concepts/taonier-flat-loop-mold.png b/public/branding/taonier-logo-flat-concepts/taonier-flat-loop-mold.png deleted file mode 100644 index 22f8d58f..00000000 Binary files a/public/branding/taonier-logo-flat-concepts/taonier-flat-loop-mold.png and /dev/null differ diff --git a/public/branding/taonier-logo-flat-concepts/taonier-flat-meme-smile.png b/public/branding/taonier-logo-flat-concepts/taonier-flat-meme-smile.png deleted file mode 100644 index 01522897..00000000 Binary files a/public/branding/taonier-logo-flat-concepts/taonier-flat-meme-smile.png and /dev/null differ diff --git a/public/branding/taonier-logo-flat-concepts/taonier-flat-play-clay.png b/public/branding/taonier-logo-flat-concepts/taonier-flat-play-clay.png deleted file mode 100644 index 20d59156..00000000 Binary files a/public/branding/taonier-logo-flat-concepts/taonier-flat-play-clay.png and /dev/null differ diff --git a/public/branding/taonier-logo-flat-concepts/taonier-flat-seal-blocks.png b/public/branding/taonier-logo-flat-concepts/taonier-flat-seal-blocks.png deleted file mode 100644 index 3c361068..00000000 Binary files a/public/branding/taonier-logo-flat-concepts/taonier-flat-seal-blocks.png and /dev/null differ diff --git a/public/branding/taonier-logo-flat-concepts/taonier-flat-spark-clay.png b/public/branding/taonier-logo-flat-concepts/taonier-flat-spark-clay.png deleted file mode 100644 index 517ea03f..00000000 Binary files a/public/branding/taonier-logo-flat-concepts/taonier-flat-spark-clay.png and /dev/null differ diff --git a/public/branding/taonier-logo-flat-concepts/taonier-logo-flat-contact-sheet.png b/public/branding/taonier-logo-flat-concepts/taonier-logo-flat-contact-sheet.png deleted file mode 100644 index 7ca0380b..00000000 Binary files a/public/branding/taonier-logo-flat-concepts/taonier-logo-flat-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-01-soft-ribbon-loop.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-01-soft-ribbon-loop.png deleted file mode 100644 index d06a853e..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-01-soft-ribbon-loop.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-02-clay-wave-knot.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-02-clay-wave-knot.png deleted file mode 100644 index 61258708..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-02-clay-wave-knot.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-03-imagination-ripple.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-03-imagination-ripple.png deleted file mode 100644 index 230b1387..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-03-imagination-ripple.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-04-friendly-clay-comet.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-04-friendly-clay-comet.png deleted file mode 100644 index ae505519..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-04-friendly-clay-comet.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-05-single-stroke-blob.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-05-single-stroke-blob.png deleted file mode 100644 index a111d64d..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-05-single-stroke-blob.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-06-two-tone-soft-flow.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-06-two-tone-soft-flow.png deleted file mode 100644 index fb60deeb..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-06-two-tone-soft-flow.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-07-open-clay-orbit.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-07-open-clay-orbit.png deleted file mode 100644 index bbdc922f..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-07-open-clay-orbit.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-flow-08-brand-flow-glyph.png b/public/branding/taonier-logo-flow-concepts/taonier-flow-08-brand-flow-glyph.png deleted file mode 100644 index d1da646a..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-flow-08-brand-flow-glyph.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-logo-flow-contact-sheet.png b/public/branding/taonier-logo-flow-concepts/taonier-logo-flow-contact-sheet.png deleted file mode 100644 index 5730f898..00000000 Binary files a/public/branding/taonier-logo-flow-concepts/taonier-logo-flow-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-flow-concepts/taonier-logo-flow-manifest.json b/public/branding/taonier-logo-flow-concepts/taonier-logo-flow-manifest.json deleted file mode 100644 index e7c0fbfd..00000000 --- a/public/branding/taonier-logo-flow-concepts/taonier-logo-flow-manifest.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T18:34:58.786Z", - "creativeDirection": { - "name": "陶泥儿连续曲线亲和图形标", - "textPolicy": "no Chinese, no English, no wordmark", - "correction": "no square base, no center star, use one continuous friendly clay curve structure", - "motif": "柔泥回环、脑洞涟漪、开放泥环、品牌曲线符" - }, - "variants": [ - { - "id": "01-soft-ribbon-loop", - "title": "柔泥回环", - "file": "taonier-flow-01-soft-ribbon-loop.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:一条宽厚柔软的陶泥带形成开放回环,像脑洞被揉开。轮廓连续,内部负形自然,不出现星形或方形底。" - }, - { - "id": "02-clay-wave-knot", - "title": "陶泥波结", - "file": "taonier-flow-02-clay-wave-knot.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:像一个温和波浪结,由 2 条互相捏合的平滑曲线组成,但必须视觉上像一个整体符号,不要碎片化。" - }, - { - "id": "03-imagination-ripple", - "title": "脑洞涟漪", - "file": "taonier-flow-03-imagination-ripple.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:用一整块陶泥曲面形成涟漪状大轮廓,中间负形像柔和水滴或脑洞入口,不能像星星。" - }, - { - "id": "04-friendly-clay-comet", - "title": "亲和泥流", - "file": "taonier-flow-04-friendly-clay-comet.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:亲和泥流。整体像一个流动的陶泥小世界,前端圆润、尾部自然收束,有动势但不尖锐。" - }, - { - "id": "05-single-stroke-blob", - "title": "单笔团块", - "file": "taonier-flow-05-single-stroke-blob.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:单笔成型。像用一笔连续曲线捏出的陶泥团,结构极简但有记忆点,适合后续矢量化。" - }, - { - "id": "06-two-tone-soft-flow", - "title": "双色软流", - "file": "taonier-flow-06-two-tone-soft-flow.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:双色曲线。暖陶白主体配少量孔雀青或陶土橙内侧曲线,让图形更吸引人,但不能变成多片拼贴。" - }, - { - "id": "07-open-clay-orbit", - "title": "开放泥环", - "file": "taonier-flow-07-open-clay-orbit.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:开放式泥环。不是闭合圆,也不是旋涡旧稿,而是一枚有缺口和呼吸感的平滑陶泥环形符号。" - }, - { - "id": "08-brand-flow-glyph", - "title": "品牌曲线符", - "file": "taonier-flow-08-brand-flow-glyph.png", - "prompt": "请生成一枚全新的「陶泥儿」产品商标图形标概念稿,但画面中绝对不要出现任何中文字、英文字母、数字、符号字样或品牌字标。\n产品定位:精品 AI UGC 轻休闲小游戏创作与传播平台,用户可以像捏陶泥一样把脑洞、梗和灵感塑造成小游戏或趣味内容。\n这次必须彻底放弃方形底和中心星星:不要整体方形、不要圆角方块、不要 App 图标底板、不要中间星形、不要五角星、不要四角星、不要闪光、不要中心挖孔星。\n核心隐喻:连续的陶泥曲线。用一整块顺滑、亲和、可塑的陶泥大结构来表达「脑洞被揉开、灵感流动、内容成型」,图形要像一个完整的流动符号,而不是底板加中心图案。\n主结构:由平滑曲线组成的大轮廓,可以像柔软泥流、脑洞涟漪、捏合的回环、温和的旋拧、单线团块或开放结。整体必须连贯、圆润、有亲和力,不能分裂成多个碎片。\n风格:现代扁平矢量商标,轻微哑光陶泥质感;边缘干净、曲线饱满、负形明确,适合商标、App 图标、社区头像和启动页。\n配色:暖陶白、浅陶土、陶土橙、暖棕为主,可加入少量低饱和孔雀青或深泥灰作为曲线内侧阴影或小连接点。颜色要温暖、有吸引力,但不要糖果色。\n识别度:远看必须能记住一个独特的大曲线轮廓;不要靠中心图案识别。缩小到 64px 时仍能看出整体曲线姿态。\n亲和力:整体像可以被揉捏的柔软小世界,有陪伴感和创作感,但不要做成脸、表情、角色或吉祥物。\n禁止:方形底、圆角方块、中心星星、任何星形、闪光、徽章、印章、砖块、泥饼、石头、陶片、饼干、糖果、手、陶艺工具、笔刷、复杂场景、按钮、UI、边框、水印、文字。\n本张重点:最终品牌符号潜力。减少材质和细节,用 1 到 2 个大曲线色块形成独特、亲和、可注册的图形标。" - } - ] -} diff --git a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-dot-dice.png b/public/branding/taonier-logo-fresh-concepts/taonier-fresh-dot-dice.png deleted file mode 100644 index c0aaea2b..00000000 Binary files a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-dot-dice.png and /dev/null differ diff --git a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-mold-window.png b/public/branding/taonier-logo-fresh-concepts/taonier-fresh-mold-window.png deleted file mode 100644 index c493fd70..00000000 Binary files a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-mold-window.png and /dev/null differ diff --git a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-pocket-world.png b/public/branding/taonier-logo-fresh-concepts/taonier-fresh-pocket-world.png deleted file mode 100644 index 390361bf..00000000 Binary files a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-pocket-world.png and /dev/null differ diff --git a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-punch-hole.png b/public/branding/taonier-logo-fresh-concepts/taonier-fresh-punch-hole.png deleted file mode 100644 index 4cba08fc..00000000 Binary files a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-punch-hole.png and /dev/null differ diff --git a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-stage-window.png b/public/branding/taonier-logo-fresh-concepts/taonier-fresh-stage-window.png deleted file mode 100644 index fc41e804..00000000 Binary files a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-stage-window.png and /dev/null differ diff --git a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-wheel-imprint.png b/public/branding/taonier-logo-fresh-concepts/taonier-fresh-wheel-imprint.png deleted file mode 100644 index 996ff9dd..00000000 Binary files a/public/branding/taonier-logo-fresh-concepts/taonier-fresh-wheel-imprint.png and /dev/null differ diff --git a/public/branding/taonier-logo-fresh-concepts/taonier-logo-fresh-contact-sheet.png b/public/branding/taonier-logo-fresh-concepts/taonier-logo-fresh-contact-sheet.png deleted file mode 100644 index 50b5a0cf..00000000 Binary files a/public/branding/taonier-logo-fresh-concepts/taonier-logo-fresh-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dot-gate.png b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dot-gate.png deleted file mode 100644 index 4df8762f..00000000 Binary files a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dot-gate.png and /dev/null differ diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dot-gate.svg b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dot-gate.svg deleted file mode 100644 index 2f2b6910..00000000 --- a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dot-gate.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dual-plate.png b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dual-plate.png deleted file mode 100644 index 3063ec82..00000000 Binary files a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dual-plate.png and /dev/null differ diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dual-plate.svg b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dual-plate.svg deleted file mode 100644 index 604555ac..00000000 --- a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-dual-plate.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-mold-chip.png b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-mold-chip.png deleted file mode 100644 index b137b791..00000000 Binary files a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-mold-chip.png and /dev/null differ diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-mold-chip.svg b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-mold-chip.svg deleted file mode 100644 index 5fbc3145..00000000 --- a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-mold-chip.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-offset-core.png b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-offset-core.png deleted file mode 100644 index fb25bc93..00000000 Binary files a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-offset-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-offset-core.svg b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-offset-core.svg deleted file mode 100644 index dc99f8ca..00000000 --- a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-offset-core.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-pinched-tile.png b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-pinched-tile.png deleted file mode 100644 index 6a80aa9a..00000000 Binary files a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-pinched-tile.png and /dev/null differ diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-pinched-tile.svg b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-pinched-tile.svg deleted file mode 100644 index 02c12aba..00000000 --- a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-pinched-tile.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-work-knot.png b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-work-knot.png deleted file mode 100644 index eb4ca865..00000000 Binary files a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-work-knot.png and /dev/null differ diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-work-knot.svg b/public/branding/taonier-logo-geometric-concepts/taonier-geometric-work-knot.svg deleted file mode 100644 index 7e0eea94..00000000 --- a/public/branding/taonier-logo-geometric-concepts/taonier-geometric-work-knot.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/public/branding/taonier-logo-geometric-concepts/taonier-logo-geometric-contact-sheet.png b/public/branding/taonier-logo-geometric-concepts/taonier-logo-geometric-contact-sheet.png deleted file mode 100644 index c9499943..00000000 Binary files a/public/branding/taonier-logo-geometric-concepts/taonier-logo-geometric-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-01-berry-aqua-pop.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-01-berry-aqua-pop.png deleted file mode 100644 index 6998e267..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-01-berry-aqua-pop.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-02-coral-lilac.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-02-coral-lilac.png deleted file mode 100644 index f6127a50..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-02-coral-lilac.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-03-mango-turquoise.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-03-mango-turquoise.png deleted file mode 100644 index fe048b76..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-03-mango-turquoise.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-04-neon-rose-mint.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-04-neon-rose-mint.png deleted file mode 100644 index e6b51446..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-04-neon-rose-mint.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-05-poppy-blue.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-05-poppy-blue.png deleted file mode 100644 index bac0c919..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-05-poppy-blue.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-06-violet-peach.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-06-violet-peach.png deleted file mode 100644 index 5a3266fe..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-06-violet-peach.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-07-flat-duotone.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-07-flat-duotone.png deleted file mode 100644 index d4275a70..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-07-flat-duotone.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-08-app-icon-bright.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-08-app-icon-bright.png deleted file mode 100644 index a633aa02..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-08-app-icon-bright.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-contact-sheet.png b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-contact-sheet.png deleted file mode 100644 index ad8e836f..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-manifest.json b/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-manifest.json deleted file mode 100644 index 4208aa7e..00000000 --- a/public/branding/taonier-logo-hand-spirit-bold-color-concepts/taonier-hand-spirit-bold-color-manifest.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "model": "gpt-image-2", - "endpoint": "/v1/images/edits", - "size": "1024x1024", - "referenceImage": "public\\branding\\taonier-logo-hand-spirit-ref01-logo-refine-concepts\\taonier-hand-spirit-ref01-logo-refine-01-flat-coral-cream.png", - "generatedAt": "2026-05-18T07:50:23.595Z", - "brief": { - "brand": "陶泥儿", - "goal": "更大胆、更吸引女生和年轻人的手托灵体 logo 配色探索", - "keep": "保留托举曲线与半球灵体结构,不加文字、不加脸、不加星星" - }, - "variants": [ - { - "id": "01-berry-aqua-pop", - "title": "莓粉青 aqua", - "file": "taonier-hand-spirit-bold-color-01-berry-aqua-pop.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: vivid raspberry pink semi-dome, bright coral side accent, fresh aqua or mint hand support, small cream negative gap. Bold, young, energetic, not sugary." - }, - { - "id": "02-coral-lilac", - "title": "珊瑚丁香", - "file": "taonier-hand-spirit-bold-color-02-coral-lilac.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: punchy coral-red semi-dome with warm pink accent, soft lilac-lavender hand support, tiny ivory separator. Feminine, fresh, and premium." - }, - { - "id": "03-mango-turquoise", - "title": "芒果松石", - "file": "taonier-hand-spirit-bold-color-03-mango-turquoise.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: bright mango-orange semi-dome, hot peach accent, turquoise hand support. High contrast and cheerful, but still flat and logo-like, not food-like." - }, - { - "id": "04-neon-rose-mint", - "title": "玫红薄荷", - "file": "taonier-hand-spirit-bold-color-04-neon-rose-mint.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: neon rose or magenta semi-dome, clean mint green hand support, warm ivory separator. Strong social-avatar memory, modern and playful." - }, - { - "id": "05-poppy-blue", - "title": "罂粟蓝调", - "file": "taonier-hand-spirit-bold-color-05-poppy-blue.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: saturated poppy orange-red semi-dome, cobalt or sky-blue support curve, cream separator. More graphic, bold, and youth-culture oriented." - }, - { - "id": "06-violet-peach", - "title": "紫桃撞色", - "file": "taonier-hand-spirit-bold-color-06-violet-peach.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: vivid violet-purple hand support with peach-orange semi-dome and pink accent. Keep the purple limited and crisp so the logo does not become a generic purple tech gradient." - }, - { - "id": "07-flat-duotone", - "title": "双色强记忆", - "file": "taonier-hand-spirit-bold-color-07-flat-duotone.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette and style: ultra-flat two-color version. Use one bold warm color for the spirit and one bold cool color for the hand. No highlight, no gradient, no shadow. Maximize trademark simplicity." - }, - { - "id": "08-app-icon-bright", - "title": "亮彩头像", - "file": "taonier-hand-spirit-bold-color-08-app-icon-bright.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a bolder, more attractive color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is more memorable social-platform color, more appealing to young users and women, while still premium and trademark-like.\nUse bold modern brand colors, not realistic material colors. Saturated colors are allowed, but they must read as clean brand color fields, not candy, dessert, food, jelly, bread, pastry, mochi, bun, sauce, or cosmetic packaging.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle app-logo polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette and style: brightest app-icon-friendly version. Use coral, hot pink, and aqua with only a very subtle broad gradient. Keep the mark bold and readable at small size." - } - ] -} diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent-manifest.json b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent-manifest.json deleted file mode 100644 index 01fe7825..00000000 --- a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent-manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "model": "gpt-image-2", - "endpoint": "/v1/images/edits", - "size": "1024x1024", - "source": "public\\branding\\taonier-logo-hand-spirit-concepts\\taonier-hand-spirit-01-gentle-hand-spirit.png", - "chromaSource": "public\\branding\\taonier-logo-hand-spirit-concepts\\taonier-hand-spirit-01-gentle-hand-spirit-transparent-source.png", - "finalOutput": "public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent.png", - "generatedAt": "2026-05-18T06:51:33.388Z", - "prompt": "Use the uploaded image as the exact edit target.\nPreserve the logo subject exactly: same abstract hand shape, same clay spirit shape, same proportions, same placement, same scale, same colors, same soft vector style.\nDo not redesign, simplify, recolor, crop, rotate, add details, remove highlights, change the hand, or change the clay spirit.\nReplace only the white/off-white background with a perfectly flat solid #00ff00 chroma-key background.\nThe background must be one uniform #00ff00 color with no shadows, gradients, texture, reflections, floor plane, border, or lighting variation.\nDo not use #00ff00 anywhere inside the logo subject.\nNo text, no watermark, no UI, no extra marks, no border.\nKeep crisp clean edges and generous safe area so the result can be converted into a transparent PNG." -} diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent-source.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent-source.png deleted file mode 100644 index e4749f22..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent-source.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent.png deleted file mode 100644 index cb84c46b..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit-transparent.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit.png deleted file mode 100644 index f7e97ca4..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-01-gentle-hand-spirit.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-02-sharing-palm.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-02-sharing-palm.png deleted file mode 100644 index d76ab2ea..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-02-sharing-palm.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-03-teal-support.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-03-teal-support.png deleted file mode 100644 index 52b04e49..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-03-teal-support.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-04-arched-spirit.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-04-arched-spirit.png deleted file mode 100644 index 84046fe0..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-04-arched-spirit.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-05-playful-offer.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-05-playful-offer.png deleted file mode 100644 index 014e1848..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-05-playful-offer.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-06-monochrome-first.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-06-monochrome-first.png deleted file mode 100644 index 96b07b92..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-06-monochrome-first.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-07-avatar-readable.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-07-avatar-readable.png deleted file mode 100644 index aaea7097..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-07-avatar-readable.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-08-vector-ready.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-08-vector-ready.png deleted file mode 100644 index 322a19aa..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-hand-spirit-08-vector-ready.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-logo-hand-spirit-contact-sheet.png b/public/branding/taonier-logo-hand-spirit-concepts/taonier-logo-hand-spirit-contact-sheet.png deleted file mode 100644 index ea47da42..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-concepts/taonier-logo-hand-spirit-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-concepts/taonier-logo-hand-spirit-manifest.json b/public/branding/taonier-logo-hand-spirit-concepts/taonier-logo-hand-spirit-manifest.json deleted file mode 100644 index 26d0bcab..00000000 --- a/public/branding/taonier-logo-hand-spirit-concepts/taonier-logo-hand-spirit-manifest.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T21:30:55.045Z", - "logoSkillSummary": { - "requiredReview": "visual inspection, 32px readability, black-white viability", - "outputStatus": "AI concept only; final logo needs vector cleanup" - }, - "brief": { - "brand": "陶泥儿", - "coreBelief": "好玩会创造", - "logoType": "symbol/icon-only mark, no wordmark", - "product": "AI UGC 轻休闲小游戏创作与传播平台,用户像捏陶泥一样把脑洞、梗和灵感塑造成可分享的作品", - "metaphor": "抽象化的手托举/递出一个软萌陶泥灵体", - "intent": [ - "托举", - "分享", - "传递", - "创作被捏成一个有生命感的小作品" - ], - "spiritShape": "不规则半球形陶泥灵体,参考黑底白色半圆拱形轮廓,但不照抄", - "audience": "女性用户友好、全年龄向、年轻明亮但不低幼", - "material": "只保留陶泥温度,不追求泥土质感", - "mustHave": [ - "手必须高度抽象,像托举曲线或掌形基座", - "陶泥灵体必须是主角,软萌但不出现脸", - "画面传达分享/传递,而不是供奉/宗教/医疗", - "32px 可识别", - "黑白化仍成立" - ] - }, - "variants": [ - { - "id": "01-gentle-hand-spirit", - "title": "温柔托举灵体", - "file": "taonier-hand-spirit-01-gentle-hand-spirit.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: the clearest version. A simple cream abstract palm curve holds a coral-peach semi-dome clay spirit. Friendly, iconic, and readable." - }, - { - "id": "02-sharing-palm", - "title": "分享掌形", - "file": "taonier-hand-spirit-02-sharing-palm.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: sharing intention. The abstract hand is slightly forward-facing, like offering the clay spirit outward, but still very simplified and not realistic." - }, - { - "id": "03-teal-support", - "title": "青绿托线", - "file": "taonier-hand-spirit-03-teal-support.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: use a clear soft teal support curve as the hand and a warm peach clay spirit above. Strong color memory, no food look." - }, - { - "id": "04-arched-spirit", - "title": "拱形泥灵", - "file": "taonier-hand-spirit-04-arched-spirit.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: emphasize the irregular semi-dome clay spirit shape from the reference: simple arched top, flatter base, slightly organic, no face." - }, - { - "id": "05-playful-offer", - "title": "轻玩递出", - "file": "taonier-hand-spirit-05-playful-offer.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: more playful and lively. The hand support suggests passing the spirit forward, with one broad curve only. Avoid decorative tiny details." - }, - { - "id": "06-monochrome-first", - "title": "黑白优先", - "file": "taonier-hand-spirit-06-monochrome-first.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: design for black-and-white survival first. Use strong positive and negative shapes so the hand and spirit remain readable without color." - }, - { - "id": "07-avatar-readable", - "title": "头像可读", - "file": "taonier-hand-spirit-07-avatar-readable.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: social avatar and favicon readability. Compact, bold silhouette, thicker hand curve, larger semi-dome spirit, no small parts." - }, - { - "id": "08-vector-ready", - "title": "矢量定稿感", - "file": "taonier-hand-spirit-08-vector-ready.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nMain metaphor: an abstract hand gently holding up and offering a cute soft clay spirit. The logo should communicate creation, sharing, passing forward, and a small idea becoming a lovable playable object.\nLogo type: abstract symbol/icon only. Not a mascot illustration, not an app-icon rounded-square background, not an emblem with text.\nComposition: one highly simplified hand-like support curve or palm base below, holding one soft clay spirit above. The hand must be abstract, smooth, and logo-like, not realistic fingers.\nClay spirit shape: a cute irregular semi-dome / half-blob / rounded arch form, inspired by a simple white arched half-circle silhouette on dark background, but transformed into a friendly original brand symbol.\nThe spirit should be soft and lovable without a face. No eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nThe hand gesture should feel like sharing or gently presenting, not worship, religion, medical care, begging, or holding a food item.\nStyle: modern minimalist vector logo. Clean broad shapes, clear silhouette, fresh bright colors, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make the spirit look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, cream filling, sauce, dessert, or food packaging.\nShape avoidance: no square base, no rounded-square app background, no free ribbon, no swirl, no S or G letter feeling, no center star.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the hand support and semi-dome clay spirit should be recognizable at 32px.\nVariant focus: designer-ready vector concept. 2-3 flat shapes, crisp boundaries, distinctive hand-support and clay-spirit silhouette, minimal material cue." - } - ] -} diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-01-dusty-rose-sage.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-01-dusty-rose-sage.png deleted file mode 100644 index 5f7f0199..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-01-dusty-rose-sage.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-02-smoke-blue-apricot.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-02-smoke-blue-apricot.png deleted file mode 100644 index cc05337c..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-02-smoke-blue-apricot.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-03-misty-lilac-clay.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-03-misty-lilac-clay.png deleted file mode 100644 index 8fb67ab9..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-03-misty-lilac-clay.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-04-butter-rose-tea.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-04-butter-rose-tea.png deleted file mode 100644 index 4c40e54e..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-04-butter-rose-tea.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-05-clay-blue-mint.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-05-clay-blue-mint.png deleted file mode 100644 index c10a8134..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-05-clay-blue-mint.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-06-powder-berry-cloud.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-06-powder-berry-cloud.png deleted file mode 100644 index 5098f639..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-06-powder-berry-cloud.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-07-sand-violet.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-07-sand-violet.png deleted file mode 100644 index 80c5d6ab..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-07-sand-violet.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-08-muted-duotone.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-08-muted-duotone.png deleted file mode 100644 index be7d9c0d..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-08-muted-duotone.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-contact-sheet.png b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-contact-sheet.png deleted file mode 100644 index c31e9b03..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-manifest.json b/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-manifest.json deleted file mode 100644 index 335ee780..00000000 --- a/public/branding/taonier-logo-hand-spirit-muted-color-concepts/taonier-hand-spirit-muted-color-manifest.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "model": "gpt-image-2", - "endpoint": "/v1/images/edits", - "size": "1024x1024", - "referenceImage": "public\\branding\\taonier-logo-hand-spirit-ref01-logo-refine-concepts\\taonier-hand-spirit-ref01-logo-refine-01-flat-coral-cream.png", - "generatedAt": "2026-05-18T08:25:13.194Z", - "brief": { - "brand": "陶泥儿", - "goal": "低饱和度但不寡淡的年轻向颜色探索", - "keep": "保留托举曲线与半球灵体结构,不加文字、不加脸、不加星星" - }, - "variants": [ - { - "id": "01-dusty-rose-sage", - "title": "雾玫鼠尾草", - "file": "taonier-hand-spirit-muted-color-01-dusty-rose-sage.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: dusty rose semi-dome, sage green support curve, warm ivory gap. Soft, modern, and feminine without being sweet." - }, - { - "id": "02-smoke-blue-apricot", - "title": "烟蓝杏橙", - "file": "taonier-hand-spirit-muted-color-02-smoke-blue-apricot.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: smoke blue support curve, pale apricot or muted peach semi-dome, cream separator. Calm, fresh, and suitable for young users." - }, - { - "id": "03-misty-lilac-clay", - "title": "雾紫陶土", - "file": "taonier-hand-spirit-muted-color-03-misty-lilac-clay.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: misty lilac support, soft terracotta clay spirit, off-white negative space. More boutique and refined, not purple-tech." - }, - { - "id": "04-butter-rose-tea", - "title": "黄油玫瑰茶", - "file": "taonier-hand-spirit-muted-color-04-butter-rose-tea.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: butter cream spirit, muted rose support curve, faint tea-green accent. Gentle, cozy, and premium with low saturation." - }, - { - "id": "05-clay-blue-mint", - "title": "陶蓝薄荷", - "file": "taonier-hand-spirit-muted-color-05-clay-blue-mint.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: clay orange or muted coral semi-dome, powder blue support, tiny mint accent. Softly playful but not heavy." - }, - { - "id": "06-powder-berry-cloud", - "title": "粉雾浆果", - "file": "taonier-hand-spirit-muted-color-06-powder-berry-cloud.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: powder berry semi-dome, cloud pink support curve, warm cream gap. Youthful, gentle, and more like a boutique brand than a toy." - }, - { - "id": "07-sand-violet", - "title": "砂紫奶雾", - "file": "taonier-hand-spirit-muted-color-07-sand-violet.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette: sand beige or pale almond spirit, muted violet support curve, soft cream separator. Quiet, tasteful, and logo-ready." - }, - { - "id": "08-muted-duotone", - "title": "低饱双色", - "file": "taonier-hand-spirit-muted-color-08-muted-duotone.png", - "prompt": "Use the uploaded logo as the structural reference. Keep the same icon-only mark structure: a soft semi-dome clay spirit above a smooth abstract hand-like support curve.\nCreate a low-saturation color exploration for a young women-friendly brand logo. No Chinese, no English, no letters, no numbers, no wordmark, no tagline, no text.\nPreserve the existing logo proportions, centered composition, abstract hand support, semi-dome spirit shape, and clean logo silhouette. Do not add a face, eyes, mouth, limbs, star, spark, UI, border, or watermark.\nThe goal is softer, more tasteful, more wearable brand color. The palette should feel like a modern lifestyle brand, not a toy, candy, dessert, or cosmetics ad.\nUse muted but attractive colors: dusty rose, misty lavender, sage green, smoke blue, butter cream, muted coral, terracotta clay, pale apricot. Keep the image warm and youthful without becoming loud.\nMake it flatter and clearer than the original reference: broad vector-friendly shapes, crisp edges, minimal gradients, no glossy highlight unless the variant explicitly asks for a subtle polish.\nKeep 32px readability and black-white viability. The mark must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nPalette and style: two-color muted duotone only. Use one subdued warm hue and one subdued cool hue. No shiny gloss, no intense contrast, no candy feeling." - } - ] -} diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-01-thin-outline-small-eyes.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-01-thin-outline-small-eyes.png deleted file mode 100644 index 9337613e..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-01-thin-outline-small-eyes.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-02-medium-outline-round-eyes.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-02-medium-outline-round-eyes.png deleted file mode 100644 index 3e064048..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-02-medium-outline-round-eyes.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-03-bold-outline-higher-eyes.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-03-bold-outline-higher-eyes.png deleted file mode 100644 index 98d05f8a..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-03-bold-outline-higher-eyes.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-04-warm-cocoa-outline.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-04-warm-cocoa-outline.png deleted file mode 100644 index aaf00a1a..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-04-warm-cocoa-outline.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-05-compact-avatar-cute.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-05-compact-avatar-cute.png deleted file mode 100644 index fffabed2..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-05-compact-avatar-cute.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-06-black-white-first.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-06-black-white-first.png deleted file mode 100644 index 52923768..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-06-black-white-first.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-07-soft-feminine-cute.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-07-soft-feminine-cute.png deleted file mode 100644 index 1b1cc20b..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-07-soft-feminine-cute.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-08-vector-ready-cute.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-08-vector-ready-cute.png deleted file mode 100644 index a70e5074..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-08-vector-ready-cute.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-contact-sheet.png b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-contact-sheet.png deleted file mode 100644 index 73cce156..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-manifest.json b/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-manifest.json deleted file mode 100644 index 26fc6d1a..00000000 --- a/public/branding/taonier-logo-hand-spirit-outline-eye-concepts/taonier-hand-spirit-outline-eye-manifest.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "model": "gpt-image-2", - "endpoint": "/v1/images/edits", - "size": "1024x1024", - "referenceImage": "public\\branding\\taonier-logo-hand-spirit-ref01-logo-refine-concepts\\taonier-hand-spirit-ref01-logo-refine-01-flat-coral-cream.png", - "generatedAt": "2026-05-18T08:56:23.597Z", - "brief": { - "brand": "陶泥儿", - "goal": "在上轮 01 的基础上加入描边和黑点眼睛,让标志更可爱", - "keep": "保留托举曲线与半球灵体结构,不加文字、不加星星、不改骨架" - }, - "variants": [ - { - "id": "01-thin-outline-small-eyes", - "title": "细描边小眼", - "file": "taonier-hand-spirit-outline-eye-01-thin-outline-small-eyes.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: the most restrained cute version. Use a thin warm outline and small matte black dot eyes with calm spacing." - }, - { - "id": "02-medium-outline-round-eyes", - "title": "中描边圆眼", - "file": "taonier-hand-spirit-outline-eye-02-medium-outline-round-eyes.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: medium outline thickness and slightly rounder dot eyes. Make the face read a touch more openly cute, but still minimal." - }, - { - "id": "03-bold-outline-higher-eyes", - "title": "粗描边高眼", - "file": "taonier-hand-spirit-outline-eye-03-bold-outline-higher-eyes.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: stronger bold outline and eyes placed a little higher on the upper dome, creating a sweeter peeking expression." - }, - { - "id": "04-warm-cocoa-outline", - "title": "暖可可描边", - "file": "taonier-hand-spirit-outline-eye-04-warm-cocoa-outline.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: use a warm cocoa or deep beige outline that makes the logo feel softer and more plush, with small centered black eyes." - }, - { - "id": "05-compact-avatar-cute", - "title": "头像可爱款", - "file": "taonier-hand-spirit-outline-eye-05-compact-avatar-cute.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: compact avatar readability. Enlarge the upper dome slightly, keep the hand support bold, and make the eyes more visible without adding any mouth." - }, - { - "id": "06-black-white-first", - "title": "黑白优先", - "file": "taonier-hand-spirit-outline-eye-06-black-white-first.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: black-and-white survival first. Make the outline and the eyes work clearly even if all color is removed. Very strong logo readability." - }, - { - "id": "07-soft-feminine-cute", - "title": "柔和少女感", - "file": "taonier-hand-spirit-outline-eye-07-soft-feminine-cute.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: a softer feminine-cute version. Keep the outline elegant and the eyes gentle; the whole mark should feel like a friendly brand mascot symbol." - }, - { - "id": "08-vector-ready-cute", - "title": "矢量定稿感", - "file": "taonier-hand-spirit-outline-eye-08-vector-ready-cute.png", - "prompt": "Use the uploaded logo as the exact structural reference. Keep the same icon-only composition: a soft semi-dome spirit above a smooth abstract hand-like support curve.\nCreate a cuter, more logo-like refinement. Add a clean outline around the whole symbol, and add two pure matte black dot eyes to the upper semi-dome part only.\nThe eyes must belong to the upper half-circle spirit, not the lower support curve. No nose, no mouth, no eyebrows, no blush, no limbs, no character body, no star, no spark, no text.\nDo not redesign the overall structure. Keep the same proportions, centered layout, and soft brand silhouette. Only make it more cute, more memorable, and more trademark-like.\nThe outline should feel clean, friendly, and brand-ready, not sticker-like, not cartoonish, not too thick unless the variant asks for it.\nUse the same soft warm palette family as the reference, but allow subtle low-saturation tweaks to improve charm and contrast. Keep it elegant and youthful, not loud.\nKeep 32px readability and black-white viability. It must still work as an app icon, social avatar, and trademark symbol.\nClean light background, generous safe area. Image-only logo concept.\nVariant focus: designer-ready vector concept. Clean crisp outline, balanced eye spacing, no decorative detail, very easy to trace into an SVG mark." - } - ] -} diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-01-flat-coral-cream.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-01-flat-coral-cream.png deleted file mode 100644 index ec552a7d..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-01-flat-coral-cream.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-02-warm-clay-premium.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-02-warm-clay-premium.png deleted file mode 100644 index dd5f35f2..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-02-warm-clay-premium.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-03-mint-support.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-03-mint-support.png deleted file mode 100644 index 8b3e5d06..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-03-mint-support.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-04-outline-vector.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-04-outline-vector.png deleted file mode 100644 index 2b8aba02..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-04-outline-vector.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-05-abstract-two-shape.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-05-abstract-two-shape.png deleted file mode 100644 index 5237ee53..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-05-abstract-two-shape.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-06-monochrome-first.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-06-monochrome-first.png deleted file mode 100644 index 713b2e33..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-06-monochrome-first.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-07-soft-gradient-premium.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-07-soft-gradient-premium.png deleted file mode 100644 index 49f19aff..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-07-soft-gradient-premium.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-08-compact-avatar.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-08-compact-avatar.png deleted file mode 100644 index 49a54015..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-hand-spirit-ref01-logo-refine-08-compact-avatar.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-logo-hand-spirit-ref01-logo-refine-contact-sheet.png b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-logo-hand-spirit-ref01-logo-refine-contact-sheet.png deleted file mode 100644 index 11fdd96a..00000000 Binary files a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-logo-hand-spirit-ref01-logo-refine-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-logo-hand-spirit-ref01-logo-refine-manifest.json b/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-logo-hand-spirit-ref01-logo-refine-manifest.json deleted file mode 100644 index 0a6922b8..00000000 --- a/public/branding/taonier-logo-hand-spirit-ref01-logo-refine-concepts/taonier-logo-hand-spirit-ref01-logo-refine-manifest.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "model": "gpt-image-2", - "endpoint": "/v1/images/edits", - "size": "1024x1024", - "referenceImage": "public\\branding\\taonier-logo-hand-spirit-concepts\\taonier-hand-spirit-01-gentle-hand-spirit.png", - "generatedAt": "2026-05-18T07:29:31.615Z", - "logoSkillSummary": { - "requiredReview": "visual inspection, 32px readability, black-white viability", - "outputStatus": "AI concept only; final logo needs vector cleanup" - }, - "brief": { - "brand": "陶泥儿", - "source": "基于 taonier-hand-spirit-01-gentle-hand-spirit 做商标化探索", - "logoType": "symbol/icon-only mark, no wordmark", - "product": "AI UGC 轻休闲小游戏创作与传播平台,用户把脑洞、梗和灵感塑造成可分享的作品", - "keep": [ - "上方软萌半球陶泥灵体", - "下方抽象托举手势/掌形曲线", - "托举、传递、分享的动作语义", - "无脸、无文字、无星星", - "亲和、精品、可用于商标和 App 图标" - ], - "explore": [ - "更扁平的纯色块版本", - "更精品的低饱和陶器色版本", - "更强线面结构版本", - "更抽象、更少形状的版本", - "更适合 32px 和黑白化的版本" - ], - "avoid": [ - "中文或英文字", - "眼睛、嘴巴、表情、角色身体", - "星星、闪光、魔法符号", - "真实手指、宗教托举、医疗护理感", - "面包、甜点、糖果、果冻、奶油、食物包装", - "复杂背景、边框、UI、按钮、水印" - ] - }, - "variants": [ - { - "id": "01-flat-coral-cream", - "title": "扁平珊瑚奶白", - "file": "taonier-hand-spirit-ref01-logo-refine-01-flat-coral-cream.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: the most direct flat-logo refinement. Use coral-orange clay spirit and cream hand support. Reduce the glossy highlight to nearly zero. Use 3-4 crisp flat shapes only." - }, - { - "id": "02-warm-clay-premium", - "title": "暖陶精品色", - "file": "taonier-hand-spirit-ref01-logo-refine-02-warm-clay-premium.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: warmer boutique clay palette. Muted terracotta, soft sand, and warm ivory. More mature and premium, with a compact iconic silhouette and no candy gloss." - }, - { - "id": "03-mint-support", - "title": "青绿托举线", - "file": "taonier-hand-spirit-ref01-logo-refine-03-mint-support.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: stronger color memory. Use a clear muted teal or mint support curve as the hand and a warm peach clay spirit above. Keep it flat, balanced, and not cosmetic." - }, - { - "id": "04-outline-vector", - "title": "线面商标", - "file": "taonier-hand-spirit-ref01-logo-refine-04-outline-vector.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: bolder trademark construction. Use a clean warm-brown contour line combined with flat fills. The outline should clarify the hand and spirit silhouette, modern rather than sticker-like." - }, - { - "id": "05-abstract-two-shape", - "title": "双形抽象", - "file": "taonier-hand-spirit-ref01-logo-refine-05-abstract-two-shape.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: higher abstraction. Reduce the mark to two dominant shapes: one semi-dome spirit and one sweeping hand support. Remove highlight details. Make the silhouette distinctive and vector-ready." - }, - { - "id": "06-monochrome-first", - "title": "黑白优先", - "file": "taonier-hand-spirit-ref01-logo-refine-06-monochrome-first.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: black-and-white survival first. Design with strong positive and negative shapes; color may be warm clay, but the mark must remain clear as a pure monochrome logo." - }, - { - "id": "07-soft-gradient-premium", - "title": "轻渐变精品", - "file": "taonier-hand-spirit-ref01-logo-refine-07-soft-gradient-premium.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: a polished but still logo-like version. Allow only very subtle broad gradients for premium softness. Remove small glossy highlights and avoid 3D rendering." - }, - { - "id": "08-compact-avatar", - "title": "头像强识别", - "file": "taonier-hand-spirit-ref01-logo-refine-08-compact-avatar.png", - "prompt": "Use the uploaded reference image as the composition and shape reference. Keep the same core structure: an abstract hand-like support curve below, gently holding a soft semi-dome clay spirit above.\nCreate a refined icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nPreserve the brand idea: fun creation, sharing, passing a small playable idea forward. The mark should feel like a small creative object being gently offered, not a scene or illustration.\nKeep the subject face-free: no eyes, no mouth, no expression, no limbs, no character body, no halo, no star, no spark.\nMake it more like a trademark and logo than the reference: fewer shapes, cleaner silhouette, clearer positive/negative structure, more vector-friendly curves, broad flat color regions, less glossy highlight, less painterly shadow.\nThe hand must remain abstract: a smooth palm-like support curve or bowl-like gesture, not realistic fingers, not a real human hand, not worship, not medical care.\nThe spirit must remain a soft irregular semi-dome or rounded clay core. It should not become a planet, helmet, bread, bun, mochi, dumpling, pastry, candy, jelly, sauce, dessert, or food package.\nStyle target: premium friendly logo mark, all-age and women-friendly, warm but not childish, suitable for app icon, social avatar, and trademark review.\nComposition: centered on a clean light background with generous safe area. Strong readable silhouette first. It must still read at 32px and in black and white.\nAvoid UI, border, sticker outline unless explicitly requested by the variant, background scene, watermark, extra marks, and any text.\nVariant focus: compact social-avatar readability. Enlarge the clay spirit slightly, thicken the hand support curve, reduce thin gaps, and keep the total mark bold at 32px." - } - ] -} diff --git a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-bowl.png b/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-bowl.png deleted file mode 100644 index 14b9e2d4..00000000 Binary files a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-bowl.png and /dev/null differ diff --git a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-clap.png b/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-clap.png deleted file mode 100644 index 36f5f5d0..00000000 Binary files a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-clap.png and /dev/null differ diff --git a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-cradle.png b/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-cradle.png deleted file mode 100644 index c4cfa074..00000000 Binary files a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-cradle.png and /dev/null differ diff --git a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-pop.png b/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-pop.png deleted file mode 100644 index 41682d9b..00000000 Binary files a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-pop.png and /dev/null differ diff --git a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-seal.png b/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-seal.png deleted file mode 100644 index 49e3363f..00000000 Binary files a/public/branding/taonier-logo-hands-concepts/taonier-hands-v2-seal.png and /dev/null differ diff --git a/public/branding/taonier-logo-hands-concepts/taonier-logo-hands-contact-sheet.png b/public/branding/taonier-logo-hands-concepts/taonier-logo-hands-contact-sheet.png deleted file mode 100644 index 9a7867b0..00000000 Binary files a/public/branding/taonier-logo-hands-concepts/taonier-logo-hands-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-magic-dot-concepts/taonier-logo-magic-dot-contact-sheet.png b/public/branding/taonier-logo-magic-dot-concepts/taonier-logo-magic-dot-contact-sheet.png deleted file mode 100644 index 7e757df6..00000000 Binary files a/public/branding/taonier-logo-magic-dot-concepts/taonier-logo-magic-dot-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-bloom.png b/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-bloom.png deleted file mode 100644 index 5303de6f..00000000 Binary files a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-bloom.png and /dev/null differ diff --git a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-mold.png b/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-mold.png deleted file mode 100644 index 142011c0..00000000 Binary files a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-mold.png and /dev/null differ diff --git a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-orbit.png b/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-orbit.png deleted file mode 100644 index d623a8eb..00000000 Binary files a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-orbit.png and /dev/null differ diff --git a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-seal.png b/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-seal.png deleted file mode 100644 index 781c298d..00000000 Binary files a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-seal.png and /dev/null differ diff --git a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-squish.png b/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-squish.png deleted file mode 100644 index d56b07ec..00000000 Binary files a/public/branding/taonier-logo-magic-dot-concepts/taonier-magic-dot-squish.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-logo-mascot-symbol-contact-sheet.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-logo-mascot-symbol-contact-sheet.png deleted file mode 100644 index e4257fac..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-logo-mascot-symbol-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-logo-mascot-symbol-manifest.json b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-logo-mascot-symbol-manifest.json deleted file mode 100644 index e9e220cd..00000000 --- a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-logo-mascot-symbol-manifest.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T21:47:40.073Z", - "logoSkillSummary": { - "requiredReview": "visual inspection, 32px readability, black-white viability", - "outputStatus": "AI concept only; final logo needs vector cleanup" - }, - "brief": { - "brand": "陶泥儿", - "coreBelief": "好玩会创造", - "logoType": "symbol/icon-only mascot mark, no wordmark", - "product": "AI UGC 轻休闲小游戏创作与传播平台,用户像捏陶泥一样把脑洞、梗和灵感塑造成可分享的作品", - "direction": "抽象吉祥物符号,从人形、精灵、怪物、动物等形态提炼", - "audience": "女性用户友好、全年龄向、年轻明亮但不低幼", - "mascotRules": [ - "必须是 logo 符号级别,不是完整角色立绘", - "轮廓要有记忆点,32px 可读", - "表情最多极简两点或无表情", - "身体结构要高度抽象、可矢量化", - "保留一点陶泥被捏出的柔软感" - ], - "avoid": [ - "复杂角色", - "儿童玩具感", - "怪物恐怖感", - "真实动物", - "食品感", - "文字", - "星星", - "写实泥土纹理" - ] - }, - "variants": [ - { - "id": "01-clay-humanoid", - "title": "抽象人形", - "file": "taonier-mascot-symbol-01-clay-humanoid.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: abstract humanoid mascot. A tiny soft clay person-like glyph with rounded head and merged body, no limbs or very minimal arms, friendly but not childish." - }, - { - "id": "02-clay-sprite", - "title": "陶泥精灵", - "file": "taonier-mascot-symbol-02-clay-sprite.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: clay sprite. A small semi-dome spirit with a gentle lifted silhouette, like a friendly creative helper, no wings, no magic stars, no fantasy clutter." - }, - { - "id": "03-soft-monster", - "title": "软萌怪物", - "file": "taonier-mascot-symbol-03-soft-monster.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: soft friendly monster glyph. Cute but not scary, no teeth, no claws, one distinctive head shape, perhaps tiny horn-like soft bumps but not devilish." - }, - { - "id": "04-animal-abstract", - "title": "抽象动物", - "file": "taonier-mascot-symbol-04-animal-abstract.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: abstract animal-like mascot. Suggest a small rounded creature through ears or tail-like curves, but not a specific real animal and not pet logo." - }, - { - "id": "05-clay-orb-being", - "title": "泥团小灵", - "file": "taonier-mascot-symbol-05-clay-orb-being.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: orb-like clay being. A simple irregular rounded body with minimal face or no face, strong silhouette, playful creation companion." - }, - { - "id": "06-playful-creature", - "title": "轻玩小怪", - "file": "taonier-mascot-symbol-06-playful-creature.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: more playful creature mark. Dynamic but compact, one distinctive asymmetric curve, readable at 32px, still premium and not a toy brand." - }, - { - "id": "07-avatar-readable", - "title": "头像可读", - "file": "taonier-mascot-symbol-07-avatar-readable.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: social avatar and favicon readability. Bold compact mascot head/body silhouette, minimal inner detail, high black-and-white clarity." - }, - { - "id": "08-vector-ready", - "title": "矢量符号感", - "file": "taonier-mascot-symbol-08-vector-ready.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works and share them with others.\nLogo type: abstract mascot symbol only. It must be a simple brand mark, not a full character illustration, not a scene, not a sticker sheet, not an app-icon rounded-square background.\nMascot meaning: a soft clay-born little being that represents playful creation, shareable ideas, and a friendly AI/UGC companion.\nStyle: modern minimalist vector mascot mark, compact silhouette, broad simple shapes, fresh bright palette, very light clay warmth only. It must look good and recognizable at 32px favicon size.\nCharacter abstraction: use a highly simplified head/body silhouette or creature glyph. No detailed anatomy. No clothing. No props. No complex limbs. Optional face should be extremely minimal, at most two small eyes; no mouth unless it is a tiny simple mark.\nShape language: soft, rounded, slightly irregular, as if gently pinched from clay, but clean and designer-ready. Friendly and memorable, not childish.\nColor direction: women-friendly and all-age fresh palette: coral orange, peach pink, cream white, clear soft teal or mint green. Use flat brand-color shapes, not candy rendering.\nAvoid realistic clay texture, dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance: do not make it look like bread, mochi, dumpling, cake, cookie, candy, jelly, donut, dessert, or food packaging.\nAvoid scary monster, aggressive teeth, claws, realistic animal, pet-shop icon, emoji face, toy mascot, chibi over-detailing, or generic blob.\nNo star, no spark, no halo, no magic wand, no hand, no pottery tool, no UI, no border, no watermark.\nComposition: centered on a clean light background, generous safe area. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the mascot silhouette should be recognizable at 32px.\nVariant focus: designer-ready vector mascot concept. 2-3 flat shapes, crisp boundaries, distinctive silhouette, minimal material cue, no illustration shading." - } - ] -} diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-01-clay-humanoid.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-01-clay-humanoid.png deleted file mode 100644 index e6c9da82..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-01-clay-humanoid.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-02-clay-sprite.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-02-clay-sprite.png deleted file mode 100644 index be1c3a66..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-02-clay-sprite.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-03-soft-monster.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-03-soft-monster.png deleted file mode 100644 index 2e1f677c..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-03-soft-monster.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-04-animal-abstract.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-04-animal-abstract.png deleted file mode 100644 index 1a8a23cc..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-04-animal-abstract.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-05-clay-orb-being.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-05-clay-orb-being.png deleted file mode 100644 index 9553a46a..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-05-clay-orb-being.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-06-playful-creature.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-06-playful-creature.png deleted file mode 100644 index b3f88980..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-06-playful-creature.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-07-avatar-readable.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-07-avatar-readable.png deleted file mode 100644 index c4daf21e..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-07-avatar-readable.png and /dev/null differ diff --git a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-08-vector-ready.png b/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-08-vector-ready.png deleted file mode 100644 index b87294ec..00000000 Binary files a/public/branding/taonier-logo-mascot-symbol-concepts/taonier-mascot-symbol-08-vector-ready.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-logo-playful-bean-contact-sheet.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-logo-playful-bean-contact-sheet.png deleted file mode 100644 index 675ba418..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-logo-playful-bean-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-logo-playful-bean-manifest.json b/public/branding/taonier-logo-playful-bean-concepts/taonier-logo-playful-bean-manifest.json deleted file mode 100644 index 48c343bb..00000000 --- a/public/branding/taonier-logo-playful-bean-concepts/taonier-logo-playful-bean-manifest.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T21:01:55.557Z", - "logoSkillSummary": { - "requiredReview": "visual inspection, 32px readability, black-white viability", - "outputStatus": "AI concept only; final logo needs vector cleanup" - }, - "brief": { - "brand": "陶泥儿", - "coreBelief": "好玩会创造", - "logoType": "symbol/icon-only mark, no wordmark", - "product": "AI UGC 轻休闲小游戏创作与传播平台,用户像捏陶泥一样把脑洞、梗和灵感塑造成可玩的作品", - "coreMetaphor": "已经成形的可玩作品胚", - "audience": "女性用户友好、全年龄向、年轻明亮但不低幼", - "visualLanguage": "抽象但有玩性的软几何玩具感", - "material": "只保留陶泥温度,不追求泥土质感", - "shape": "闭合不规则圆润豆形,外轮廓流畅、亲和、有玩性", - "colors": [ - "珊瑚橙", - "蜜桃粉", - "奶油白", - "清透青绿", - "少量暖黄或柔紫可选" - ], - "mustHave": [ - "无中文、无英文、无字标", - "无星星、无脸、无表情", - "无方形底盘", - "无食物感", - "32px 可识别", - "黑白化仍成立" - ] - }, - "variants": [ - { - "id": "01-fresh-bean-mark", - "title": "清新豆形标", - "file": "taonier-playful-bean-01-fresh-bean-mark.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: the cleanest fresh bean mark. Use coral orange and cream white with a tiny soft teal accent. Strong closed irregular bean silhouette, very readable." - }, - { - "id": "02-peach-soft-geometry", - "title": "蜜桃软几何", - "file": "taonier-playful-bean-02-peach-soft-geometry.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: peach pink and coral soft geometry. Feminine-friendly but not cosmetic, not candy. One smooth inner color field supports the closed bean shape." - }, - { - "id": "03-mint-creation-embryo", - "title": "青绿创作胚", - "file": "taonier-playful-bean-03-mint-creation-embryo.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: clear mint or teal as the memory accent, with warm cream and coral. The mark should feel like a playable creation object, not a leaf or seed." - }, - { - "id": "04-female-bright-mark", - "title": "女性向明亮款", - "file": "taonier-playful-bean-04-female-bright-mark.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: brighter women-friendly palette, soft coral, peach, cream, and one clean mint accent. Keep it premium and avoid beauty-brand cliché." - }, - { - "id": "05-all-age-play-mark", - "title": "全龄轻玩款", - "file": "taonier-playful-bean-05-all-age-play-mark.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: all-age casual play. More energetic and memorable, but still simple. Use two or three flat color fields, no small decorative details." - }, - { - "id": "06-monochrome-first", - "title": "黑白优先款", - "file": "taonier-playful-bean-06-monochrome-first.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: design for black-and-white survival first. Bold positive and negative shapes, color only supports the structure. No delicate gradients." - }, - { - "id": "07-avatar-readable", - "title": "头像小尺寸款", - "file": "taonier-playful-bean-07-avatar-readable.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: social avatar and favicon readability. Full, compact closed bean silhouette with one distinctive broad internal curve; no tiny dots." - }, - { - "id": "08-vector-ready", - "title": "矢量定稿感款", - "file": "taonier-playful-bean-08-vector-ready.png", - "prompt": "Create an icon-only brand logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nBrand core belief: fun creation. Users can turn imagination, memes, and ideas into playable AI/UGC casual game works.\nLogo meaning: an already-formed playable creation embryo. It should feel like a small friendly finished creative object, not a process diagram and not raw clay.\nLogo type: abstract symbol/icon only. No wordmark, no mascot, no face, no app-icon rounded-square background.\nMain element: one closed irregular rounded bean-like soft geometric shape. The outer contour must be smooth, friendly, memorable, complete, and vector-friendly.\nThe shape must not be a square, rounded square, circle, ellipse, simple blob, ribbon, swirl, S shape, G shape, open ring, loose strip, badge, stamp, or tile.\nThe symbol should be abstract but playful: it should feel like a soft geometric toy-like creation object, mature enough for a premium brand and not childish.\nInternal design: optional 1-2 broad curved color fields, soft cut-ins, or abstract playful inlays. Do not show a transformation process. Do not add a center icon.\nNo star, no spark, no star-shaped negative space, no eyes, no mouth, no character body, no hand, no pottery tool.\nStyle: modern minimalist vector logo. Flat, crisp, simple broad shapes, very light clay warmth only. No realistic texture. It must look good and recognizable at 32px favicon size.\nColor direction: fresh bright palette for women-friendly and all-age users: coral orange, peach pink, cream white, clear soft teal or mint green. Use brand-color flat shapes, not candy rendering.\nAvoid muted mud colors as the dominant palette. Avoid dirty clay, brick, pottery shard, mud pie, rough craft class object, and archaeology stamp feeling.\nFood avoidance is critical: do not make it look like bread, chocolate bread, pastry, cookie, candy, jelly, donut, cream filling, sauce, baked dough, dessert, fruit candy, or food packaging.\nComposition: centered on a clean light background, generous safe area, no border, no UI, no watermark. Use simple readable silhouette first.\nValidation targets: black-and-white version should still read clearly; the closed bean-like silhouette should be recognizable at 32px.\nVariant focus: designer-ready vector concept. 2-3 flat shapes, crisp boundaries, distinctive closed rounded-bean contour, minimal material cue." - } - ] -} diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-01-fresh-bean-mark.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-01-fresh-bean-mark.png deleted file mode 100644 index 20caf56a..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-01-fresh-bean-mark.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-02-peach-soft-geometry.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-02-peach-soft-geometry.png deleted file mode 100644 index d1bcf08a..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-02-peach-soft-geometry.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-03-mint-creation-embryo.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-03-mint-creation-embryo.png deleted file mode 100644 index b72c679c..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-03-mint-creation-embryo.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-04-female-bright-mark.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-04-female-bright-mark.png deleted file mode 100644 index 1ce817be..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-04-female-bright-mark.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-05-all-age-play-mark.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-05-all-age-play-mark.png deleted file mode 100644 index 6eebdacc..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-05-all-age-play-mark.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-06-monochrome-first.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-06-monochrome-first.png deleted file mode 100644 index 5552f400..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-06-monochrome-first.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-07-avatar-readable.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-07-avatar-readable.png deleted file mode 100644 index 14aabbf5..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-07-avatar-readable.png and /dev/null differ diff --git a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-08-vector-ready.png b/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-08-vector-ready.png deleted file mode 100644 index 91a55e96..00000000 Binary files a/public/branding/taonier-logo-playful-bean-concepts/taonier-playful-bean-08-vector-ready.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch-hole-concepts/taonier-logo-punch-hole-contact-sheet.png b/public/branding/taonier-logo-punch-hole-concepts/taonier-logo-punch-hole-contact-sheet.png deleted file mode 100644 index c0eef79f..00000000 Binary files a/public/branding/taonier-logo-punch-hole-concepts/taonier-logo-punch-hole-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-app-token.png b/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-app-token.png deleted file mode 100644 index 8f87935f..00000000 Binary files a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-app-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-color-inlay.png b/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-color-inlay.png deleted file mode 100644 index e20e4c8f..00000000 Binary files a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-color-inlay.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-hole-balance.png b/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-hole-balance.png deleted file mode 100644 index 5b5439b0..00000000 Binary files a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-hole-balance.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-locked-shape.png b/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-locked-shape.png deleted file mode 100644 index d1d4ec6d..00000000 Binary files a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-locked-shape.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-mono-test.png b/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-mono-test.png deleted file mode 100644 index 452fc1dc..00000000 Binary files a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-mono-test.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-stable-icon.png b/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-stable-icon.png deleted file mode 100644 index b7cbdaaa..00000000 Binary files a/public/branding/taonier-logo-punch-hole-concepts/taonier-punch-stable-icon.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch04-color-concepts/taonier-logo-punch04-color-contact-sheet.png b/public/branding/taonier-logo-punch04-color-concepts/taonier-logo-punch04-color-contact-sheet.png deleted file mode 100644 index dce1af6a..00000000 Binary files a/public/branding/taonier-logo-punch04-color-concepts/taonier-logo-punch04-color-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-clay-gradient-flat.png b/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-clay-gradient-flat.png deleted file mode 100644 index 72063022..00000000 Binary files a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-clay-gradient-flat.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-cream-window.png b/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-cream-window.png deleted file mode 100644 index c983a866..00000000 Binary files a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-cream-window.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-mint-shadow.png b/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-mint-shadow.png deleted file mode 100644 index e66d5b79..00000000 Binary files a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-mint-shadow.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-navy-game-core.png b/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-navy-game-core.png deleted file mode 100644 index 47eb02ba..00000000 Binary files a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-navy-game-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-negative-tile.png b/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-negative-tile.png deleted file mode 100644 index 78ffb418..00000000 Binary files a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-negative-tile.png and /dev/null differ diff --git a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-warm-ink-core.png b/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-warm-ink-core.png deleted file mode 100644 index b825fada..00000000 Binary files a/public/branding/taonier-logo-punch04-color-concepts/taonier-punch04-warm-ink-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-logo-ref04-locked-color-contact-sheet.png b/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-logo-ref04-locked-color-contact-sheet.png deleted file mode 100644 index bc0e7101..00000000 Binary files a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-logo-ref04-locked-color-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-blue-ink.png b/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-blue-ink.png deleted file mode 100644 index 45a4952d..00000000 Binary files a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-blue-ink.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-green-ink.png b/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-green-ink.png deleted file mode 100644 index 3e7761ef..00000000 Binary files a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-green-ink.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-plum-ink.png b/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-plum-ink.png deleted file mode 100644 index f9ce68df..00000000 Binary files a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-plum-ink.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-shrink-core.png b/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-shrink-core.png deleted file mode 100644 index c9d9c197..00000000 Binary files a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-shrink-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-soft-charcoal.png b/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-soft-charcoal.png deleted file mode 100644 index c04cee0d..00000000 Binary files a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-soft-charcoal.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-warm-ink.png b/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-warm-ink.png deleted file mode 100644 index 3b0528a5..00000000 Binary files a/public/branding/taonier-logo-ref04-locked-color-concepts/taonier-ref04-locked-warm-ink.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-logo-ref04-palette-refine-contact-sheet.png b/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-logo-ref04-palette-refine-contact-sheet.png deleted file mode 100644 index e735051d..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-logo-ref04-palette-refine-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-biscuit.png b/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-biscuit.png deleted file mode 100644 index 8c410b47..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-biscuit.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-butter.png b/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-butter.png deleted file mode 100644 index 456824fc..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-butter.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-cream.png b/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-cream.png deleted file mode 100644 index 8f577dc9..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-cream.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-milk.png b/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-milk.png deleted file mode 100644 index d9851534..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-ref04-palette-refine-milk.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-sparkle-reference-crop.png b/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-sparkle-reference-crop.png deleted file mode 100644 index 2a22d000..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-concepts/taonier-sparkle-reference-crop.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-logo-ref04-palette-refine-v2-contact-sheet.png b/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-logo-ref04-palette-refine-v2-contact-sheet.png deleted file mode 100644 index 62f0e8c2..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-logo-ref04-palette-refine-v2-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-light-vanilla.png b/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-light-vanilla.png deleted file mode 100644 index 3b585f4d..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-light-vanilla.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-pale-cream.png b/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-pale-cream.png deleted file mode 100644 index d57ac416..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-pale-cream.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-soft-butter.png b/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-soft-butter.png deleted file mode 100644 index 83b328e6..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v2-concepts/taonier-ref04-palette-refine-v2-soft-butter.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-logo-ref04-palette-refine-v3-contact-sheet.png b/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-logo-ref04-palette-refine-v3-contact-sheet.png deleted file mode 100644 index e5ec8913..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-logo-ref04-palette-refine-v3-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-butter-soft.png b/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-butter-soft.png deleted file mode 100644 index 1f6dd27d..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-butter-soft.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-milk-cream.png b/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-milk-cream.png deleted file mode 100644 index a3680d8e..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-milk-cream.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-soft-vanilla.png b/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-soft-vanilla.png deleted file mode 100644 index d2aaf01f..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v3-concepts/taonier-ref04-palette-refine-v3-soft-vanilla.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-logo-ref04-palette-refine-v4-contact-sheet.png b/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-logo-ref04-palette-refine-v4-contact-sheet.png deleted file mode 100644 index d1ed73b5..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-logo-ref04-palette-refine-v4-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-cream-paper.png b/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-cream-paper.png deleted file mode 100644 index 34aedd36..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-cream-paper.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-pale-butter.png b/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-pale-butter.png deleted file mode 100644 index c3456100..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-pale-butter.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-soft-champagne.png b/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-soft-champagne.png deleted file mode 100644 index 81e1f6f0..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-soft-champagne.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-warm-ivory.png b/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-warm-ivory.png deleted file mode 100644 index e80a3fb2..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v4-concepts/taonier-ref04-palette-refine-v4-warm-ivory.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-logo-ref04-palette-refine-v5-contact-sheet.png b/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-logo-ref04-palette-refine-v5-contact-sheet.png deleted file mode 100644 index a9500de5..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-logo-ref04-palette-refine-v5-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-balanced-bright-spark.png b/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-balanced-bright-spark.png deleted file mode 100644 index 10509d6e..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-balanced-bright-spark.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-filled-centered-spark.png b/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-filled-centered-spark.png deleted file mode 100644 index ebd57523..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-filled-centered-spark.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-smooth-left-small-spark.png b/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-smooth-left-small-spark.png deleted file mode 100644 index dffaf239..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-smooth-left-small-spark.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-solid-core-no-hole.png b/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-solid-core-no-hole.png deleted file mode 100644 index 7914250b..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-refine-v5-concepts/taonier-ref04-palette-refine-v5-solid-core-no-hole.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-transfer/taonier-logo-ref04-palette-transfer-contact-sheet.png b/public/branding/taonier-logo-ref04-palette-transfer/taonier-logo-ref04-palette-transfer-contact-sheet.png deleted file mode 100644 index fd8ff342..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-transfer/taonier-logo-ref04-palette-transfer-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-palette-transfer/taonier-ref04-palette-transfer-warm-yellow-sparkle.png b/public/branding/taonier-logo-ref04-palette-transfer/taonier-ref04-palette-transfer-warm-yellow-sparkle.png deleted file mode 100644 index a3dd55bf..00000000 Binary files a/public/branding/taonier-logo-ref04-palette-transfer/taonier-ref04-palette-transfer-warm-yellow-sparkle.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-logo-ref04-warm-sparkle-contact-sheet.png b/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-logo-ref04-warm-sparkle-contact-sheet.png deleted file mode 100644 index e60e1621..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-logo-ref04-warm-sparkle-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-caramel.png b/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-caramel.png deleted file mode 100644 index 7e61d5f2..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-caramel.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-clay-quiet.png b/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-clay-quiet.png deleted file mode 100644 index 6f1d8d84..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-clay-quiet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-cocoa.png b/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-cocoa.png deleted file mode 100644 index e21e08e1..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-cocoa.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-plum.png b/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-plum.png deleted file mode 100644 index da9fa07a..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-plum.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-rust.png b/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-rust.png deleted file mode 100644 index f07ba90a..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-rust.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-terracotta.png b/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-terracotta.png deleted file mode 100644 index 80658b35..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-concepts/taonier-ref04-warm-sparkle-terracotta.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-logo-ref04-warm-sparkle-v2-contact-sheet.png b/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-logo-ref04-warm-sparkle-v2-contact-sheet.png deleted file mode 100644 index e60e1621..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-logo-ref04-warm-sparkle-v2-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-caramel.png b/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-caramel.png deleted file mode 100644 index 7e61d5f2..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-caramel.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-clay-quiet.png b/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-clay-quiet.png deleted file mode 100644 index 6f1d8d84..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-clay-quiet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-cocoa.png b/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-cocoa.png deleted file mode 100644 index e21e08e1..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-cocoa.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-plum.png b/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-plum.png deleted file mode 100644 index da9fa07a..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-plum.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-rust.png b/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-rust.png deleted file mode 100644 index f07ba90a..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-rust.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-terracotta.png b/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-terracotta.png deleted file mode 100644 index 80658b35..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-sparkle-v2-concepts/taonier-ref04-warm-sparkle-terracotta.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-logo-ref04-warm-star-contact-sheet.png b/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-logo-ref04-warm-star-contact-sheet.png deleted file mode 100644 index efc7f4c4..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-logo-ref04-warm-star-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-caramel.png b/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-caramel.png deleted file mode 100644 index 58060a02..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-caramel.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-cocoa.png b/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-cocoa.png deleted file mode 100644 index 90d9dcc7..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-cocoa.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-olive.png b/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-olive.png deleted file mode 100644 index 2075da25..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-olive.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-plum.png b/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-plum.png deleted file mode 100644 index 68420c98..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-plum.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-rust.png b/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-rust.png deleted file mode 100644 index 2cf515d0..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-rust.png and /dev/null differ diff --git a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-terracotta.png b/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-terracotta.png deleted file mode 100644 index fcb5e381..00000000 Binary files a/public/branding/taonier-logo-ref04-warm-star-concepts/taonier-ref04-warm-star-terracotta.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-logo-short-foot-creature-contact-sheet.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-logo-short-foot-creature-contact-sheet.png deleted file mode 100644 index 946f9817..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-logo-short-foot-creature-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-logo-short-foot-creature-manifest.json b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-logo-short-foot-creature-manifest.json deleted file mode 100644 index 7ffea9ad..00000000 --- a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-logo-short-foot-creature-manifest.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "model": "gpt-image-2-all", - "size": "1024x1024", - "generatedAt": "2026-05-14T22:27:21.056Z", - "logoSkillSummary": { - "requiredReview": "visual inspection, 32px readability, black-white viability", - "outputStatus": "AI concept only; final logo needs vector cleanup" - }, - "brief": { - "brand": "陶泥儿", - "coreBelief": "好玩会创造", - "logoType": "symbol/icon-only mascot mark, no wordmark", - "product": "AI UGC 轻休闲小游戏创作与传播平台,用户把脑洞、梗和灵感塑造成可分享的作品", - "direction": "低重心短脚泥团小灵体 / 小怪物:参考图只用于造型,不继承写实陶瓷质感", - "audience": "女性用户友好、全年龄向、年轻明亮但不低幼", - "shapeRules": [ - "主体是坐在地上的闭合泥团生物,像一个稳定的软陶泥胚", - "底部有 3-5 个短短的圆脚或脚趾状支点,但不能变成爪子", - "头顶可以有弯角、小尖、软芽、卷曲或捏起的造型,作为记忆点", - "整体必须是 logo 符号级别,不是完整角色插画", - "32px 下仍能看出低重心泥团、短脚和头顶造型" - ], - "avoid": [ - "中文或英文字", - "星星或闪光", - "手托举元素", - "写实陶瓷高光", - "脏泥土或砖块", - "面团、汤圆、甜点、面包、巧克力、糖果、布丁", - "恐怖怪物、牙齿、爪子", - "儿童玩具、表情包贴纸" - ] - }, - "variants": [ - { - "id": "01-curled-tip", - "title": "弯角泥团", - "file": "taonier-short-foot-creature-01-curled-tip.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: a squat clay lump creature with one soft curled tip leaning gently forward, four tiny rounded feet, calm premium silhouette." - }, - { - "id": "02-soft-sprout", - "title": "软芽泥团", - "file": "taonier-short-foot-creature-02-soft-sprout.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: a low mound creature with a pinched sprout-like top made from the same clay body, three short feet, fresh and memorable." - }, - { - "id": "03-wave-tuft", - "title": "波浪小怪", - "file": "taonier-short-foot-creature-03-wave-tuft.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: a playful clay creature with a single wave-shaped top tuft, broad sitting base, 4 tiny feet, more dynamic but still logo-simple." - }, - { - "id": "04-round-horn", - "title": "圆角小怪", - "file": "taonier-short-foot-creature-04-round-horn.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: a friendly abstract little monster with one rounded horn-like bump and a second smaller bump, stubby feet, no scary details." - }, - { - "id": "05-low-squat", - "title": "低趴泥团", - "file": "taonier-short-foot-creature-05-low-squat.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: extra low and stable clay mound, wide base, five tiny rounded feet, top feature is a subtle pinched crest, very favicon-readable." - }, - { - "id": "06-asymmetric-charm", - "title": "偏心灵体", - "file": "taonier-short-foot-creature-06-asymmetric-charm.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: asymmetrical friendly spirit mark, body leans slightly to one side, curled top balances the shape, short feet stay grounded." - }, - { - "id": "07-avatar-bold", - "title": "头像强识别", - "file": "taonier-short-foot-creature-07-avatar-bold.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: bold social avatar readability, thick simple silhouette, two tiny eye dots allowed, top tuft and feet readable at 32px." - }, - { - "id": "08-vector-outline", - "title": "商标轮廓", - "file": "taonier-short-foot-creature-08-vector-outline.png", - "prompt": "Create an icon-only mascot logo symbol for the Chinese product named \"陶泥儿\"; do not render any Chinese, English, letters, numbers, wordmark, tagline, or text.\nThe reference idea is only shape language: a squat soft clay lump creature sitting on the ground, with several very short rounded feet and a distinctive top tuft or curved horn. Do not copy realistic ceramic rendering.\nBrand core: fun creation. The mark should feel like a tiny clay-born creative spirit: soft, friendly, shareable, and able to turn ideas into playful AI/UGC casual game works.\nLogo type: simple mascot symbol only. It must be a brand mark, not a full character illustration, not a scene, not a sticker, not a rounded-square app icon background.\nMain silhouette: low center of gravity, closed mound body, slightly irregular but clean outline, broad base, 3 to 5 stubby rounded feet at the bottom. Feet must be short and cute, never claws, toes, paws, or realistic animal legs.\nTop silhouette: one memorable soft sculpted feature on the head, such as a curled clay tip, small pinched sprout, rounded horn, or soft wave tuft. The top feature should be part of the same clay body, not a separate hat or accessory.\nFace policy: face is optional; if used, only two tiny simple eye dots. No mouth, no blush, no emoji expression, no detailed face. The silhouette should work even without the face.\nStyle: modern minimalist vector mascot logo, flat brand-color shapes, clean curves, premium but warm. Slight clay warmth through color and form only; no realistic texture.\nColor direction: earthy but fresh, using warm clay beige, terracotta, cream white, soft coral, and a very small teal or mint accent if helpful. Avoid dirty mud, brick red dominance, candy gradients, glossy dessert rendering.\nFood avoidance is critical: do not make it look like mochi, dumpling, bun, bread, cake, cookie, candy, pudding, chocolate, jelly, or any edible mascot.\nAvoid pottery class object, ceramic figurine, handmade toy, rough mud texture, archaeology stamp, realistic glaze, 3D clay render, or shiny ceramic highlight.\nAvoid scary monster, teeth, claws, spikes, realistic animal, pet logo, chibi over-detailing, generic blob, star, spark, halo, magic wand, hand, pottery tool, UI, border, or watermark.\nComposition: centered on clean light background, generous safe area, strong readable silhouette first. It should look recognizable at 32px and still work in black and white.\nVariant focus: designer-ready vector mark. Use 2-3 flat shapes, crisp boundaries, very strong black-and-white silhouette, minimal inner detail." - } - ] -} diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-01-curled-tip.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-01-curled-tip.png deleted file mode 100644 index 4461557f..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-01-curled-tip.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-02-soft-sprout.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-02-soft-sprout.png deleted file mode 100644 index 5a970cb6..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-02-soft-sprout.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-03-wave-tuft.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-03-wave-tuft.png deleted file mode 100644 index 124731c0..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-03-wave-tuft.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-04-round-horn.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-04-round-horn.png deleted file mode 100644 index 8f79afce..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-04-round-horn.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-05-low-squat.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-05-low-squat.png deleted file mode 100644 index 809c978f..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-05-low-squat.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-06-asymmetric-charm.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-06-asymmetric-charm.png deleted file mode 100644 index 48fd9dde..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-06-asymmetric-charm.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-07-avatar-bold.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-07-avatar-bold.png deleted file mode 100644 index 81e3b441..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-07-avatar-bold.png and /dev/null differ diff --git a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-08-vector-outline.png b/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-08-vector-outline.png deleted file mode 100644 index bc1b3799..00000000 Binary files a/public/branding/taonier-logo-short-foot-creature-concepts/taonier-short-foot-creature-08-vector-outline.png and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-logo-spiral-reference-contact-sheet.png b/public/branding/taonier-logo-spiral-reference-concepts/taonier-logo-spiral-reference-contact-sheet.png deleted file mode 100644 index 081680b1..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-logo-spiral-reference-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png b/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png deleted file mode 100644 index be26c140..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-candy-roll.png b/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-candy-roll.png deleted file mode 100644 index a15757c2..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-candy-roll.png and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-creation-whirl.png b/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-creation-whirl.png deleted file mode 100644 index 8e44674b..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-creation-whirl.png and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-reference.jpg b/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-reference.jpg deleted file mode 100644 index 909668d0..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-reference.jpg and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-soft-squish.png b/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-soft-squish.png deleted file mode 100644 index 6bd10dd0..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-soft-squish.png and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-soft-token.png b/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-soft-token.png deleted file mode 100644 index a03c8eb0..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-soft-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-star-core.png b/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-star-core.png deleted file mode 100644 index 25d974b5..00000000 Binary files a/public/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-star-core.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-concepts/taonier-logo-squish-contact-sheet.png b/public/branding/taonier-logo-squish-concepts/taonier-logo-squish-contact-sheet.png deleted file mode 100644 index 8d49544d..00000000 Binary files a/public/branding/taonier-logo-squish-concepts/taonier-logo-squish-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-bounce.png b/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-bounce.png deleted file mode 100644 index 619ae0b7..00000000 Binary files a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-bounce.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-comet.png b/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-comet.png deleted file mode 100644 index eaf7d61d..00000000 Binary files a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-comet.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-pulse.png b/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-pulse.png deleted file mode 100644 index ffb8f935..00000000 Binary files a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-pulse.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-spark-gap.png b/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-spark-gap.png deleted file mode 100644 index b82ae240..00000000 Binary files a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-spark-gap.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-token.png b/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-token.png deleted file mode 100644 index a80c05e4..00000000 Binary files a/public/branding/taonier-logo-squish-concepts/taonier-squish-v2-token.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-bubble-q.png b/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-bubble-q.png deleted file mode 100644 index ad325f91..00000000 Binary files a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-bubble-q.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-candy-mint.png b/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-candy-mint.png deleted file mode 100644 index 83f2cb53..00000000 Binary files a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-candy-mint.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-contact-sheet.png b/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-contact-sheet.png deleted file mode 100644 index 882d871b..00000000 Binary files a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-coral-soda.png b/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-coral-soda.png deleted file mode 100644 index 7add634e..00000000 Binary files a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-coral-soda.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-original-plus.png b/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-original-plus.png deleted file mode 100644 index 02bf0268..00000000 Binary files a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-original-plus.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-peach-jelly.png b/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-peach-jelly.png deleted file mode 100644 index 382cf5a8..00000000 Binary files a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-peach-jelly.png and /dev/null differ diff --git a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-pop-bright.png b/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-pop-bright.png deleted file mode 100644 index 89a21434..00000000 Binary files a/public/branding/taonier-logo-squish-recolor-variants/taonier-squish-recolor-pop-bright.png and /dev/null differ diff --git a/public/branding/taonier-logo-v3-concepts/taonier-logo-v3-contact-sheet.png b/public/branding/taonier-logo-v3-concepts/taonier-logo-v3-contact-sheet.png deleted file mode 100644 index 0140e14d..00000000 Binary files a/public/branding/taonier-logo-v3-concepts/taonier-logo-v3-contact-sheet.png and /dev/null differ diff --git a/public/branding/taonier-logo-v3-concepts/taonier-v3-finger-spark.png b/public/branding/taonier-logo-v3-concepts/taonier-v3-finger-spark.png deleted file mode 100644 index 6ad95167..00000000 Binary files a/public/branding/taonier-logo-v3-concepts/taonier-v3-finger-spark.png and /dev/null differ diff --git a/public/branding/taonier-logo-v3-concepts/taonier-v3-magic-dot.png b/public/branding/taonier-logo-v3-concepts/taonier-v3-magic-dot.png deleted file mode 100644 index c998fe77..00000000 Binary files a/public/branding/taonier-logo-v3-concepts/taonier-v3-magic-dot.png and /dev/null differ diff --git a/public/branding/taonier-logo-v3-concepts/taonier-v3-seed-pop.png b/public/branding/taonier-logo-v3-concepts/taonier-v3-seed-pop.png deleted file mode 100644 index 3173df89..00000000 Binary files a/public/branding/taonier-logo-v3-concepts/taonier-v3-seed-pop.png and /dev/null differ diff --git a/public/branding/taonier-logo-v3-concepts/taonier-v3-soft-t.png b/public/branding/taonier-logo-v3-concepts/taonier-v3-soft-t.png deleted file mode 100644 index 84f36dc4..00000000 Binary files a/public/branding/taonier-logo-v3-concepts/taonier-v3-soft-t.png and /dev/null differ diff --git a/public/branding/taonier-logo-v3-concepts/taonier-v3-work-gem.png b/public/branding/taonier-logo-v3-concepts/taonier-v3-work-gem.png deleted file mode 100644 index 688b8140..00000000 Binary files a/public/branding/taonier-logo-v3-concepts/taonier-v3-work-gem.png and /dev/null differ diff --git a/public/edutainment-baby-object/default-left-hand.png b/public/edutainment-baby-object/default-left-hand.png deleted file mode 100644 index 03c336cf..00000000 Binary files a/public/edutainment-baby-object/default-left-hand.png and /dev/null differ diff --git a/public/edutainment-baby-object/default-right-hand.png b/public/edutainment-baby-object/default-right-hand.png deleted file mode 100644 index 69f60995..00000000 Binary files a/public/edutainment-baby-object/default-right-hand.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-clean-transparent.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-clean-transparent.png deleted file mode 100644 index 62980beb..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-clean-transparent.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-defringed-strong.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-defringed-strong.png deleted file mode 100644 index 5f80b985..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-defringed-strong.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-defringed.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-defringed.png deleted file mode 100644 index 44fe9098..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-defringed.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-final-clean.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-final-clean.png deleted file mode 100644 index 03c336cf..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-final-clean.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-final-transparent.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-final-transparent.png deleted file mode 100644 index e072f085..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-final-transparent.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-transparent.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-transparent.png deleted file mode 100644 index e0be70c2..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-left-hand-v4-transparent.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-clean-transparent.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-clean-transparent.png deleted file mode 100644 index 6d6fa6cb..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-clean-transparent.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-defringed-strong.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-defringed-strong.png deleted file mode 100644 index c3433271..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-defringed-strong.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-defringed.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-defringed.png deleted file mode 100644 index af490122..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-defringed.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-final-clean.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-final-clean.png deleted file mode 100644 index 69f60995..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-final-clean.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-final-transparent.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-final-transparent.png deleted file mode 100644 index 99abe77b..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-final-transparent.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-transparent.png b/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-transparent.png deleted file mode 100644 index 60d923c4..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/baby-object-right-hand-v4-transparent.png and /dev/null differ diff --git a/public/edutainment-baby-object/image2-picture-book-hands/left-hand-picture-book-v1.png b/public/edutainment-baby-object/image2-picture-book-hands/left-hand-picture-book-v1.png deleted file mode 100644 index 34f5c1c5..00000000 Binary files a/public/edutainment-baby-object/image2-picture-book-hands/left-hand-picture-book-v1.png and /dev/null differ diff --git a/public/wooden-fish/default-hit-sound.mp3 b/public/wooden-fish/default-hit-sound.mp3 new file mode 100644 index 00000000..521b6c17 Binary files /dev/null and b/public/wooden-fish/default-hit-sound.mp3 differ diff --git a/scripts/dev.mjs b/scripts/dev.mjs index ef4fe055..0e2762b6 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -257,6 +257,96 @@ function requireCommand(command) { } } +function readWorkspaceSpacetimeVersion() { + const manifestText = readFileSync(manifestPath, 'utf8'); + const match = /^spacetimedb\s*=\s*(?:"([^"]+)"|\{[^}]*version\s*=\s*"([^"]+)")/mu.exec( + manifestText, + ); + const version = match?.[1] ?? match?.[2] ?? ''; + if (!version) { + throw new Error('无法从 server-rs/Cargo.toml 读取 spacetimedb 版本'); + } + return version; +} + +function parseSpacetimeToolVersion(output) { + const match = /spacetimedb tool version\s+([0-9]+\.[0-9]+\.[0-9]+)/u.exec(output); + return match?.[1] ?? ''; +} + +function readSpacetimeToolVersion() { + const result = spawnSync('spacetime', ['--version'], { + cwd: repoRoot, + encoding: 'utf8', + shell: process.platform === 'win32', + }); + + if (result.error) { + throw new Error(`读取 spacetime 版本失败: ${result.error.message}`); + } + + const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`; + const version = parseSpacetimeToolVersion(output); + if (!version) { + throw new Error(`无法解析 spacetime 版本输出: ${trimPreview(output)}`); + } + return version; +} + +function assertSpacetimeToolVersionMatchesWorkspace({ + toolVersion, + workspaceVersion, +}) { + if (toolVersion === workspaceVersion) { + return; + } + + throw new Error( + [ + `本机 spacetime CLI/standalone 版本 ${toolVersion} 与 server-rs 锁定的 SpacetimeDB ${workspaceVersion} 不一致。`, + '版本错位会导致 procedure 返回值 BSATN 反序列化失败,前端表现为 SpacetimeDB procedure 调用超时。', + `请执行 spacetime version install ${workspaceVersion} && spacetime version use ${workspaceVersion} 后重新运行本命令。`, + ].join(''), + ); +} + +function assertReusableSpacetimeProcessVersionMatchesWorkspace({ + dataDir, + serverUrl, +}) { + const recordedVersion = readRecordedSpacetimeToolVersion(dataDir); + const workspaceVersion = readWorkspaceSpacetimeVersion(); + if (!recordedVersion) { + throw new Error( + [ + `检测到正在运行的本地 SpacetimeDB: ${serverUrl},但缺少 SpacetimeDB 版本记录。`, + '为避免复用旧 standalone 导致 procedure 返回值 BSATN 反序列化失败和前端调用超时,请先停止该进程,再重新运行 npm run dev:spacetime。', + ].join(''), + ); + } + + if (recordedVersion === workspaceVersion) { + return; + } + + throw new Error( + [ + `正在运行的本地 SpacetimeDB standalone 版本 ${recordedVersion} 与 server-rs 锁定的 SpacetimeDB ${workspaceVersion} 不一致。`, + '版本错位会导致 procedure 返回值 BSATN 反序列化失败,前端表现为 SpacetimeDB procedure 调用超时。', + '请停止当前 SpacetimeDB 进程,执行 spacetime version use ', + workspaceVersion, + ' 后重新运行 npm run dev:spacetime。', + ].join(''), + ); +} + +function ensureSpacetimeToolVersionMatchesWorkspace() { + assertSpacetimeToolVersionMatchesWorkspace({ + toolVersion: readSpacetimeToolVersion(), + workspaceVersion: readWorkspaceSpacetimeVersion(), + }); +} + function ensureRequiredFiles(command) { const requiredFiles = []; @@ -478,6 +568,9 @@ class DevRunner { ) { requireCommand('spacetime'); } + if (this.shouldValidateSpacetimeToolVersion(command)) { + ensureSpacetimeToolVersionMatchesWorkspace(); + } await this.tryReuseExistingSpacetime(command); await this.resolvePorts(command); @@ -485,6 +578,19 @@ class DevRunner { this.printSummary(command); } + shouldValidateSpacetimeToolVersion(command) { + if (command === 'spacetime') { + return true; + } + if (command === 'all') { + return !this.options.skipSpacetime || !this.options.skipPublish; + } + if (command === 'api-server') { + return isLoopbackSpacetimeServer(this.state.spacetimeServer); + } + return false; + } + async tryReuseExistingSpacetime(command) { if (this.options.skipSpacetime) { return; @@ -525,6 +631,11 @@ class DevRunner { continue; } + assertReusableSpacetimeProcessVersionMatchesWorkspace({ + dataDir: this.options.spacetimeDataDir, + serverUrl: candidate, + }); + const port = safeUrlPort(candidate); if (Number.isInteger(port) && port > 0) { this.options.spacetimePort = port; @@ -705,6 +816,15 @@ class DevRunner { const logFile = resolve(options.spacetimeDataDir, 'logs/dev-spacetime-start.log'); const logStream = createWriteStream(logFile, {flags: 'a', encoding: 'utf8'}); service.logStream = logStream; + const spacetimeToolVersion = readSpacetimeToolVersion(); + assertSpacetimeToolVersionMatchesWorkspace({ + toolVersion: spacetimeToolVersion, + workspaceVersion: readWorkspaceSpacetimeVersion(), + }); + recordSpacetimeToolVersion( + options.spacetimeDataDir, + spacetimeToolVersion, + ); console.log(`[dev:spacetime] log: ${logFile}`); const env = { @@ -1357,6 +1477,15 @@ function readRecordedSpacetimeUrl(dataDir) { return ''; } +function readRecordedSpacetimeToolVersion(dataDir) { + const versionPath = resolve(dataDir, 'dev-spacetime-tool-version'); + if (!existsSync(versionPath)) { + return ''; + } + + return readFileSync(versionPath, 'utf8').split(/\r?\n/u)[0]?.trim() ?? ''; +} + function readRecordedSpacetimePidState(dataDir) { const pidPath = resolve(dataDir, 'spacetime.pid'); if (!existsSync(pidPath)) { @@ -1389,6 +1518,16 @@ function readRecordedSpacetimePidState(dataDir) { } } +function recordSpacetimeToolVersion(dataDir, version) { + const versionPath = resolve(dataDir, 'dev-spacetime-tool-version'); + ensureParentDir(versionPath); + try { + writeFileSync(versionPath, `${version}\n`, 'utf8'); + } catch (error) { + console.warn(`[dev:spacetime] 写入版本记录失败 ${versionPath}: ${error.message}`); + } +} + function recordSpacetimeUrl(dataDir, serverUrl) { const targets = [ resolve(dataDir, 'dev-spacetime-url'), @@ -1580,10 +1719,13 @@ function isSpacetimePublishPermissionError(error) { export { DevRunner, + assertReusableSpacetimeProcessVersionMatchesWorkspace, + assertSpacetimeToolVersionMatchesWorkspace, buildSpacetimePublishArgs, createDevServerSpawnOptions, createWatchConfigs, isSpacetimePublishPermissionError, + parseSpacetimeToolVersion, parseArgs, shouldAcceptWatchEvent, }; diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index 06015c59..851e9c07 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -1,4 +1,4 @@ -import {mkdtempSync, rmSync, writeFileSync} from 'node:fs'; +import {mkdtempSync, readFileSync, rmSync, writeFileSync} from 'node:fs'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; @@ -6,10 +6,13 @@ import {afterEach, describe, expect, test, vi} from 'vitest'; import { DevRunner, + assertReusableSpacetimeProcessVersionMatchesWorkspace, + assertSpacetimeToolVersionMatchesWorkspace, buildSpacetimePublishArgs, createDevServerSpawnOptions, createWatchConfigs, isSpacetimePublishPermissionError, + parseSpacetimeToolVersion, parseArgs, shouldAcceptWatchEvent, } from './dev.mjs'; @@ -21,6 +24,15 @@ afterEach(() => { vi.restoreAllMocks(); }); +function workspaceSpacetimeVersionForTest() { + const manifestText = readFileSync('server-rs/Cargo.toml', 'utf8'); + const match = /^spacetimedb\s*=\s*"([^"]+)"/mu.exec(manifestText); + if (!match) { + throw new Error('无法读取测试用 SpacetimeDB 版本'); + } + return match[1]; +} + describe('dev scheduler argument routing', () => { test('完整 dev 栈覆盖前端代理到本次解析出的 api-server 地址', () => { const {command, explicitOptions, options} = parseArgs([], { @@ -104,6 +116,11 @@ describe('dev scheduler spacetime reuse guard', () => { try { writeFileSync(join(tempDir, 'dev-spacetime-url'), 'http://127.0.0.1:3199\n', 'utf8'); writeFileSync(join(tempDir, 'spacetime.pid'), `${process.pid}\n`, 'utf8'); + writeFileSync( + join(tempDir, 'dev-spacetime-tool-version'), + `${workspaceSpacetimeVersionForTest()}\n`, + 'utf8', + ); globalThis.fetch = vi.fn(async () => ({status: 200})) as unknown as typeof fetch; const {command, explicitOptions, options} = parseArgs( @@ -126,6 +143,11 @@ describe('dev scheduler spacetime reuse guard', () => { const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-spacetime-reuse-')); try { writeFileSync(join(tempDir, 'spacetime.pid'), `${process.pid}\n`, 'utf8'); + writeFileSync( + join(tempDir, 'dev-spacetime-tool-version'), + `${workspaceSpacetimeVersionForTest()}\n`, + 'utf8', + ); globalThis.fetch = vi.fn(async () => ({status: 200})) as unknown as typeof fetch; const {command, explicitOptions, options} = parseArgs( @@ -212,6 +234,40 @@ describe('dev scheduler watch routing', () => { }); describe('dev scheduler spacetime refresh', () => { + test('解析 spacetime --version 输出里的 tool version', () => { + const version = parseSpacetimeToolVersion(` +A new version of SpacetimeDB is available: v2.2.0 (current: v2.1.0) +spacetimedb tool version 2.2.0; spacetimedb-lib version 2.2.0; +`); + + expect(version).toBe('2.2.0'); + }); + + test('本机 spacetime 版本和 workspace 锁定版本不一致时直接报清楚', () => { + expect(() => + assertSpacetimeToolVersionMatchesWorkspace({ + toolVersion: '2.1.0', + workspaceVersion: '2.2.0', + }), + ).toThrow('procedure 返回值 BSATN 反序列化失败'); + }); + + test('复用本地 SpacetimeDB standalone 前校验启动时版本记录', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-spacetime-version-')); + try { + writeFileSync(join(tempDir, 'dev-spacetime-tool-version'), '2.1.0\n', 'utf8'); + + expect(() => + assertReusableSpacetimeProcessVersionMatchesWorkspace({ + dataDir: tempDir, + serverUrl: 'http://127.0.0.1:3101', + }), + ).toThrow('SpacetimeDB procedure 调用超时'); + } finally { + rmSync(tempDir, {recursive: true, force: true}); + } + }); + test('本地发布 403 时识别为身份权限问题,避免误杀 standalone', () => { const error = new Error( 'Pre-publish check failed with status 403 Forbidden: c200... is not authorized to perform action on database c200...: update database', diff --git a/scripts/generate-edutainment-road-town-map-concepts.mjs b/scripts/generate-edutainment-road-town-map-concepts.mjs new file mode 100644 index 00000000..a821d251 --- /dev/null +++ b/scripts/generate-edutainment-road-town-map-concepts.mjs @@ -0,0 +1,443 @@ +import { Buffer } from 'node:buffer'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; + +const repoRoot = process.cwd(); +const outDir = path.join( + repoRoot, + 'output', + 'imagegen', + 'edutainment-road-town-map-concepts-20260523', +); +const styleReferencePath = path.join( + repoRoot, + 'public', + 'child-motion-demo', + 'picture-book-grass-stage.png', +); +const layoutReferencePaths = [ + path.join(repoRoot, 'tmp', 'edutainment_video_frames', 'video1_t2_11950ms.png'), + path.join(repoRoot, 'tmp', 'edutainment_video_frames', 'video1_t3_23400ms.png'), +]; +const defaultTimeoutMs = 1000000; + +const commonPrompt = [ + '横屏 16:9 电视端寓教于乐板块玩法入口概念图。', + '新的结构要求:抛弃原本乐园地图、环形乐园、岛屿乐园和主题公园分区结构,改为参考 Toca Life World 地图的“马路串联小建筑群”结构。', + '每一屏都是一个完整的主题小建筑群街区:画面中心必须有一条灰蓝色可通车马路横向贯穿,马路有双车道、白色虚线、斑马线、路口、转弯支路和小汽车。', + '马路必须从画面左侧边缘进入、从画面右侧边缘离开,左右边缘都要明确暗示可以接到上一屏和下一屏,像横向滑动世界地图的一段。', + '建筑群沿马路两侧聚集,不要平均散成乐园点位;每屏像一个小镇街区、一组主题建筑、一段可探索街道。', + '采用轻微俯视的卡通等距地图视角,能看清建筑立面、屋顶、路网和车辆路径。', + '整体保持 Genarrative 寓教于乐现有明亮卡通绘本插画风:柔和水彩笔触、纸张纹理、温暖草地、浅蓝天空、圆润儿童友好、干净低噪声。', + '入口模块只用建筑外形和道具暗示,不写任何文字:识物可用水果店、动物卡片屋、观察橱窗;绘画可用彩笔工坊、画纸屋;动作可用小操场、跳圈馆;拼图可用积木拼图屋;声音节奏可用小剧场、音乐屋;自然探索可用树屋、温室、观测台。', + '参考图只用于学习“中央马路 + 两侧主题小建筑群 + 横向连续世界”的结构,不要复制 Toca Life World 的 logo、角色、建筑外形、UI、按钮、文字、字幕、水印或品牌元素。', + '不要出现中文、英文、数字、字母、按钮文案、UI 面板、教程说明、logo、水印、商业 IP、真实品牌、城堡、环球影城/迪士尼式乐园、环形乐园岛、漂浮岛、地球球体、过度奇幻设施、真实照片感、暗黑科技风。', +].join(''); + +const concepts = [ + { + id: 'edutainment-road-town-01-cognition-main-street', + title: '识物认知主街', + prompt: [ + commonPrompt, + '本屏主题:识物认知主街。', + '马路从左到右横穿中轴,路两侧是一组水果小店、动物观察窗、图形积木屋、透明展示橱窗和小型公交站,像儿童认知街区。', + '建筑数量 6 到 8 个,分成前景近路建筑和后景山坡建筑,街区整体紧凑,不能变成分散乐园点。', + '左边缘的道路要露出半个弯道和继续驶入的小汽车,右边缘道路继续出画,暗示下一屏可以接到绘画工坊街。', + ].join(''), + }, + { + id: 'edutainment-road-town-02-creative-workshop-street', + title: '绘画创作工坊街', + prompt: [ + commonPrompt, + '本屏主题:绘画创作工坊街。', + '中央马路横贯画面,马路两侧聚成彩笔工坊、画纸屋、颜料罐屋、绘本小剧场、手工材料店和圆顶小展馆。', + '建筑轮廓要像一组主题小建筑群,屋顶可有画笔、颜料、纸张、拼贴形状等图形化暗示,但不要出现任何文字。', + '左边道路接识物主街,右边道路接运动音乐街;用连续路面、车道线、小汽车和路灯强调可横向滑动探索。', + ].join(''), + }, + { + id: 'edutainment-road-town-03-motion-music-block', + title: '运动音乐街区', + prompt: [ + commonPrompt, + '本屏主题:运动音乐街区。', + '中央马路是主要路径,两侧是一组动作热身馆、小操场、跳圈屋、音乐小剧场、鼓点屋、铃铛塔和户外小舞台。', + '建筑群要围绕道路形成一个活泼街区,前景可以有迷你停车位、斑马线、小汽车、路牌形状图标,但不要文字。', + '道路左侧出画连接绘画创作工坊街,右侧出画连接自然探索街;画面边缘不能封死。', + ].join(''), + }, + { + id: 'edutainment-road-town-04-nature-lab-avenue', + title: '自然探索实验大道', + prompt: [ + commonPrompt, + '本屏主题:自然探索实验大道。', + '中央马路横向穿过一组自然探索小建筑群:温室、树屋、昆虫观察屋、云朵气象站、小望远镜塔、湖边小码头和拼图桥。', + '建筑沿道路成街区分布,后景是柔和山坡和树林,前景保留路边草地、停车位和小汽车路径。', + '左边道路接运动音乐街,右边道路继续进入森林或新的学习街区,明确体现一屏接一屏的世界地图结构。', + ].join(''), + }, +]; + +function readDotenv(fileName) { + const filePath = path.join(repoRoot, fileName); + if (!existsSync(filePath)) { + return {}; + } + + const values = {}; + for (const line of readFileSync(filePath, 'utf8').split(/\r?\n/u)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) { + continue; + } + const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u.exec(trimmed); + if (!match) { + continue; + } + let value = match[2].trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + values[match[1]] = value; + } + return values; +} + +function resolveEnv() { + const loaded = { + ...readDotenv('.env.example'), + ...readDotenv('.env.local'), + ...readDotenv('.env.secrets.local'), + ...process.env, + }; + return { + baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '') + .trim() + .replace(/\/+$/u, ''), + apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(), + timeoutMs: Number.parseInt( + String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs), + 10, + ), + }; +} + +function generationUrl(baseUrl) { + return baseUrl.endsWith('/v1') + ? `${baseUrl}/images/generations` + : `${baseUrl}/v1/images/generations`; +} + +function collectStringsByKey(value, targetKey, output) { + if (Array.isArray(value)) { + value.forEach((entry) => collectStringsByKey(entry, targetKey, output)); + return; + } + if (!value || typeof value !== 'object') { + return; + } + + for (const [key, nested] of Object.entries(value)) { + if (key === targetKey) { + if (typeof nested === 'string' && nested.trim()) { + output.push(nested.trim()); + } + if (Array.isArray(nested)) { + nested.forEach((entry) => { + if (typeof entry === 'string' && entry.trim()) { + output.push(entry.trim()); + } + }); + } + } + collectStringsByKey(nested, targetKey, output); + } +} + +function inferExtensionFromBytes(bytes) { + if (bytes.subarray(0, 8).equals(Buffer.from('\x89PNG\r\n\x1A\n', 'binary'))) { + return 'png'; + } + if (bytes.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff]))) { + return 'jpg'; + } + if ( + bytes.subarray(0, 4).toString('ascii') === 'RIFF' && + bytes.subarray(8, 12).toString('ascii') === 'WEBP' + ) { + return 'webp'; + } + return 'png'; +} + +function inferExtensionFromContentType(contentType) { + const normalized = contentType.split(';')[0]?.trim().toLowerCase(); + if (normalized === 'image/png') { + return 'png'; + } + if (normalized === 'image/webp') { + return 'webp'; + } + if (normalized === 'image/jpeg') { + return 'jpg'; + } + return 'png'; +} + +function toDataUrl(filePath) { + if (!existsSync(filePath)) { + return null; + } + const bytes = readFileSync(filePath); + const extension = inferExtensionFromBytes(bytes); + const mime = extension === 'jpg' ? 'image/jpeg' : `image/${extension}`; + return `data:${mime};base64,${bytes.toString('base64')}`; +} + +function referenceDataUrls() { + return [styleReferencePath, ...layoutReferencePaths] + .map((filePath) => toDataUrl(filePath)) + .filter(Boolean); +} + +function buildRequestBody(concept, size, references) { + const body = { + model: 'gpt-image-2-all', + prompt: concept.prompt, + n: 1, + size, + }; + if (references.length > 0) { + body.image = references; + } + return body; +} + +function buildDryRunRequestBody(concept, size, references) { + return { + model: 'gpt-image-2-all', + prompt: concept.prompt, + n: 1, + size, + imageReferenceCount: references.length, + }; +} + +async function fetchJson(url, options, timeoutMs) { + const abortController = new AbortController(); + const timer = setTimeout(() => abortController.abort(), timeoutMs); + try { + const response = await fetch(url, { + ...options, + signal: abortController.signal, + }); + const text = await response.text(); + if (!response.ok) { + throw new Error(`VectorEngine ${response.status}: ${text.slice(0, 600)}`); + } + return JSON.parse(text); + } catch (error) { + if (error?.name === 'AbortError') { + throw new Error(`VectorEngine request timed out after ${timeoutMs}ms`); + } + throw error; + } finally { + clearTimeout(timer); + } +} + +async function downloadUrl(url, timeoutMs) { + const abortController = new AbortController(); + const timer = setTimeout(() => abortController.abort(), timeoutMs); + try { + const response = await fetch(url, { signal: abortController.signal }); + if (!response.ok) { + throw new Error(`download ${response.status}`); + } + return { + bytes: Buffer.from(await response.arrayBuffer()), + extension: inferExtensionFromContentType( + response.headers.get('content-type') || '', + ), + }; + } catch (error) { + if (error?.name === 'AbortError') { + throw new Error(`Generated image download timed out after ${timeoutMs}ms`); + } + throw error; + } finally { + clearTimeout(timer); + } +} + +function outputPathFor(concept, extension = 'png') { + return path.join(outDir, `${concept.id}.${extension}`); +} + +function findExistingOutputPath(concept) { + for (const extension of ['png', 'jpg', 'jpeg', 'webp']) { + const candidate = outputPathFor(concept, extension); + if (existsSync(candidate)) { + return candidate; + } + } + return null; +} + +async function generateOne(env, concept, size, references) { + const payload = await fetchJson( + generationUrl(env.baseUrl), + { + method: 'POST', + headers: { + Authorization: `Bearer ${env.apiKey}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(buildRequestBody(concept, size, references)), + }, + env.timeoutMs, + ); + + const urls = []; + const b64Images = []; + collectStringsByKey(payload, 'url', urls); + collectStringsByKey(payload, 'image', urls); + collectStringsByKey(payload, 'image_url', urls); + collectStringsByKey(payload, 'b64_json', b64Images); + + let image; + const imageUrl = [...new Set(urls)].find((url) => /^https?:\/\//u.test(url)); + if (imageUrl) { + image = await downloadUrl(imageUrl, env.timeoutMs); + } else if (b64Images[0]) { + const bytes = Buffer.from(b64Images[0], 'base64'); + image = { + bytes, + extension: inferExtensionFromBytes(bytes), + }; + } else { + throw new Error(`VectorEngine returned no image for ${concept.id}`); + } + + mkdirSync(outDir, { recursive: true }); + const outputPath = outputPathFor(concept, image.extension); + writeFileSync(outputPath, image.bytes); + return outputPath; +} + +const args = new Map(); +for (let index = 2; index < process.argv.length; index += 1) { + const raw = process.argv[index]; + if (raw.startsWith('--')) { + const next = process.argv[index + 1]; + if (next && !next.startsWith('--')) { + args.set(raw, next); + index += 1; + } else { + args.set(raw, true); + } + } +} + +const size = String(args.get('--size') || '3840x2160'); +const dryRun = args.has('--dry-run') || !args.has('--live'); +const selectedIds = String(args.get('--only') || '') + .split(',') + .map((value) => value.trim()) + .filter(Boolean); +const selectedConcepts = concepts.filter( + (concept) => selectedIds.length === 0 || selectedIds.includes(concept.id), +); +const references = referenceDataUrls(); + +if (dryRun) { + console.log( + JSON.stringify( + { + mode: 'dry-run', + outDir, + size, + styleReference: existsSync(styleReferencePath) + ? styleReferencePath + : null, + layoutReferences: layoutReferencePaths.filter((filePath) => + existsSync(filePath), + ), + count: selectedConcepts.length, + requests: selectedConcepts.map((concept) => ({ + id: concept.id, + title: concept.title, + body: buildDryRunRequestBody(concept, size, references), + })), + }, + null, + 2, + ), + ); + process.exit(0); +} + +const env = resolveEnv(); +if (!env.baseUrl || !env.apiKey) { + console.error( + JSON.stringify({ + ok: false, + error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY', + hasBaseUrl: Boolean(env.baseUrl), + hasApiKey: Boolean(env.apiKey), + }), + ); + process.exit(1); +} + +const files = []; +const generatedFileById = new Map(); +for (const concept of selectedConcepts) { + console.log(`Generating ${concept.id}...`); + const file = await generateOne(env, concept, size, references); + files.push(file); + generatedFileById.set(concept.id, file); +} + +const metadataFiles = concepts + .map((concept) => { + const file = generatedFileById.get(concept.id) ?? findExistingOutputPath(concept); + if (!file) { + return null; + } + return { + id: concept.id, + title: concept.title, + file, + prompt: concept.prompt, + }; + }) + .filter(Boolean); + +writeFileSync( + path.join(outDir, 'generation-metadata.json'), + JSON.stringify( + { + model: 'gpt-image-2-all', + size, + generatedAt: new Date().toISOString(), + styleReference: existsSync(styleReferencePath) ? styleReferencePath : null, + layoutReferences: layoutReferencePaths.filter((filePath) => + existsSync(filePath), + ), + generatedIds: selectedConcepts.map((concept) => concept.id), + files: metadataFiles, + }, + null, + 2, + ), +); + +console.log(JSON.stringify({ ok: true, count: files.length, files }, null, 2)); diff --git a/scripts/generate-edutainment-toca-world-map-concepts.mjs b/scripts/generate-edutainment-toca-world-map-concepts.mjs new file mode 100644 index 00000000..0cddf399 --- /dev/null +++ b/scripts/generate-edutainment-toca-world-map-concepts.mjs @@ -0,0 +1,422 @@ +import { Buffer } from 'node:buffer'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; + +const repoRoot = process.cwd(); +const outDir = path.join( + repoRoot, + 'output', + 'imagegen', + 'edutainment-toca-world-map-concepts-20260523', +); +const styleReferencePath = path.join( + repoRoot, + 'public', + 'child-motion-demo', + 'picture-book-grass-stage.png', +); +const defaultTimeoutMs = 1000000; + +const commonPrompt = [ + '横屏 16:9 寓教于乐板块玩法入口概念图。', + '参考 Toca Life World 的地图组织方式:一个横向展开的大世界,允许向左和向右以“一屏一屏”的单位继续延伸和探索。', + '画面必须像儿童绘本插画,明亮、干净、温暖、圆润、手绘感强,保留 Genarrative 寓教于乐现有的草地、水彩、纸张纹理和童趣气质。', + '地图里要有清晰的道路、步道、河流、桥、分区节点和地标,但不要复制 Toca 的 logo、角色、字体、UI、建筑外形或配色。', + '每个玩法入口都要像一个可单独进入的区域,区域之间既连贯又能独立辨认,适合后续做横向滑动或分屏探索。', + '左右两侧都要有明显的延展感,边缘不能封死,重要场景不要都挤在画面中心。', + '中心只保留一个轻量主枢纽,左右各留出半屏到一屏的可继续探索空间。', + '不要出现文字、数字、字母、按钮文案、UI 面板、logo、水印、真实照片感、暗黑科技风、过度复杂的人物表情、现成商业 IP 角色。', +].join(''); + +const concepts = [ + { + id: 'edutainment-toca-world-01-river-town', + title: '河道城镇带', + prompt: [ + commonPrompt, + '版式方向:一条横向城镇带沿着河道和主路展开,左边是果园与识物区,中间是创作和主枢纽区,右边是音乐广场、自然观察和运动区。', + '每个区域像独立小镇街区,房屋和设施分布在道路两侧,远处山坡和天空继续延伸,整体感觉像可以一直向左右滑动。', + '左侧入口更偏自然与认知,右侧入口更偏探索与表演,中间保留一个安静广场作为聚合点。', + ].join(''), + }, + { + id: 'edutainment-toca-world-02-mountain-arc', + title: '山脊长廊', + prompt: [ + commonPrompt, + '版式方向:一条山脊和环山公路贯穿左右,地图像被拉长的山地长廊,城市、树林、草坡和小店沿山脊展开。', + '左屏是水果农场和画画工坊,中屏是社区广场和拼图桥,右屏是音乐小剧场、动物观察和冒险步道。', + '地形要有明显起伏,但道路必须连续、可走、可向两侧继续延伸,像同一世界的不同屏幕段落。', + ].join(''), + }, + { + id: 'edutainment-toca-world-03-island-chain', + title: '岛链世界', + prompt: [ + commonPrompt, + '版式方向:多个小岛通过桥梁、栈道和缆车相连,像一条横向的岛链,每个岛都像一屏可探索区域。', + '左侧岛是识物果园岛,中间岛是绘本创作岛和主广场,右侧岛是运动草地岛、音乐舞台岛和自然探索岛。', + '每个岛的边缘都要暗示下一岛的延伸,水面和道路自然把画面向左向右打开,不要封闭成一个圆形世界。', + ].join(''), + }, + { + id: 'edutainment-toca-world-04-city-park-spine', + title: '城市公园脊', + prompt: [ + commonPrompt, + '版式方向:一条城市公园主脊从左到右穿过整个画面,主脊两侧是不同功能街区,像儿童版的世界地图主干道。', + '左边是安静学习区和果园,中央是综合广场、画画棚和拼图桥,右边是音乐广场、运动场、动物观测点和森林边界。', + '整体更接近 Toca 式“世界菜单”感,但美术要更像绘本插画,不要扁平矢量 UI,也不要把地图画成球体。', + ].join(''), + }, +]; + +function readDotenv(fileName) { + const filePath = path.join(repoRoot, fileName); + if (!existsSync(filePath)) { + return {}; + } + + const values = {}; + for (const line of readFileSync(filePath, 'utf8').split(/\r?\n/u)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) { + continue; + } + const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u.exec(trimmed); + if (!match) { + continue; + } + let value = match[2].trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + values[match[1]] = value; + } + return values; +} + +function resolveEnv() { + const loaded = { + ...readDotenv('.env.example'), + ...readDotenv('.env.local'), + ...readDotenv('.env.secrets.local'), + ...process.env, + }; + return { + baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '') + .trim() + .replace(/\/+$/u, ''), + apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(), + timeoutMs: Number.parseInt( + String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs), + 10, + ), + }; +} + +function generationUrl(baseUrl) { + return baseUrl.endsWith('/v1') + ? `${baseUrl}/images/generations` + : `${baseUrl}/v1/images/generations`; +} + +function collectStringsByKey(value, targetKey, output) { + if (Array.isArray(value)) { + value.forEach((entry) => collectStringsByKey(entry, targetKey, output)); + return; + } + if (!value || typeof value !== 'object') { + return; + } + + for (const [key, nested] of Object.entries(value)) { + if (key === targetKey) { + if (typeof nested === 'string' && nested.trim()) { + output.push(nested.trim()); + } + if (Array.isArray(nested)) { + nested.forEach((entry) => { + if (typeof entry === 'string' && entry.trim()) { + output.push(entry.trim()); + } + }); + } + } + collectStringsByKey(nested, targetKey, output); + } +} + +function inferExtensionFromBytes(bytes) { + if (bytes.subarray(0, 8).equals(Buffer.from('\x89PNG\r\n\x1A\n', 'binary'))) { + return 'png'; + } + if (bytes.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff]))) { + return 'jpg'; + } + if ( + bytes.subarray(0, 4).toString('ascii') === 'RIFF' && + bytes.subarray(8, 12).toString('ascii') === 'WEBP' + ) { + return 'webp'; + } + return 'png'; +} + +function inferExtensionFromContentType(contentType) { + const normalized = contentType.split(';')[0]?.trim().toLowerCase(); + if (normalized === 'image/png') { + return 'png'; + } + if (normalized === 'image/webp') { + return 'webp'; + } + if (normalized === 'image/jpeg') { + return 'jpg'; + } + return 'png'; +} + +function toDataUrl(filePath) { + if (!existsSync(filePath)) { + return null; + } + const bytes = readFileSync(filePath); + const extension = inferExtensionFromBytes(bytes); + const mime = extension === 'jpg' ? 'image/jpeg' : `image/${extension}`; + return `data:${mime};base64,${bytes.toString('base64')}`; +} + +function buildRequestBody(concept, size, hasStyleReference) { + const body = { + model: 'gpt-image-2-all', + prompt: concept.prompt, + n: 1, + size, + }; + if (hasStyleReference) { + const ref = toDataUrl(styleReferencePath); + if (ref) { + body.image = [ref]; + } + } + return body; +} + +function buildDryRunRequestBody(concept, size, hasStyleReference) { + return { + model: 'gpt-image-2-all', + prompt: concept.prompt, + n: 1, + size, + imageReferenceCount: hasStyleReference ? 1 : 0, + }; +} + +async function fetchJson(url, options, timeoutMs) { + const abortController = new AbortController(); + const timer = setTimeout(() => abortController.abort(), timeoutMs); + try { + const response = await fetch(url, { + ...options, + signal: abortController.signal, + }); + const text = await response.text(); + if (!response.ok) { + throw new Error(`VectorEngine ${response.status}: ${text.slice(0, 600)}`); + } + return JSON.parse(text); + } catch (error) { + if (error?.name === 'AbortError') { + throw new Error(`VectorEngine request timed out after ${timeoutMs}ms`); + } + throw error; + } finally { + clearTimeout(timer); + } +} + +async function downloadUrl(url, timeoutMs) { + const abortController = new AbortController(); + const timer = setTimeout(() => abortController.abort(), timeoutMs); + try { + const response = await fetch(url, { signal: abortController.signal }); + if (!response.ok) { + throw new Error(`download ${response.status}`); + } + return { + bytes: Buffer.from(await response.arrayBuffer()), + extension: inferExtensionFromContentType( + response.headers.get('content-type') || '', + ), + }; + } catch (error) { + if (error?.name === 'AbortError') { + throw new Error(`Generated image download timed out after ${timeoutMs}ms`); + } + throw error; + } finally { + clearTimeout(timer); + } +} + +function outputPathFor(concept, extension = 'png') { + return path.join(outDir, `${concept.id}.${extension}`); +} + +function findExistingOutputPath(concept) { + for (const extension of ['png', 'jpg', 'jpeg', 'webp']) { + const candidate = outputPathFor(concept, extension); + if (existsSync(candidate)) { + return candidate; + } + } + return null; +} + +async function generateOne(env, concept, size) { + const payload = await fetchJson( + generationUrl(env.baseUrl), + { + method: 'POST', + headers: { + Authorization: `Bearer ${env.apiKey}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(buildRequestBody(concept, size, existsSync(styleReferencePath))), + }, + env.timeoutMs, + ); + + const urls = []; + const b64Images = []; + collectStringsByKey(payload, 'url', urls); + collectStringsByKey(payload, 'image', urls); + collectStringsByKey(payload, 'image_url', urls); + collectStringsByKey(payload, 'b64_json', b64Images); + + let image; + const imageUrl = [...new Set(urls)].find((url) => /^https?:\/\//u.test(url)); + if (imageUrl) { + image = await downloadUrl(imageUrl, env.timeoutMs); + } else if (b64Images[0]) { + const bytes = Buffer.from(b64Images[0], 'base64'); + image = { + bytes, + extension: inferExtensionFromBytes(bytes), + }; + } else { + throw new Error(`VectorEngine returned no image for ${concept.id}`); + } + + mkdirSync(outDir, { recursive: true }); + const outputPath = outputPathFor(concept, image.extension); + writeFileSync(outputPath, image.bytes); + return outputPath; +} + +const args = new Map(); +for (let index = 2; index < process.argv.length; index += 1) { + const raw = process.argv[index]; + if (raw.startsWith('--')) { + const next = process.argv[index + 1]; + if (next && !next.startsWith('--')) { + args.set(raw, next); + index += 1; + } else { + args.set(raw, true); + } + } +} + +const size = String(args.get('--size') || '2048x1152'); +const dryRun = args.has('--dry-run') || !args.has('--live'); +const selectedIds = String(args.get('--only') || '') + .split(',') + .map((value) => value.trim()) + .filter(Boolean); +const selectedConcepts = concepts.filter( + (concept) => selectedIds.length === 0 || selectedIds.includes(concept.id), +); +const hasStyleReference = existsSync(styleReferencePath); + +if (dryRun) { + console.log( + JSON.stringify( + { + mode: 'dry-run', + outDir, + size, + hasStyleReference, + count: selectedConcepts.length, + requests: selectedConcepts.map((concept) => ({ + id: concept.id, + title: concept.title, + body: buildDryRunRequestBody(concept, size, hasStyleReference), + })), + }, + null, + 2, + ), + ); + process.exit(0); +} + +const env = resolveEnv(); +if (!env.baseUrl || !env.apiKey) { + console.error( + JSON.stringify({ + ok: false, + error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY', + hasBaseUrl: Boolean(env.baseUrl), + hasApiKey: Boolean(env.apiKey), + }), + ); + process.exit(1); +} + +const files = []; +const generatedFileById = new Map(); +for (const concept of selectedConcepts) { + console.log(`Generating ${concept.id}...`); + const file = await generateOne(env, concept, size); + files.push(file); + generatedFileById.set(concept.id, file); +} + +const metadataFiles = concepts + .map((concept) => { + const file = generatedFileById.get(concept.id) ?? findExistingOutputPath(concept); + if (!file) { + return null; + } + return { + id: concept.id, + title: concept.title, + file, + prompt: concept.prompt, + }; + }) + .filter(Boolean); + +writeFileSync( + path.join(outDir, 'generation-metadata.json'), + JSON.stringify( + { + model: 'gpt-image-2-all', + size, + generatedAt: new Date().toISOString(), + styleReference: hasStyleReference ? styleReferencePath : null, + generatedIds: selectedConcepts.map((concept) => concept.id), + files: metadataFiles, + }, + null, + 2, + ), +); + +console.log(JSON.stringify({ ok: true, count: files.length, files }, null, 2)); diff --git a/scripts/generate-spacetime-bindings.mjs b/scripts/generate-spacetime-bindings.mjs new file mode 100644 index 00000000..a49f1e60 --- /dev/null +++ b/scripts/generate-spacetime-bindings.mjs @@ -0,0 +1,334 @@ +import {spawn} from 'node:child_process'; +import {existsSync} from 'node:fs'; +import {cp, mkdir, readdir, rm, stat} from 'node:fs/promises'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(SCRIPT_DIR, '..'); +const MODULE_PATH = path.join(REPO_ROOT, 'server-rs', 'crates', 'spacetime-module'); + +const TARGETS = [ + { + name: 'Rust', + lang: 'rust', + tempName: 'rs', + outDir: path.join( + REPO_ROOT, + 'server-rs', + 'crates', + 'spacetime-client', + 'src', + 'module_bindings', + ), + entryFile: path.join( + REPO_ROOT, + 'server-rs', + 'crates', + 'spacetime-client', + 'src', + 'module_bindings.rs', + ), + }, +]; + +const args = new Set(process.argv.slice(2)); +const KNOWN_ARGS = new Set(['--rust-only']); + +for (const arg of args) { + if (!KNOWN_ARGS.has(arg)) { + console.error(`[spacetime:generate] 未知参数: ${arg}`); + process.exit(1); + } +} + +if (!existsSync(path.join(MODULE_PATH, 'Cargo.toml'))) { + console.error(`[spacetime:generate] 未找到模块: ${MODULE_PATH}`); + process.exit(1); +} + +const tempRoot = resolveTempRoot(); +assertSafeTempRoot(tempRoot); +const selectedTargets = TARGETS.filter((target) => shouldRunTarget(target.lang)); + +if (selectedTargets.length === 0) { + console.error('[spacetime:generate] 没有需要生成的目标。'); + process.exit(1); +} + +await mkdir(tempRoot, {recursive: true}); + +for (const target of selectedTargets) { + const tempOutDir = path.join(tempRoot, target.tempName); + await recreateTempDir(tempOutDir); + + console.log(`[spacetime:generate] 生成 ${target.name} bindings 到短路径: ${tempOutDir}`); + await generateBindings(target, tempOutDir); + + const fileCount = await countFiles(tempOutDir); + if (fileCount === 0) { + throw new Error(`${target.name} bindings 未生成任何文件。`); + } + + console.log(`[spacetime:generate] 同步 ${fileCount} 个文件到 ${target.outDir}`); + await replaceGeneratedDir(tempOutDir, target.outDir); + await moveGeneratedEntryFile(target); +} + +await rm(tempRoot, {recursive: true, force: true}); +console.log('[spacetime:generate] bindings 生成完成。'); + +function shouldRunTarget(lang) { + if (args.has('--rust-only')) { + return lang === 'rust'; + } + + return true; +} + +function resolveTempRoot() { + if (process.env.GENARRATIVE_BINDGEN_TEMP_ROOT) { + return path.resolve(process.env.GENARRATIVE_BINDGEN_TEMP_ROOT); + } + + // Windows 下 SpacetimeDB CLI 2.1.0 会把所有生成文件路径一次性传给 formatter; + // Rust bindings 文件数较多,输出到仓库深目录时容易触发 CreateProcess 路径总长限制。 + if (process.platform === 'win32') { + return path.join(path.parse(REPO_ROOT).root, '.genarrative-bindgen'); + } + + return path.join(REPO_ROOT, 'tmp', 'spacetime-bindgen'); +} + +async function recreateTempDir(dir) { + assertInside(dir, tempRoot, '临时生成目录'); + await rm(dir, {recursive: true, force: true}); + await mkdir(dir, {recursive: true}); +} + +async function replaceGeneratedDir(fromDir, toDir) { + assertInside(toDir, REPO_ROOT, '仓库生成目录'); + await rm(toDir, {recursive: true, force: true}); + await mkdir(toDir, {recursive: true}); + const entries = await readdir(fromDir, {withFileTypes: true}); + + for (const entry of entries) { + await cp(path.join(fromDir, entry.name), path.join(toDir, entry.name), { + recursive: true, + force: true, + }); + } +} + +async function moveGeneratedEntryFile(target) { + if (!target.entryFile) { + return; + } + + assertInside(target.entryFile, REPO_ROOT, '生成入口文件'); + + const generatedEntryFile = + findExistingFile(path.join(target.outDir, 'module_bindings.rs')) ?? + findExistingFile(path.join(target.outDir, 'mod.rs')); + + if (!generatedEntryFile) { + throw new Error( + `${target.name} bindings 缺少入口文件: ${path.join(target.outDir, 'module_bindings.rs')} / ${path.join(target.outDir, 'mod.rs')}`, + ); + } + + await rm(target.entryFile, {force: true}); + await cp(generatedEntryFile, target.entryFile, {force: true}); + if (path.resolve(generatedEntryFile) !== path.resolve(target.entryFile)) { + await rm(generatedEntryFile, {force: true}); + } +} + +function findExistingFile(candidate) { + return existsSync(candidate) ? candidate : undefined; +} + +function assertInside(candidate, parent, label) { + const relative = path.relative(path.resolve(parent), path.resolve(candidate)); + if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) { + throw new Error(`${label} 不在预期目录内: ${candidate}`); + } +} + +function assertSafeTempRoot(dir) { + const resolved = path.resolve(dir); + const parsed = path.parse(resolved); + const basename = path.basename(resolved).toLowerCase(); + + if (resolved === path.resolve(REPO_ROOT) || resolved === parsed.root) { + throw new Error(`临时根目录不允许指向仓库或磁盘根目录: ${resolved}`); + } + + if (!basename.includes('bindgen')) { + throw new Error(`临时根目录必须是明确的 bindings 生成目录: ${resolved}`); + } +} + +function buildGenerateArgs(target, outDir) { + const generateArgs = [ + 'generate', + '--no-config', + '--lang', + target.lang, + '--out-dir', + outDir, + '--module-path', + MODULE_PATH, + '--include-private', + '--yes', + ]; + + return generateArgs; +} + +async function generateBindings(target, outDir) { + const result = await run('spacetime', buildGenerateArgs(target, outDir), { + allowGeneratedFormatFailure: target.lang === 'rust', + }); + + if (result.generatedFormatFailed) { + // Windows 下 SpacetimeDB CLI 2.1.0 会把所有 Rust 文件一次性传给 formatter; + // 这里只接管“文件已生成但 CLI 格式化失败”的尾段,并仍然只同步生成目录。 + console.warn( + `[spacetime:generate] ${target.name} bindings 已生成,但 SpacetimeDB CLI 自带格式化失败;改用短路径分批 rustfmt。`, + ); + await formatRustBindings(outDir); + } +} + +async function formatRustBindings(outDir) { + const rustFiles = await collectRustFiles(outDir); + if (rustFiles.length === 0) { + throw new Error(`Rust bindings 未生成任何 .rs 文件,无法格式化: ${outDir}`); + } + + for (const chunk of chunkCommandArgs(rustFiles)) { + await run('rustfmt', ['--edition', '2024', ...chunk]); + } +} + +async function collectRustFiles(dir) { + const files = []; + const entries = await readdir(dir, {withFileTypes: true}); + + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + files.push(...(await collectRustFiles(entryPath))); + continue; + } + + if (entry.isFile() && entry.name.endsWith('.rs')) { + files.push(entryPath); + } + } + + return files; +} + +function chunkCommandArgs(argsToChunk) { + // Windows CreateProcess 受命令行长度限制;分批能避免 bindings 文件变多后再次失败。 + const maxCommandLineChars = process.platform === 'win32' ? 20_000 : 100_000; + const chunks = []; + let current = []; + let currentLength = 0; + + for (const arg of argsToChunk) { + const argLength = arg.length + 3; + if (current.length > 0 && currentLength + argLength > maxCommandLineChars) { + chunks.push(current); + current = []; + currentLength = 0; + } + + current.push(arg); + currentLength += argLength; + } + + if (current.length > 0) { + chunks.push(current); + } + + return chunks; +} + +function run(command, commandArgs, options = {}) { + return new Promise((resolve, reject) => { + const child = spawn(command, commandArgs, { + cwd: REPO_ROOT, + env: process.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + let output = ''; + + child.stdout.on('data', (chunk) => { + const text = chunk.toString(); + output += text; + process.stdout.write(text); + }); + + child.stderr.on('data', (chunk) => { + const text = chunk.toString(); + output += text; + process.stderr.write(text); + }); + + child.on('error', reject); + child.on('exit', (code, signal) => { + if (signal) { + reject(new Error(`${command} 被信号中断: ${signal}`)); + return; + } + + const generatedFormatFailed = output.includes('Could not format generated files'); + + if (generatedFormatFailed && options.allowGeneratedFormatFailure) { + console.warn( + `[spacetime:generate] ${command} generated files but formatting failed; continuing with validation.`, + ); + resolve({generatedFormatFailed}); + return; + } + + if (generatedFormatFailed) { + reject(new Error(`${command} generated files but formatting failed.`)); + return; + } + + if (code === 0) { + resolve({generatedFormatFailed: false}); + return; + } + + reject(new Error(`${command} 退出码: ${code ?? 'unknown'}`)); + }); + }); +} + +async function countFiles(dir) { + let count = 0; + const entries = await readdir(dir, {withFileTypes: true}); + + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + count += await countFiles(entryPath); + continue; + } + + if (entry.isFile() || (await stat(entryPath)).isFile()) { + count += 1; + } + } + + return count; +} diff --git a/server-rs/crates/api-server/src/admin.rs b/server-rs/crates/api-server/src/admin.rs index cd72a6d0..80ab9045 100644 --- a/server-rs/crates/api-server/src/admin.rs +++ b/server-rs/crates/api-server/src/admin.rs @@ -251,6 +251,9 @@ fn map_admin_creation_entry_type_config( visible: entry.visible, open: entry.open, sort_order: entry.sort_order, + category_id: entry.category_id, + category_label: entry.category_label, + category_sort_order: entry.category_sort_order, updated_at_micros: entry.updated_at_micros, } } @@ -275,6 +278,9 @@ fn validate_admin_creation_entry_config( visible: payload.visible, open: payload.open, sort_order: payload.sort_order, + category_id: payload.category_id.trim().to_string(), + category_label: payload.category_label.trim().to_string(), + category_sort_order: payload.category_sort_order, }) } diff --git a/server-rs/crates/api-server/src/creation_entry_config.rs b/server-rs/crates/api-server/src/creation_entry_config.rs index fda55d57..166225d1 100644 --- a/server-rs/crates/api-server/src/creation_entry_config.rs +++ b/server-rs/crates/api-server/src/creation_entry_config.rs @@ -143,6 +143,15 @@ pub(crate) fn default_creation_entry_config_response() -> CreationEntryConfigRes title: module_runtime::DEFAULT_CREATION_ENTRY_MODAL_TITLE.to_string(), description: module_runtime::DEFAULT_CREATION_ENTRY_MODAL_DESCRIPTION.to_string(), }, + event_banner: module_runtime::CreationEntryEventBannerSnapshot { + title: module_runtime::DEFAULT_CREATION_ENTRY_EVENT_TITLE.to_string(), + description: module_runtime::DEFAULT_CREATION_ENTRY_EVENT_DESCRIPTION.to_string(), + cover_image_src: module_runtime::DEFAULT_CREATION_ENTRY_EVENT_COVER_IMAGE_SRC + .to_string(), + prize_pool_mud_points: module_runtime::DEFAULT_CREATION_ENTRY_EVENT_PRIZE_POOL_MUD_POINTS, + starts_at_text: module_runtime::DEFAULT_CREATION_ENTRY_EVENT_STARTS_AT_TEXT.to_string(), + ends_at_text: module_runtime::DEFAULT_CREATION_ENTRY_EVENT_ENDS_AT_TEXT.to_string(), + }, creation_types: module_runtime::default_creation_entry_type_snapshots(0), updated_at_micros: 0, }) @@ -259,5 +268,8 @@ mod tests { assert!(baby_object_match.open); assert_eq!(baby_object_match.badge, "\u{53ef}\u{521b}\u{5efa}"); assert_eq!(baby_object_match.sort_order, 90); + assert_eq!(baby_object_match.category_id, "character"); + assert_eq!(baby_object_match.category_label, "\u{89d2}\u{8272}\u{521b}\u{4f5c}"); + assert_eq!(baby_object_match.category_sort_order, 40); } } diff --git a/server-rs/crates/api-server/src/match3d/tests.rs b/server-rs/crates/api-server/src/match3d/tests.rs index 905a1697..425fcb69 100644 --- a/server-rs/crates/api-server/src/match3d/tests.rs +++ b/server-rs/crates/api-server/src/match3d/tests.rs @@ -874,6 +874,7 @@ fn match3d_regenerated_asset_keeps_stable_identity_and_side_assets() { container_image_object_key: None, status: "image_ready".to_string(), error: None, + ..Default::default() }); let mut generated_asset = test_match3d_generated_item_asset(99, "新草莓"); generated_asset.image_src = @@ -1062,6 +1063,7 @@ fn match3d_background_asset_requires_background_and_container_images() { container_image_object_key: None, status: "image_ready".to_string(), error: None, + ..Default::default() }; let with_container = Match3DGeneratedBackgroundAsset { container_prompt: Some("果园容器".to_string()), @@ -1108,6 +1110,7 @@ fn match3d_default_cover_prefers_generated_container_ui_image() { container_image_object_key: None, status: "image_ready".to_string(), error: None, + ..Default::default() }), status: "image_ready".to_string(), error: None, @@ -1169,7 +1172,7 @@ fn match3d_cover_reference_prompt_marks_reference_images() { #[test] fn match3d_cover_edit_prompt_preserves_uploaded_image() { - let prompt = build_match3d_cover_edit_prompt("水果封面"); + let prompt = build_match3d_cover_uploaded_reference_prompt("水果封面"); assert!(prompt.contains("上传的封面图作为第一优先级")); assert!(prompt.contains("保留主图的主体、构图、视角和主要配色")); @@ -1212,6 +1215,7 @@ fn match3d_fallback_work_profile_keeps_generated_background_asset() { ), status: "image_ready".to_string(), error: None, + ..Default::default() }), status: "image_ready".to_string(), error: None, @@ -1349,6 +1353,7 @@ fn match3d_agent_session_response_hydrates_persisted_ui_assets() { ), status: "image_ready".to_string(), error: None, + ..Default::default() }), status: "image_ready".to_string(), error: None, @@ -1424,6 +1429,7 @@ fn match3d_agent_session_response_keeps_draft_ui_assets_without_work_detail_hydr ), status: "image_ready".to_string(), error: None, + ..Default::default() }), status: "image_ready".to_string(), error: None, @@ -1807,6 +1813,7 @@ fn match3d_work_summary_marks_complete_generated_assets_ready() { ), status: "image_ready".to_string(), error: None, + ..Default::default() }), ..test_match3d_generated_item_asset(1, "草莓") }]; diff --git a/server-rs/crates/api-server/src/state.rs b/server-rs/crates/api-server/src/state.rs index 6c6d1c60..bfda79d0 100644 --- a/server-rs/crates/api-server/src/state.rs +++ b/server-rs/crates/api-server/src/state.rs @@ -516,6 +516,10 @@ impl AppState { visible: enabled, open: enabled, sort_order: i32::try_from(config.creation_types.len()).unwrap_or(i32::MAX), + category_id: module_runtime::DEFAULT_CREATION_ENTRY_CATEGORY_ID.to_string(), + category_label: module_runtime::DEFAULT_CREATION_ENTRY_CATEGORY_LABEL + .to_string(), + category_sort_order: 0, updated_at_micros: 0, }, ); diff --git a/server-rs/crates/api-server/src/vector_engine_audio_generation.rs b/server-rs/crates/api-server/src/vector_engine_audio_generation.rs index 04e51f6e..e44f141e 100644 --- a/server-rs/crates/api-server/src/vector_engine_audio_generation.rs +++ b/server-rs/crates/api-server/src/vector_engine_audio_generation.rs @@ -233,15 +233,13 @@ pub async fn create_visual_novel_sound_effect_task( } pub async fn create_sound_effect_task( - State(state): State, + State(_state): State, axum::extract::Extension(request_context): axum::extract::Extension, payload: Result, JsonRejection>, ) -> Result, Response> { - let Json(payload) = parse_json_payload(&request_context, payload)?; - create_sound_effect_task_response(&state, payload.prompt, payload.duration, payload.seed) - .await - .map(|task| json_success_body(Some(&request_context), task)) - .map_err(|error| error.into_response_with_context(Some(&request_context))) + let _ = parse_json_payload(&request_context, payload)?; + Err(creation_audio_generation_disabled_error() + .into_response_with_context(Some(&request_context))) } pub(crate) async fn generate_sound_effect_asset_for_creation( @@ -874,27 +872,8 @@ fn build_visual_novel_audio_target( fn build_creation_audio_target( payload: creation_audio::PublishGeneratedAudioAssetRequest, - slot: AudioAssetSlot, + _slot: AudioAssetSlot, ) -> Result { - if matches!(slot, AudioAssetSlot::SoundEffect) - && payload.entity_kind.trim() == "wooden_fish_work" - && payload.slot.trim() == "hit_sound" - && payload.asset_kind.trim() == "wooden_fish_hit_sound" - && payload.storage_prefix - == Some(creation_audio::CreationAudioStoragePrefix::WoodenFishAssets) - { - let entity_id = normalize_limited_text(&payload.entity_id, "entityId", 160)?; - return Ok(AudioAssetBindingTarget { - storage_scope: payload.entity_kind.trim().to_string(), - entity_kind: payload.entity_kind.trim().to_string(), - entity_id, - slot: payload.slot.trim().to_string(), - asset_kind: payload.asset_kind.trim().to_string(), - profile_id: normalize_optional_text(payload.profile_id.as_deref()), - storage_prefix: LegacyAssetPrefix::WoodenFishAssets, - }); - } - Err(creation_audio_generation_disabled_error_for_target(payload)) } @@ -1473,7 +1452,7 @@ mod tests { } #[test] - fn disabled_creation_audio_targets_return_gone_except_wooden_fish_sound_effects() { + fn disabled_creation_audio_targets_return_gone_including_wooden_fish_sound_effects() { let payload = creation_audio::PublishGeneratedAudioAssetRequest { entity_kind: "puzzle_work".to_string(), entity_id: "puzzle-profile-1".to_string(), @@ -1515,13 +1494,9 @@ mod tests { profile_id: Some("wooden-fish-profile-1".to_string()), storage_prefix: Some(creation_audio::CreationAudioStoragePrefix::WoodenFishAssets), }; - let target = build_creation_audio_target(payload, AudioAssetSlot::SoundEffect) - .expect("wooden fish hit sound target should be enabled"); - - assert_eq!(target.entity_kind, "wooden_fish_work"); - assert_eq!(target.slot, "hit_sound"); - assert_eq!(target.storage_prefix, LegacyAssetPrefix::WoodenFishAssets); - assert_eq!(target.storage_scope, "wooden_fish_work"); + let error = build_creation_audio_target(payload, AudioAssetSlot::SoundEffect) + .expect_err("wooden fish hit sound target should be disabled"); + assert_eq!(error.status_code(), StatusCode::GONE); } #[test] diff --git a/server-rs/crates/api-server/src/wooden_fish.rs b/server-rs/crates/api-server/src/wooden_fish.rs index 68be6000..4eedff39 100644 --- a/server-rs/crates/api-server/src/wooden_fish.rs +++ b/server-rs/crates/api-server/src/wooden_fish.rs @@ -42,9 +42,6 @@ use crate::{ platform_errors::map_oss_error, request_context::RequestContext, state::AppState, - vector_engine_audio_generation::{ - GeneratedCreationAudioTarget, generate_sound_effect_asset_for_creation, - }, }; const WOODEN_FISH_PROVIDER: &str = "wooden-fish"; @@ -53,16 +50,17 @@ const WOODEN_FISH_RUNTIME_PROVIDER: &str = "wooden-fish-runtime"; const WOODEN_FISH_TEMPLATE_ID: &str = "wooden-fish"; const WOODEN_FISH_TEMPLATE_NAME: &str = "敲木鱼"; const DEFAULT_HIT_OBJECT_PROMPT: &str = "默认敲击物图案,圆润木质质感,透明背景"; -const DEFAULT_HIT_SOUND_PROMPT: &str = "清脆短促的木鱼敲击声"; const DEFAULT_HIT_OBJECT_ASSET_ID: &str = "wooden-fish-default-hit-object"; const DEFAULT_HIT_OBJECT_IMAGE_SRC: &str = "/wooden-fish/default-hit-object.png"; +const DEFAULT_HIT_SOUND_ASSET_ID: &str = "wooden-fish-default-hit-sound"; +const DEFAULT_HIT_SOUND_AUDIO_SRC: &str = "/wooden-fish/default-hit-sound.mp3"; const WOODEN_FISH_ENTITY_KIND: &str = "wooden_fish_work"; const WOODEN_FISH_HIT_OBJECT_SLOT: &str = "hit_object"; const WOODEN_FISH_HIT_OBJECT_ASSET_KIND: &str = "wooden_fish_hit_object"; const WOODEN_FISH_BACKGROUND_SLOT: &str = "background"; const WOODEN_FISH_BACKGROUND_ASSET_KIND: &str = "wooden_fish_background"; -const WOODEN_FISH_HIT_SOUND_SLOT: &str = "hit_sound"; -const WOODEN_FISH_HIT_SOUND_ASSET_KIND: &str = "wooden_fish_hit_sound"; +const WOODEN_FISH_BACK_BUTTON_SLOT: &str = "back_button"; +const WOODEN_FISH_BACK_BUTTON_ASSET_KIND: &str = "wooden_fish_back_button"; const WOODEN_FISH_HIT_SOUND_DURATION_SECONDS: u8 = 3; const DEFAULT_HIT_OBJECT_REFERENCE_BYTES: &[u8] = include_bytes!(concat!( env!("CARGO_MANIFEST_DIR"), @@ -154,14 +152,7 @@ pub async fn execute_wooden_fish_action( &mut payload, ) .await?; - maybe_generate_hit_sound_asset( - &state, - &request_context, - &session_id, - owner_user_id.as_str(), - &mut payload, - ) - .await?; + maybe_generate_hit_sound_asset(&mut payload); let response = state .spacetime_client() .execute_wooden_fish_action(session_id, owner_user_id, payload) @@ -371,16 +362,15 @@ fn build_wooden_fish_draft(payload: &WoodenFishWorkspaceCreateRequest) -> Wooden .as_ref() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()), - hit_sound_prompt: payload - .hit_sound_prompt - .as_ref() - .map(|value| value.trim().to_string()) - .filter(|value| !value.is_empty()) - .or_else(|| Some(DEFAULT_HIT_SOUND_PROMPT.to_string())), + hit_sound_prompt: None, floating_words: normalize_floating_words(payload.floating_words.clone()), hit_object_asset: None, background_asset: None, - hit_sound_asset: payload.hit_sound_asset.clone(), + back_button_asset: None, + hit_sound_asset: payload + .hit_sound_asset + .clone() + .or_else(|| Some(default_wooden_fish_hit_sound_asset())), cover_image_src: None, generation_status: WoodenFishGenerationStatus::Draft, } @@ -418,7 +408,10 @@ async fn maybe_generate_hit_object_asset( ) { return Ok(()); } - if payload.hit_object_asset.is_some() && payload.background_asset.is_some() { + if payload.hit_object_asset.is_some() + && payload.background_asset.is_some() + && payload.back_button_asset.is_some() + { return Ok(()); } @@ -447,6 +440,7 @@ async fn maybe_generate_hit_object_asset( })?; payload.hit_object_asset = Some(generated.hit_object_asset); payload.background_asset = Some(generated.background_asset); + payload.back_button_asset = Some(generated.back_button_asset); Ok(()) } @@ -463,6 +457,18 @@ fn default_wooden_fish_hit_object_asset() -> WoodenFishImageAsset { } } +fn default_wooden_fish_hit_sound_asset() -> WoodenFishAudioAsset { + WoodenFishAudioAsset { + asset_id: DEFAULT_HIT_SOUND_ASSET_ID.to_string(), + audio_src: DEFAULT_HIT_SOUND_AUDIO_SRC.to_string(), + audio_object_key: "public/wooden-fish/default-hit-sound.mp3".to_string(), + asset_object_id: DEFAULT_HIT_SOUND_ASSET_ID.to_string(), + source: "bundled-default".to_string(), + prompt: Some("默认木鱼音".to_string()), + duration_ms: Some(u32::from(WOODEN_FISH_HIT_SOUND_DURATION_SECONDS) * 1_000), + } +} + fn is_default_hit_object_prompt(prompt: &str) -> bool { let normalized = normalize_hit_object_prompt_for_default_match(prompt); normalized.is_empty() @@ -530,130 +536,27 @@ async fn resolve_hit_object_profile_id( }) } -async fn maybe_generate_hit_sound_asset( - state: &AppState, - request_context: &RequestContext, - session_id: &str, - owner_user_id: &str, - payload: &mut WoodenFishActionRequest, -) -> Result<(), Response> { +fn maybe_generate_hit_sound_asset(payload: &mut WoodenFishActionRequest) { if !matches!( payload.action_type, shared_contracts::wooden_fish::WoodenFishActionType::CompileDraft | shared_contracts::wooden_fish::WoodenFishActionType::GenerateHitSound + | shared_contracts::wooden_fish::WoodenFishActionType::ReplaceHitSound ) { - return Ok(()); + return; } - if matches!( - payload.action_type, - shared_contracts::wooden_fish::WoodenFishActionType::CompileDraft - ) && payload.hit_sound_asset.is_some() - { - return Ok(()); + payload.hit_sound_prompt = None; + if payload.hit_sound_asset.is_some() { + return; } - let profile_id = - resolve_hit_object_profile_id(state, request_context, session_id, owner_user_id, payload) - .await?; - payload.profile_id = Some(profile_id.clone()); - let prompt = payload - .hit_sound_prompt - .as_deref() - .map(|value| clean_string(value, DEFAULT_HIT_SOUND_PROMPT)) - .filter(|value| !value.trim().is_empty()) - .unwrap_or_else(|| DEFAULT_HIT_SOUND_PROMPT.to_string()); - - let asset = generate_wooden_fish_hit_sound_asset( - state, - owner_user_id, - profile_id.as_str(), - prompt.as_str(), - ) - .await - .map_err(|error| { - wooden_fish_error_response(request_context, WOODEN_FISH_CREATION_PROVIDER, error) - })?; - payload.hit_sound_asset = Some(asset); - Ok(()) -} - -async fn generate_wooden_fish_hit_sound_asset( - state: &AppState, - owner_user_id: &str, - profile_id: &str, - prompt: &str, -) -> Result { - let final_prompt = build_wooden_fish_hit_sound_prompt(prompt); - let generated = generate_sound_effect_asset_for_creation( - state, - owner_user_id, - final_prompt.clone(), - Some(WOODEN_FISH_HIT_SOUND_DURATION_SECONDS), - None, - GeneratedCreationAudioTarget { - entity_kind: WOODEN_FISH_ENTITY_KIND.to_string(), - entity_id: profile_id.to_string(), - slot: WOODEN_FISH_HIT_SOUND_SLOT.to_string(), - asset_kind: WOODEN_FISH_HIT_SOUND_ASSET_KIND.to_string(), - profile_id: Some(profile_id.to_string()), - storage_prefix: LegacyAssetPrefix::WoodenFishAssets, - }, - ) - .await?; - map_generated_creation_audio_to_wooden_fish_asset( - profile_id, - final_prompt.as_str(), - generated, - WOODEN_FISH_HIT_SOUND_DURATION_SECONDS, - ) -} - -fn build_wooden_fish_hit_sound_prompt(prompt: &str) -> String { - format!( - "为敲木鱼玩法生成一次点击触发的短促敲击音效:{}。要求:干净、清脆、无旋律、无环境噪声、无语音、无文字提示音,适合高频点击时叠加播放。", - clean_string(prompt, DEFAULT_HIT_SOUND_PROMPT) - ) -} - -fn map_generated_creation_audio_to_wooden_fish_asset( - profile_id: &str, - prompt: &str, - asset: shared_contracts::creation_audio::CreationAudioAsset, - duration_seconds: u8, -) -> Result { - let asset_object_id = asset - .asset_object_id - .filter(|value| !value.trim().is_empty()) - .ok_or_else(|| { - AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "vector-engine", - "message": "敲木鱼音效生成完成但缺少资产对象 ID", - })) - })?; - let audio_object_key = asset.audio_src.trim().trim_start_matches('/').to_string(); - if audio_object_key.is_empty() { - return Err( - AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "vector-engine", - "message": "敲木鱼音效生成完成但缺少音频地址", - })), - ); - } - - Ok(WoodenFishAudioAsset { - asset_id: format!("{profile_id}-hit-sound-{}", asset.task_id), - audio_src: asset.audio_src, - audio_object_key, - asset_object_id, - source: "generated".to_string(), - prompt: asset.prompt.or_else(|| Some(prompt.to_string())), - duration_ms: Some(u32::from(duration_seconds) * 1_000), - }) + payload.hit_sound_asset = Some(default_wooden_fish_hit_sound_asset()); } struct WoodenFishGeneratedImageAssets { hit_object_asset: WoodenFishImageAsset, background_asset: WoodenFishImageAsset, + back_button_asset: WoodenFishImageAsset, } async fn generate_wooden_fish_image_assets( @@ -674,7 +577,7 @@ async fn generate_wooden_fish_image_assets( let theme_reference_image = resolve_wooden_fish_theme_reference_image(clean_reference_image_src)?; - let (hit_object_asset, background_reference_image) = + let (hit_object_asset, hit_object_reference_image) = if should_generate_wooden_fish_hit_object(prompt, clean_reference_image_src) { let hit_object_prompt = build_wooden_fish_hit_object_prompt(theme.as_str()); let mut reference_images = vec![default_reference_image.clone()]; @@ -699,8 +602,11 @@ async fn generate_wooden_fish_image_assets( "message": "生成敲木鱼敲击物图案失败:上游未返回图片", })) })?; - let background_reference_image = - downloaded_wooden_fish_reference_image(&image, "wooden-fish-generated-hit-object"); + let image = prepare_wooden_fish_hit_object_image_for_persist(image)?; + let hit_object_reference_image = downloaded_wooden_fish_reference_image( + &image, + "wooden-fish-generated-hit-object-transparent", + ); let hit_object_asset = persist_wooden_fish_image_asset( state, owner_user_id, @@ -719,7 +625,7 @@ async fn generate_wooden_fish_image_assets( }, ) .await?; - (hit_object_asset, background_reference_image) + (hit_object_asset, hit_object_reference_image) } else { ( default_wooden_fish_hit_object_asset(), @@ -734,7 +640,7 @@ async fn generate_wooden_fish_image_assets( background_prompt.as_str(), None, "9:16", - &background_reference_image, + &hit_object_reference_image, "生成敲木鱼背景环境图失败", ) .await?; @@ -749,6 +655,8 @@ async fn generate_wooden_fish_image_assets( "message": "生成敲木鱼背景环境图失败:上游未返回图片", })) })?; + let background_reference_image = + downloaded_wooden_fish_reference_image(&background_image, "wooden-fish-generated-background"); let background_asset = persist_wooden_fish_image_asset( state, owner_user_id, @@ -767,23 +675,79 @@ async fn generate_wooden_fish_image_assets( }, ) .await?; + let back_button_prompt = build_wooden_fish_back_button_prompt(theme.as_str()); + let back_button_generated = create_openai_image_edit_with_references( + &http_client, + &settings, + back_button_prompt.as_str(), + None, + "1:1", + 1, + &[ + hit_object_reference_image.clone(), + background_reference_image, + ], + "生成敲木鱼返回按钮图失败", + ) + .await?; + let back_button_task_id = back_button_generated.task_id.clone(); + let back_button_image = back_button_generated + .images + .into_iter() + .next() + .ok_or_else(|| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": "vector-engine", + "message": "生成敲木鱼返回按钮图失败:上游未返回图片", + })) + })?; + let back_button_image = prepare_wooden_fish_green_screen_image_for_persist( + back_button_image, + "敲木鱼返回按钮图", + )?; + let back_button_asset = persist_wooden_fish_image_asset( + state, + owner_user_id, + session_id, + profile_id, + back_button_task_id.as_str(), + back_button_prompt.as_str(), + back_button_image, + current_utc_micros(), + WoodenFishImageSlotPersistSpec { + slot: WOODEN_FISH_BACK_BUTTON_SLOT, + asset_kind: WOODEN_FISH_BACK_BUTTON_ASSET_KIND, + asset_id_part: "back-button", + width: 1024, + height: 1024, + }, + ) + .await?; Ok(WoodenFishGeneratedImageAssets { hit_object_asset, background_asset, + back_button_asset, }) } fn build_wooden_fish_hit_object_prompt(prompt: &str) -> String { format!( - "生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。\n新主题为:{}", + "生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。尺寸1:1,先输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影、无道具,主体完整居中,主体边缘必须干净,不要直接输出透明底。随后由服务端对绿色背景主体图做抠图去除绿色背景。最终结果只保留单个敲击物图案,禁止黑底、白底、棋盘格、纸板底或任何实底背景;主体本身不要使用与绿幕接近的纯绿色,若新主题天然包含绿色,请改用偏深、偏黄或偏蓝的绿色并与绿幕清晰区分。\n新主题为:{}", clean_string(prompt, DEFAULT_HIT_OBJECT_PROMPT) ) } fn build_wooden_fish_background_prompt(prompt: &str) -> String { format!( - "生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。\n主题为:{}", + "生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。尺寸竖屏9:16。参考图必须是第一步敲击物抠图完成后的透明图,不继承任何绿色底色、绿幕底色或纯绿色画布,并要求最终输出完整不透明的背景环境图。中央主体预留区必须保持干净,画面中央 40% 区域禁止出现主题主体、主体局部特写、主体轮廓影子、重复元素或主题主体的局部碎片;主题元素只允许出现在外围氛围,不得把主题物品画在画面中央,也不要把主题物品作为背景中心装饰。\n主题为:{}", + clean_string(prompt, DEFAULT_HIT_OBJECT_PROMPT) + ) +} + +fn build_wooden_fish_back_button_prompt(prompt: &str) -> String { + format!( + "生成敲木鱼左上角返回按钮图。要求以参考图-去除绿色背景后的敲击物主体和背景环境图为主题、画风、材质和配色参考,但参考图只用来约束圆形底色和中央左箭头的颜色搭配,不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案。按钮必须始终是标准圆形,整体像单个圆形图标,按钮主体在画布中的视觉尺寸比当前模板再放大约 50%,圆心居中,圆形外沿加一圈和主题色搭配的干净外描边,让它更像一个按钮,但仍然只保留一个清晰、简洁、居中的向左返回箭头,不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具。尺寸1:1,输出绿色背景主体图(纯绿色绿幕),背景必须是单一纯绿色 #00FF00 且平整无纹理、无渐变、无阴影。按钮主体边缘干净,后续由服务端扣除绿色背景;按钮底色不要使用与绿幕接近的纯绿色,若主题天然包含绿色,请仅在圆形底色上使用偏深、偏黄或偏蓝的主题绿色,并用更高对比的箭头颜色区分。\n主题为:{}", clean_string(prompt, DEFAULT_HIT_OBJECT_PROMPT) ) } @@ -866,6 +830,39 @@ fn downloaded_wooden_fish_reference_image( } } +fn prepare_wooden_fish_hit_object_image_for_persist( + image: DownloadedOpenAiImage, +) -> Result { + prepare_wooden_fish_green_screen_image_for_persist(image, "敲木鱼敲击物图案") +} + +fn prepare_wooden_fish_green_screen_image_for_persist( + image: DownloadedOpenAiImage, + failure_label: &str, +) -> Result { + let source = image::load_from_memory(image.bytes.as_slice()).map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": WOODEN_FISH_CREATION_PROVIDER, + "message": format!("{failure_label}解码失败:{error}"), + })) + })?; + let mut encoded = std::io::Cursor::new(Vec::new()); + crate::generated_asset_sheets::apply_generated_asset_sheet_green_screen_alpha(source) + .write_to(&mut encoded, image::ImageFormat::Png) + .map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": WOODEN_FISH_CREATION_PROVIDER, + "message": format!("{failure_label}绿幕去背失败:{error}"), + })) + })?; + + Ok(DownloadedOpenAiImage { + bytes: encoded.into_inner(), + mime_type: "image/png".to_string(), + extension: "png".to_string(), + }) +} + struct WoodenFishImageSlotPersistSpec { slot: &'static str, asset_kind: &'static str, @@ -1194,23 +1191,95 @@ mod tests { use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD}; #[test] - fn wooden_fish_hit_object_prompt_uses_hidden_image2_flow() { + fn wooden_fish_hit_object_prompt_uses_hidden_green_screen_flow() { let prompt = build_wooden_fish_hit_object_prompt("赛博莲花木鱼"); - assert_eq!( - prompt, - "生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。\n新主题为:赛博莲花木鱼" - ); + assert!(prompt.contains( + "生成敲木鱼新样式,要求结构,画风与参考图保持高度一致,新样式颜色搭配使用新主题对应的颜色。" + )); + assert!(prompt.contains("尺寸1:1")); + assert!(prompt.contains("绿色背景主体图")); + assert!(prompt.contains("纯绿色绿幕")); + assert!(prompt.contains("#00FF00")); + assert!(prompt.contains("不要直接输出透明底")); + assert!(prompt.contains("主体本身不要使用与绿幕接近的纯绿色")); + assert!(prompt.contains("新主题为:赛博莲花木鱼")); } #[test] fn wooden_fish_background_prompt_uses_hidden_image2_flow() { - let prompt = build_wooden_fish_background_prompt("赛博莲花木鱼"); + let prompt = build_wooden_fish_background_prompt("苹果"); + assert!(prompt.contains( + "生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。" + )); + assert!(prompt.contains("尺寸竖屏9:16")); + assert!(prompt.contains("抠图完成后的透明图")); + assert!(prompt.contains("不继承任何绿色底色")); + assert!(prompt.contains("完整不透明的背景环境图")); + assert!(prompt.contains("中央主体预留区")); + assert!(prompt.contains("禁止出现主题主体")); + assert!(prompt.contains("苹果")); + assert!(prompt.contains("不得把主题物品画在画面中央")); + assert!(prompt.contains("主题为:苹果")); + } + + #[test] + fn wooden_fish_back_button_prompt_forces_plain_circular_icon() { + let prompt = build_wooden_fish_back_button_prompt("玉米"); + + assert!(prompt.contains("参考图只用来约束圆形底色和中央左箭头的颜色搭配")); + assert!(prompt.contains("按钮必须始终是标准圆形")); + assert!(prompt.contains("按钮主体在画布中的视觉尺寸比当前模板再放大约 50%")); + assert!(prompt.contains("圆形外沿加一圈和主题色搭配的干净外描边")); + assert!(prompt.contains("只保留一个清晰、简洁、居中的向左返回箭头")); + assert!(prompt.contains("不要继承复杂造型、花纹、浮雕边、异形外框或装饰图案")); + assert!(prompt.contains("不要出现文字、数字、水印、按钮外标签、额外 UI 面板、木槌或敲击道具")); + assert!(prompt.contains("按钮底色不要使用与绿幕接近的纯绿色")); + assert!(prompt.contains("主题为:玉米")); + } + + #[test] + fn wooden_fish_hit_object_prepare_removes_green_screen_background() { + let width = 12; + let height = 12; + let mut image = image::RgbaImage::from_pixel(width, height, image::Rgba([0, 255, 0, 255])); + for y in 4..8 { + for x in 4..8 { + image.put_pixel(x, y, image::Rgba([190, 70, 42, 255])); + } + } + image.put_pixel(6, 6, image::Rgba([18, 14, 12, 255])); + let mut encoded = std::io::Cursor::new(Vec::new()); + image::DynamicImage::ImageRgba8(image) + .write_to(&mut encoded, image::ImageFormat::Png) + .expect("test image should encode"); + + let original_bytes = encoded.into_inner(); + let processed = prepare_wooden_fish_hit_object_image_for_persist(DownloadedOpenAiImage { + bytes: original_bytes.clone(), + mime_type: "image/png".to_string(), + extension: "png".to_string(), + }); + let processed = processed.expect("processed image should succeed"); + let decoded = image::load_from_memory(processed.bytes.as_slice()) + .expect("processed image should decode") + .to_rgba8(); + + assert_eq!(processed.mime_type, "image/png"); + assert_eq!(processed.extension, "png"); assert_eq!( - prompt, - "生成敲木鱼背景,要求主题,画风与参考图保持高度一致,背景元素和颜色搭配与主题对应,木鱼预设在屏幕中央位置,木鱼主体周围元素保持干净,背景氛围围绕外围设计,背景环境图中不包含新木鱼物品,背景氛围中不增加木槌互动物品。\n主题为:赛博莲花木鱼" + decoded.get_pixel(0, 0).0[3], + 0, + "绿幕背景必须在入库前去除" ); + assert_eq!(decoded.get_pixel(4, 4).0[3], 255); + assert_eq!( + decoded.get_pixel(6, 6).0[3], + 255, + "敲击物内部深色细节不能被当成背景抠除" + ); + assert_ne!(processed.bytes, original_bytes); } #[test] @@ -1273,37 +1342,41 @@ mod tests { } #[test] - fn wooden_fish_audio_asset_maps_from_generated_sound_effect() { - let asset = shared_contracts::creation_audio::CreationAudioAsset { - task_id: "task-hit-sound-1".to_string(), - provider: "vector-engine-vidu".to_string(), - asset_object_id: Some("assetobj-hit-sound-1".to_string()), - asset_kind: Some(WOODEN_FISH_HIT_SOUND_ASSET_KIND.to_string()), - audio_src: "/generated-wooden-fish-assets/wooden-fish-profile-1/hit-sound.mp3" - .to_string(), - prompt: Some("清脆木鱼声".to_string()), - title: None, - updated_at: None, + fn wooden_fish_default_hit_sound_asset_uses_bundled_mp3() { + let asset = default_wooden_fish_hit_sound_asset(); + + assert_eq!(asset.asset_id, "wooden-fish-default-hit-sound"); + assert_eq!(asset.audio_src, "/wooden-fish/default-hit-sound.mp3"); + assert_eq!( + asset.audio_object_key, + "public/wooden-fish/default-hit-sound.mp3" + ); + assert_eq!(asset.asset_object_id, "wooden-fish-default-hit-sound"); + assert_eq!(asset.source, "bundled-default"); + assert_eq!(asset.prompt.as_deref(), Some("默认木鱼音")); + } + + #[test] + fn wooden_fish_draft_uses_default_hit_sound_asset_and_ignores_prompt() { + let payload = WoodenFishWorkspaceCreateRequest { + template_id: WOODEN_FISH_TEMPLATE_ID.to_string(), + work_title: "今日敲木鱼".to_string(), + work_description: String::new(), + theme_tags: vec!["敲木鱼".to_string()], + hit_object_prompt: "金色木鱼".to_string(), + hit_object_reference_image_src: None, + hit_sound_prompt: Some("清脆木鱼声".to_string()), + hit_sound_asset: None, + floating_words: vec![], }; - let mapped = map_generated_creation_audio_to_wooden_fish_asset( - "wooden-fish-profile-1", - "清脆木鱼声", - asset, - WOODEN_FISH_HIT_SOUND_DURATION_SECONDS, - ) - .expect("generated sound effect should map to wooden fish audio asset"); + let draft = build_wooden_fish_draft(&payload); - assert_eq!( - mapped.asset_id, - "wooden-fish-profile-1-hit-sound-task-hit-sound-1" - ); - assert_eq!( - mapped.audio_object_key, - "generated-wooden-fish-assets/wooden-fish-profile-1/hit-sound.mp3" - ); - assert_eq!(mapped.asset_object_id, "assetobj-hit-sound-1"); - assert_eq!(mapped.source, "generated"); - assert_eq!(mapped.duration_ms, Some(3_000)); + assert!(draft.hit_sound_prompt.is_none()); + let asset = draft + .hit_sound_asset + .expect("default hit sound asset should be attached"); + assert_eq!(asset.audio_src, "/wooden-fish/default-hit-sound.mp3"); + assert_eq!(asset.prompt.as_deref(), Some("默认木鱼音")); } } diff --git a/server-rs/crates/module-runtime/src/application.rs b/server-rs/crates/module-runtime/src/application.rs index dec7f729..2d997da6 100644 --- a/server-rs/crates/module-runtime/src/application.rs +++ b/server-rs/crates/module-runtime/src/application.rs @@ -10,8 +10,8 @@ use crate::domain::*; use crate::errors::RuntimeProfileFieldError; use crate::format_utc_micros; use shared_contracts::creation_entry_config::{ - CreationEntryConfigResponse, CreationEntryStartCardResponse, CreationEntryTypeModalResponse, - CreationEntryTypeResponse, + CreationEntryConfigResponse, CreationEntryEventBannerResponse, CreationEntryStartCardResponse, + CreationEntryTypeModalResponse, CreationEntryTypeResponse, }; pub fn build_creation_entry_config_response( @@ -28,6 +28,14 @@ pub fn build_creation_entry_config_response( title: snapshot.type_modal.title, description: snapshot.type_modal.description, }, + event_banner: CreationEntryEventBannerResponse { + title: snapshot.event_banner.title, + description: snapshot.event_banner.description, + cover_image_src: snapshot.event_banner.cover_image_src, + prize_pool_mud_points: snapshot.event_banner.prize_pool_mud_points, + starts_at_text: snapshot.event_banner.starts_at_text, + ends_at_text: snapshot.event_banner.ends_at_text, + }, creation_types: snapshot .creation_types .into_iter() @@ -40,6 +48,9 @@ pub fn build_creation_entry_config_response( visible: item.visible, open: item.open, sort_order: item.sort_order, + category_id: item.category_id, + category_label: item.category_label, + category_sort_order: item.category_sort_order, updated_at_micros: item.updated_at_micros, }) .collect(), @@ -59,6 +70,9 @@ pub fn default_creation_entry_type_snapshots( true, true, 10, + "recent", + "最近创作", + 10, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -70,6 +84,9 @@ pub fn default_creation_entry_type_snapshots( false, true, 20, + "recommended", + "热门推荐", + 20, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -81,6 +98,9 @@ pub fn default_creation_entry_type_snapshots( true, true, 30, + "recent", + "最近创作", + 10, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -92,6 +112,9 @@ pub fn default_creation_entry_type_snapshots( true, true, 40, + "recent", + "最近创作", + 10, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -103,6 +126,9 @@ pub fn default_creation_entry_type_snapshots( true, true, 45, + "recommended", + "热门推荐", + 20, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -114,6 +140,9 @@ pub fn default_creation_entry_type_snapshots( true, true, 47, + "festival", + "节日主题", + 30, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -125,6 +154,9 @@ pub fn default_creation_entry_type_snapshots( false, true, 50, + "material", + "材质工艺", + 60, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -136,6 +168,9 @@ pub fn default_creation_entry_type_snapshots( true, false, 60, + "scene", + "生活场景", + 50, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -147,6 +182,9 @@ pub fn default_creation_entry_type_snapshots( true, false, 70, + "character", + "角色创作", + 40, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -158,6 +196,9 @@ pub fn default_creation_entry_type_snapshots( false, true, 80, + "recommended", + "热门推荐", + 20, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -169,6 +210,9 @@ pub fn default_creation_entry_type_snapshots( true, true, 85, + "recommended", + "热门推荐", + 20, updated_at_micros, ), build_default_creation_entry_type_snapshot( @@ -180,6 +224,9 @@ pub fn default_creation_entry_type_snapshots( true, true, 90, + "character", + "角色创作", + 40, updated_at_micros, ), ] @@ -195,6 +242,9 @@ fn build_default_creation_entry_type_snapshot( visible: bool, open: bool, sort_order: i32, + category_id: &str, + category_label: &str, + category_sort_order: i32, updated_at_micros: i64, ) -> CreationEntryTypeSnapshot { CreationEntryTypeSnapshot { @@ -206,6 +256,9 @@ fn build_default_creation_entry_type_snapshot( visible, open, sort_order, + category_id: category_id.to_string(), + category_label: category_label.to_string(), + category_sort_order, updated_at_micros, } } diff --git a/server-rs/crates/module-runtime/src/domain.rs b/server-rs/crates/module-runtime/src/domain.rs index 4d1da0bc..bc7dff76 100644 --- a/server-rs/crates/module-runtime/src/domain.rs +++ b/server-rs/crates/module-runtime/src/domain.rs @@ -50,6 +50,15 @@ pub const DEFAULT_CREATION_ENTRY_START_IDLE_BADGE: &str = "模板 Tab"; pub const DEFAULT_CREATION_ENTRY_START_BUSY_BADGE: &str = "正在开启"; pub const DEFAULT_CREATION_ENTRY_MODAL_TITLE: &str = "选择创作类型"; pub const DEFAULT_CREATION_ENTRY_MODAL_DESCRIPTION: &str = "先选玩法类型,再进入对应创作工作台。"; +pub const DEFAULT_CREATION_ENTRY_CATEGORY_ID: &str = "recent"; +pub const DEFAULT_CREATION_ENTRY_CATEGORY_LABEL: &str = "最近创作"; +pub const DEFAULT_CREATION_ENTRY_EVENT_TITLE: &str = "主题创作赛"; +pub const DEFAULT_CREATION_ENTRY_EVENT_DESCRIPTION: &str = "用温暖的色彩,捏出秋天的故事。"; +pub const DEFAULT_CREATION_ENTRY_EVENT_COVER_IMAGE_SRC: &str = + "/branding/taonier-logo-spiral-reference-concepts/taonier-spiral-bouncy-clay.png"; +pub const DEFAULT_CREATION_ENTRY_EVENT_PRIZE_POOL_MUD_POINTS: u64 = 58_000; +pub const DEFAULT_CREATION_ENTRY_EVENT_STARTS_AT_TEXT: &str = "2024.10.20 10:00"; +pub const DEFAULT_CREATION_ENTRY_EVENT_ENDS_AT_TEXT: &str = "2024.11.20 23:59"; #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -67,6 +76,17 @@ pub struct CreationEntryTypeModalSnapshot { pub description: String, } +#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CreationEntryEventBannerSnapshot { + pub title: String, + pub description: String, + pub cover_image_src: String, + pub prize_pool_mud_points: u64, + pub starts_at_text: String, + pub ends_at_text: String, +} + #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CreationEntryTypeSnapshot { @@ -78,6 +98,9 @@ pub struct CreationEntryTypeSnapshot { pub visible: bool, pub open: bool, pub sort_order: i32, + pub category_id: String, + pub category_label: String, + pub category_sort_order: i32, pub updated_at_micros: i64, } @@ -87,6 +110,7 @@ pub struct CreationEntryConfigSnapshot { pub config_id: String, pub start_card: CreationEntryStartCardSnapshot, pub type_modal: CreationEntryTypeModalSnapshot, + pub event_banner: CreationEntryEventBannerSnapshot, pub creation_types: Vec, pub updated_at_micros: i64, } @@ -102,6 +126,9 @@ pub struct CreationEntryTypeAdminUpsertInput { pub visible: bool, pub open: bool, pub sort_order: i32, + pub category_id: String, + pub category_label: String, + pub category_sort_order: i32, } #[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))] diff --git a/server-rs/crates/module-runtime/src/lib.rs b/server-rs/crates/module-runtime/src/lib.rs index 5bbcd1b8..596ef888 100644 --- a/server-rs/crates/module-runtime/src/lib.rs +++ b/server-rs/crates/module-runtime/src/lib.rs @@ -230,6 +230,9 @@ mod tests { assert!(baby_object_match.open); assert_eq!(baby_object_match.badge, "可创建"); assert_eq!(baby_object_match.sort_order, 90); + assert_eq!(baby_object_match.category_id, "character"); + assert_eq!(baby_object_match.category_label, "角色创作"); + assert_eq!(baby_object_match.category_sort_order, 40); assert_eq!( baby_object_match.image_src, "/child-motion-demo/picture-book-grass-stage.png" @@ -250,6 +253,8 @@ mod tests { assert!(rpg.open); assert_eq!(rpg.badge, "可创建"); assert_eq!(rpg.sort_order, 10); + assert_eq!(rpg.category_id, "recent"); + assert_eq!(rpg.category_label, "最近创作"); assert_eq!(rpg.image_src, "/creation-type-references/rpg.webp"); } diff --git a/server-rs/crates/shared-contracts/src/admin.rs b/server-rs/crates/shared-contracts/src/admin.rs index 8d0cb19e..b776f28c 100644 --- a/server-rs/crates/shared-contracts/src/admin.rs +++ b/server-rs/crates/shared-contracts/src/admin.rs @@ -30,6 +30,9 @@ pub struct AdminCreationEntryTypeConfigPayload { pub visible: bool, pub open: bool, pub sort_order: i32, + pub category_id: String, + pub category_label: String, + pub category_sort_order: i32, pub updated_at_micros: i64, } @@ -45,6 +48,9 @@ pub struct AdminUpsertCreationEntryTypeConfigRequest { pub visible: bool, pub open: bool, pub sort_order: i32, + pub category_id: String, + pub category_label: String, + pub category_sort_order: i32, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] diff --git a/server-rs/crates/shared-contracts/src/creation_entry_config.rs b/server-rs/crates/shared-contracts/src/creation_entry_config.rs index 1cebef29..c6664cbb 100644 --- a/server-rs/crates/shared-contracts/src/creation_entry_config.rs +++ b/server-rs/crates/shared-contracts/src/creation_entry_config.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; pub struct CreationEntryConfigResponse { pub start_card: CreationEntryStartCardResponse, pub type_modal: CreationEntryTypeModalResponse, + pub event_banner: CreationEntryEventBannerResponse, pub creation_types: Vec, } @@ -24,6 +25,17 @@ pub struct CreationEntryTypeModalResponse { pub description: String, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CreationEntryEventBannerResponse { + pub title: String, + pub description: String, + pub cover_image_src: String, + pub prize_pool_mud_points: u64, + pub starts_at_text: String, + pub ends_at_text: String, +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct CreationEntryTypeResponse { @@ -35,5 +47,8 @@ pub struct CreationEntryTypeResponse { pub visible: bool, pub open: bool, pub sort_order: i32, + pub category_id: String, + pub category_label: String, + pub category_sort_order: i32, pub updated_at_micros: i64, } diff --git a/server-rs/crates/shared-contracts/src/wooden_fish.rs b/server-rs/crates/shared-contracts/src/wooden_fish.rs index 18cc81d5..18788a9e 100644 --- a/server-rs/crates/shared-contracts/src/wooden_fish.rs +++ b/server-rs/crates/shared-contracts/src/wooden_fish.rs @@ -93,6 +93,9 @@ pub struct WoodenFishActionRequest { #[serde(skip_deserializing)] pub background_asset: Option, #[serde(default)] + #[serde(skip_deserializing)] + pub back_button_asset: Option, + #[serde(default)] pub hit_sound_prompt: Option, #[serde(default)] pub hit_sound_asset: Option, @@ -128,6 +131,8 @@ pub struct WoodenFishDraftResponse { #[serde(default)] pub background_asset: Option, #[serde(default)] + pub back_button_asset: Option, + #[serde(default)] pub hit_sound_asset: Option, #[serde(default)] pub cover_image_src: Option, @@ -192,6 +197,8 @@ pub struct WoodenFishWorkProfileResponse { pub hit_object_asset: WoodenFishImageAsset, #[serde(default)] pub background_asset: Option, + #[serde(default)] + pub back_button_asset: Option, pub hit_sound_asset: WoodenFishAudioAsset, pub floating_words: Vec, } @@ -384,6 +391,18 @@ mod tests { width: 1024, height: 1536, }), + back_button_asset: Some(WoodenFishImageAsset { + asset_id: "back-button-1".to_string(), + image_src: "/generated-wooden-fish-assets/profile/back-button/image.png" + .to_string(), + image_object_key: "generated-wooden-fish-assets/profile/back-button/image.png" + .to_string(), + asset_object_id: "back-button-object-1".to_string(), + generation_provider: "image2".to_string(), + prompt: "赛博莲花返回按钮".to_string(), + width: 1024, + height: 1024, + }), hit_sound_prompt: Some("短促木鱼声".to_string()), hit_sound_asset: Some(WoodenFishAudioAsset { asset_id: "sound-1".to_string(), @@ -406,6 +425,7 @@ mod tests { json!("generated-wooden-fish-assets/profile/hit-object/image.png") ); assert_eq!(payload["backgroundAsset"]["height"], json!(1536)); + assert_eq!(payload["backButtonAsset"]["width"], json!(1024)); assert_eq!(payload["hitSoundAsset"]["source"], json!("upload")); assert_eq!(payload["hitSoundAsset"]["durationMs"], json!(800)); } @@ -454,6 +474,16 @@ mod tests { prompt: Some("清脆木鱼".to_string()), duration_ms: Some(600), }; + let back_button = WoodenFishImageAsset { + asset_id: "back-button-1".to_string(), + image_src: "/generated/wooden-fish-back-button.png".to_string(), + image_object_key: "generated/wooden-fish-back-button.png".to_string(), + asset_object_id: "back-button-object-1".to_string(), + generation_provider: "image2".to_string(), + prompt: "主题返回按钮".to_string(), + width: 1024, + height: 1024, + }; let profile = WoodenFishWorkProfileResponse { summary: WoodenFishWorkSummaryResponse { runtime_kind: "wooden-fish".to_string(), @@ -485,12 +515,14 @@ mod tests { floating_words: vec!["功德".to_string()], hit_object_asset: Some(image.clone()), background_asset: None, + back_button_asset: Some(back_button.clone()), hit_sound_asset: Some(audio.clone()), cover_image_src: Some(image.image_src.clone()), generation_status: WoodenFishGenerationStatus::Ready, }, hit_object_asset: image, background_asset: None, + back_button_asset: Some(back_button), hit_sound_asset: audio, floating_words: vec!["功德".to_string()], }; @@ -503,5 +535,9 @@ mod tests { json!("image2") ); assert_eq!(payload["hitSoundAsset"]["source"], json!("generated")); + assert_eq!( + payload["backButtonAsset"]["imageSrc"], + json!("/generated/wooden-fish-back-button.png") + ); } } diff --git a/server-rs/crates/spacetime-client/src/mapper/big_fish.rs b/server-rs/crates/spacetime-client/src/mapper/big_fish.rs index 8fb549c2..688bd873 100644 --- a/server-rs/crates/spacetime-client/src/mapper/big_fish.rs +++ b/server-rs/crates/spacetime-client/src/mapper/big_fish.rs @@ -450,6 +450,10 @@ mod tests { cover_image_src: None, ui_background_image_src: None, ui_background_image_object_key: None, + level_background_image_src: None, + level_background_image_object_key: None, + ui_spritesheet_image_src: None, + ui_spritesheet_image_object_key: None, background_music: None, board: PuzzleBoardSnapshot { rows: 3, diff --git a/server-rs/crates/spacetime-client/src/mapper/runtime.rs b/server-rs/crates/spacetime-client/src/mapper/runtime.rs index 1a96e2c2..af558240 100644 --- a/server-rs/crates/spacetime-client/src/mapper/runtime.rs +++ b/server-rs/crates/spacetime-client/src/mapper/runtime.rs @@ -11,6 +11,9 @@ impl From for CreationEntryTy visible: input.visible, open: input.open, sort_order: input.sort_order, + category_id: input.category_id, + category_label: input.category_label, + category_sort_order: input.category_sort_order, } } } @@ -151,6 +154,29 @@ pub(crate) fn build_creation_entry_config_record_from_rows( title: header.modal_title, description: header.modal_description, }, + event_banner: module_runtime::CreationEntryEventBannerSnapshot { + title: creation_entry_text_or_default( + header.event_title, + module_runtime::DEFAULT_CREATION_ENTRY_EVENT_TITLE, + ), + description: creation_entry_text_or_default( + header.event_description, + module_runtime::DEFAULT_CREATION_ENTRY_EVENT_DESCRIPTION, + ), + cover_image_src: creation_entry_text_or_default( + header.event_cover_image_src, + module_runtime::DEFAULT_CREATION_ENTRY_EVENT_COVER_IMAGE_SRC, + ), + prize_pool_mud_points: header.event_prize_pool_mud_points, + starts_at_text: creation_entry_text_or_default( + header.event_starts_at_text, + module_runtime::DEFAULT_CREATION_ENTRY_EVENT_STARTS_AT_TEXT, + ), + ends_at_text: creation_entry_text_or_default( + header.event_ends_at_text, + module_runtime::DEFAULT_CREATION_ENTRY_EVENT_ENDS_AT_TEXT, + ), + }, creation_types: creation_types .into_iter() .map(|item| module_runtime::CreationEntryTypeSnapshot { @@ -162,6 +188,15 @@ pub(crate) fn build_creation_entry_config_record_from_rows( visible: item.visible, open: item.open, sort_order: item.sort_order, + category_id: creation_entry_text_or_default( + item.category_id, + module_runtime::DEFAULT_CREATION_ENTRY_CATEGORY_ID, + ), + category_label: creation_entry_text_or_default( + item.category_label, + module_runtime::DEFAULT_CREATION_ENTRY_CATEGORY_LABEL, + ), + category_sort_order: item.category_sort_order, updated_at_micros: item.updated_at.to_micros_since_unix_epoch(), }) .collect(), @@ -185,6 +220,14 @@ fn map_creation_entry_config_snapshot( title: snapshot.type_modal.title, description: snapshot.type_modal.description, }, + event_banner: module_runtime::CreationEntryEventBannerSnapshot { + title: snapshot.event_banner.title, + description: snapshot.event_banner.description, + cover_image_src: snapshot.event_banner.cover_image_src, + prize_pool_mud_points: snapshot.event_banner.prize_pool_mud_points, + starts_at_text: snapshot.event_banner.starts_at_text, + ends_at_text: snapshot.event_banner.ends_at_text, + }, creation_types: snapshot .creation_types .into_iter() @@ -197,6 +240,9 @@ fn map_creation_entry_config_snapshot( visible: item.visible, open: item.open, sort_order: item.sort_order, + category_id: item.category_id, + category_label: item.category_label, + category_sort_order: item.category_sort_order, updated_at_micros: item.updated_at_micros, }) .collect(), @@ -204,6 +250,13 @@ fn map_creation_entry_config_snapshot( } } +fn creation_entry_text_or_default(value: Option, default_value: &str) -> String { + value + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| default_value.to_string()) +} + pub(crate) fn map_runtime_setting_procedure_result( result: RuntimeSettingProcedureResult, ) -> Result { diff --git a/server-rs/crates/spacetime-client/src/mapper/wooden_fish.rs b/server-rs/crates/spacetime-client/src/mapper/wooden_fish.rs index b4edf32f..d4ec1031 100644 --- a/server-rs/crates/spacetime-client/src/mapper/wooden_fish.rs +++ b/server-rs/crates/spacetime-client/src/mapper/wooden_fish.rs @@ -113,6 +113,7 @@ fn map_wooden_fish_work_snapshot( floating_words: snapshot.floating_words.clone(), hit_object_asset: snapshot.hit_object_asset.clone().map(map_image_asset), background_asset: snapshot.background_asset.clone().map(map_image_asset), + back_button_asset: snapshot.back_button_asset.clone().map(map_image_asset), hit_sound_asset: snapshot.hit_sound_asset.clone().map(map_audio_asset), cover_image_src: empty_string_to_none(snapshot.cover_image_src.clone()), generation_status: parse_generation_status(&snapshot.generation_status), @@ -147,6 +148,7 @@ fn map_wooden_fish_work_snapshot( draft, hit_object_asset, background_asset: snapshot.background_asset.map(map_image_asset), + back_button_asset: snapshot.back_button_asset.map(map_image_asset), hit_sound_asset, floating_words: snapshot.floating_words, }) @@ -166,6 +168,7 @@ fn map_wooden_fish_draft_snapshot(snapshot: WoodenFishDraftSnapshot) -> WoodenFi floating_words: snapshot.floating_words, hit_object_asset: snapshot.hit_object_asset.map(map_image_asset), background_asset: snapshot.background_asset.map(map_image_asset), + back_button_asset: snapshot.back_button_asset.map(map_image_asset), hit_sound_asset: snapshot.hit_sound_asset.map(map_audio_asset), cover_image_src: snapshot.cover_image_src, generation_status: parse_generation_status(&snapshot.generation_status), diff --git a/server-rs/crates/spacetime-client/src/module_bindings.rs b/server-rs/crates/spacetime-client/src/module_bindings.rs index 5f9f3392..3aa8dc89 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings.rs @@ -234,6 +234,7 @@ pub mod creation_entry_config_procedure_result_type; pub mod creation_entry_config_snapshot_type; pub mod creation_entry_config_table; pub mod creation_entry_config_type; +pub mod creation_entry_event_banner_snapshot_type; pub mod creation_entry_start_card_snapshot_type; pub mod creation_entry_type_admin_upsert_input_type; pub mod creation_entry_type_config_table; @@ -1262,6 +1263,7 @@ pub use creation_entry_config_procedure_result_type::CreationEntryConfigProcedur pub use creation_entry_config_snapshot_type::CreationEntryConfigSnapshot; pub use creation_entry_config_table::*; pub use creation_entry_config_type::CreationEntryConfig; +pub use creation_entry_event_banner_snapshot_type::CreationEntryEventBannerSnapshot; pub use creation_entry_start_card_snapshot_type::CreationEntryStartCardSnapshot; pub use creation_entry_type_admin_upsert_input_type::CreationEntryTypeAdminUpsertInput; pub use creation_entry_type_config_table::*; @@ -3285,10 +3287,6 @@ impl __sdk::DbUpdate for DbUpdate { &self.visual_novel_work_profile, ) .with_updates_by_pk(|row| &row.profile_id); - diff.bark_battle_gallery_view = cache.apply_diff_to_table::( - "bark_battle_gallery_view", - &self.bark_battle_gallery_view, - ); diff.wooden_fish_agent_session = cache .apply_diff_to_table::( "wooden_fish_agent_session", @@ -3310,6 +3308,10 @@ impl __sdk::DbUpdate for DbUpdate { &self.wooden_fish_work_profile, ) .with_updates_by_pk(|row| &row.profile_id); + diff.bark_battle_gallery_view = cache.apply_diff_to_table::( + "bark_battle_gallery_view", + &self.bark_battle_gallery_view, + ); diff.big_fish_gallery_view = cache.apply_diff_to_table::( "big_fish_gallery_view", &self.big_fish_gallery_view, diff --git a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_snapshot_type.rs index 424caf89..0679a45a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_snapshot_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_snapshot_type.rs @@ -4,6 +4,7 @@ #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; +use super::creation_entry_event_banner_snapshot_type::CreationEntryEventBannerSnapshot; use super::creation_entry_start_card_snapshot_type::CreationEntryStartCardSnapshot; use super::creation_entry_type_modal_snapshot_type::CreationEntryTypeModalSnapshot; use super::creation_entry_type_snapshot_type::CreationEntryTypeSnapshot; @@ -14,6 +15,7 @@ pub struct CreationEntryConfigSnapshot { pub config_id: String, pub start_card: CreationEntryStartCardSnapshot, pub type_modal: CreationEntryTypeModalSnapshot, + pub event_banner: CreationEntryEventBannerSnapshot, pub creation_types: Vec, pub updated_at_micros: i64, } diff --git a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_type.rs index e06f35f4..c3234d1d 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_config_type.rs @@ -15,6 +15,12 @@ pub struct CreationEntryConfig { pub modal_title: String, pub modal_description: String, pub updated_at: __sdk::Timestamp, + pub event_title: Option, + pub event_description: Option, + pub event_cover_image_src: Option, + pub event_prize_pool_mud_points: u64, + pub event_starts_at_text: Option, + pub event_ends_at_text: Option, } impl __sdk::InModule for CreationEntryConfig { @@ -33,6 +39,12 @@ pub struct CreationEntryConfigCols { pub modal_title: __sdk::__query_builder::Col, pub modal_description: __sdk::__query_builder::Col, pub updated_at: __sdk::__query_builder::Col, + pub event_title: __sdk::__query_builder::Col>, + pub event_description: __sdk::__query_builder::Col>, + pub event_cover_image_src: __sdk::__query_builder::Col>, + pub event_prize_pool_mud_points: __sdk::__query_builder::Col, + pub event_starts_at_text: __sdk::__query_builder::Col>, + pub event_ends_at_text: __sdk::__query_builder::Col>, } impl __sdk::__query_builder::HasCols for CreationEntryConfig { @@ -47,6 +59,21 @@ impl __sdk::__query_builder::HasCols for CreationEntryConfig { modal_title: __sdk::__query_builder::Col::new(table_name, "modal_title"), modal_description: __sdk::__query_builder::Col::new(table_name, "modal_description"), updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"), + event_title: __sdk::__query_builder::Col::new(table_name, "event_title"), + event_description: __sdk::__query_builder::Col::new(table_name, "event_description"), + event_cover_image_src: __sdk::__query_builder::Col::new( + table_name, + "event_cover_image_src", + ), + event_prize_pool_mud_points: __sdk::__query_builder::Col::new( + table_name, + "event_prize_pool_mud_points", + ), + event_starts_at_text: __sdk::__query_builder::Col::new( + table_name, + "event_starts_at_text", + ), + event_ends_at_text: __sdk::__query_builder::Col::new(table_name, "event_ends_at_text"), } } } diff --git a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_event_banner_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_event_banner_snapshot_type.rs new file mode 100644 index 00000000..9d60ef30 --- /dev/null +++ b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_event_banner_snapshot_type.rs @@ -0,0 +1,20 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct CreationEntryEventBannerSnapshot { + pub title: String, + pub description: String, + pub cover_image_src: String, + pub prize_pool_mud_points: u64, + pub starts_at_text: String, + pub ends_at_text: String, +} + +impl __sdk::InModule for CreationEntryEventBannerSnapshot { + type Module = super::RemoteModule; +} diff --git a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_admin_upsert_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_admin_upsert_input_type.rs index b2e7eccc..ae561402 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_admin_upsert_input_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_admin_upsert_input_type.rs @@ -15,6 +15,9 @@ pub struct CreationEntryTypeAdminUpsertInput { pub visible: bool, pub open: bool, pub sort_order: i32, + pub category_id: String, + pub category_label: String, + pub category_sort_order: i32, } impl __sdk::InModule for CreationEntryTypeAdminUpsertInput { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_config_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_config_type.rs index c6290d03..6fa8c5ed 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_config_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_config_type.rs @@ -16,6 +16,9 @@ pub struct CreationEntryTypeConfig { pub open: bool, pub sort_order: i32, pub updated_at: __sdk::Timestamp, + pub category_id: Option, + pub category_label: Option, + pub category_sort_order: i32, } impl __sdk::InModule for CreationEntryTypeConfig { @@ -35,6 +38,9 @@ pub struct CreationEntryTypeConfigCols { pub open: __sdk::__query_builder::Col, pub sort_order: __sdk::__query_builder::Col, pub updated_at: __sdk::__query_builder::Col, + pub category_id: __sdk::__query_builder::Col>, + pub category_label: __sdk::__query_builder::Col>, + pub category_sort_order: __sdk::__query_builder::Col, } impl __sdk::__query_builder::HasCols for CreationEntryTypeConfig { @@ -50,6 +56,12 @@ impl __sdk::__query_builder::HasCols for CreationEntryTypeConfig { open: __sdk::__query_builder::Col::new(table_name, "open"), sort_order: __sdk::__query_builder::Col::new(table_name, "sort_order"), updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"), + category_id: __sdk::__query_builder::Col::new(table_name, "category_id"), + category_label: __sdk::__query_builder::Col::new(table_name, "category_label"), + category_sort_order: __sdk::__query_builder::Col::new( + table_name, + "category_sort_order", + ), } } } diff --git a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_snapshot_type.rs index 814edd78..51d87596 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_snapshot_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/creation_entry_type_snapshot_type.rs @@ -15,6 +15,9 @@ pub struct CreationEntryTypeSnapshot { pub visible: bool, pub open: bool, pub sort_order: i32, + pub category_id: String, + pub category_label: String, + pub category_sort_order: i32, pub updated_at_micros: i64, } diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_compile_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_compile_input_type.rs index 402f40ab..2e1060b4 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_compile_input_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_compile_input_type.rs @@ -20,6 +20,7 @@ pub struct WoodenFishDraftCompileInput { pub hit_object_asset_json: Option, pub background_asset_json: Option, pub hit_sound_asset_json: Option, + pub back_button_asset_json: Option, pub floating_words_json: Option, pub cover_image_src: Option, pub generation_status: Option, diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_snapshot_type.rs index 17c4e2b9..e2beff33 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_snapshot_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_draft_snapshot_type.rs @@ -22,6 +22,7 @@ pub struct WoodenFishDraftSnapshot { pub floating_words: Vec, pub hit_object_asset: Option, pub background_asset: Option, + pub back_button_asset: Option, pub hit_sound_asset: Option, pub cover_image_src: Option, pub generation_status: String, diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_gallery_view_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_gallery_view_row_type.rs index ac17e2de..1139a878 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_gallery_view_row_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_gallery_view_row_type.rs @@ -24,6 +24,7 @@ pub struct WoodenFishGalleryViewRow { pub hit_sound_prompt: Option, pub hit_object_asset: Option, pub background_asset: Option, + pub back_button_asset: Option, pub hit_sound_asset: Option, pub floating_words: Vec, pub cover_image_src: String, @@ -60,6 +61,8 @@ pub struct WoodenFishGalleryViewRowCols { __sdk::__query_builder::Col>, pub background_asset: __sdk::__query_builder::Col>, + pub back_button_asset: + __sdk::__query_builder::Col>, pub hit_sound_asset: __sdk::__query_builder::Col>, pub floating_words: __sdk::__query_builder::Col>, @@ -96,6 +99,7 @@ impl __sdk::__query_builder::HasCols for WoodenFishGalleryViewRow { hit_sound_prompt: __sdk::__query_builder::Col::new(table_name, "hit_sound_prompt"), hit_object_asset: __sdk::__query_builder::Col::new(table_name, "hit_object_asset"), background_asset: __sdk::__query_builder::Col::new(table_name, "background_asset"), + back_button_asset: __sdk::__query_builder::Col::new(table_name, "back_button_asset"), hit_sound_asset: __sdk::__query_builder::Col::new(table_name, "hit_sound_asset"), floating_words: __sdk::__query_builder::Col::new(table_name, "floating_words"), cover_image_src: __sdk::__query_builder::Col::new(table_name, "cover_image_src"), diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_profile_row_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_profile_row_type.rs index c82a9c6c..456316a8 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_profile_row_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_profile_row_type.rs @@ -28,6 +28,7 @@ pub struct WoodenFishWorkProfileRow { pub updated_at: __sdk::Timestamp, pub published_at: Option<__sdk::Timestamp>, pub background_asset_json: Option, + pub back_button_asset_json: Option, } impl __sdk::InModule for WoodenFishWorkProfileRow { @@ -62,6 +63,8 @@ pub struct WoodenFishWorkProfileRowCols { __sdk::__query_builder::Col>, pub background_asset_json: __sdk::__query_builder::Col>, + pub back_button_asset_json: + __sdk::__query_builder::Col>, } impl __sdk::__query_builder::HasCols for WoodenFishWorkProfileRow { @@ -107,6 +110,10 @@ impl __sdk::__query_builder::HasCols for WoodenFishWorkProfileRow { table_name, "background_asset_json", ), + back_button_asset_json: __sdk::__query_builder::Col::new( + table_name, + "back_button_asset_json", + ), } } } diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_snapshot_type.rs index fdaf3116..3a15b12b 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_snapshot_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_snapshot_type.rs @@ -23,6 +23,7 @@ pub struct WoodenFishWorkSnapshot { pub hit_sound_prompt: Option, pub hit_object_asset: Option, pub background_asset: Option, + pub back_button_asset: Option, pub hit_sound_asset: Option, pub floating_words: Vec, pub cover_image_src: String, diff --git a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_update_input_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_update_input_type.rs index cd7c3547..ac4f2977 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_update_input_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/wooden_fish_work_update_input_type.rs @@ -18,6 +18,7 @@ pub struct WoodenFishWorkUpdateInput { pub hit_object_asset_json: Option, pub background_asset_json: Option, pub hit_sound_asset_json: Option, + pub back_button_asset_json: Option, pub floating_words_json: Option, pub cover_image_src: Option, pub generation_status: Option, diff --git a/server-rs/crates/spacetime-client/src/wooden_fish.rs b/server-rs/crates/spacetime-client/src/wooden_fish.rs index edcfd312..1aadc15f 100644 --- a/server-rs/crates/spacetime-client/src/wooden_fish.rs +++ b/server-rs/crates/spacetime-client/src/wooden_fish.rs @@ -15,7 +15,6 @@ use shared_kernel::build_prefixed_uuid_id; const WOODEN_FISH_TEMPLATE_ID: &str = "wooden-fish"; const WOODEN_FISH_TEMPLATE_NAME: &str = "敲木鱼"; const DEFAULT_HIT_OBJECT_PROMPT: &str = "默认敲击物图案,圆润木质质感,透明背景"; -const DEFAULT_HIT_SOUND_PROMPT: &str = "清脆短促的木鱼敲击声"; impl SpacetimeClient { pub async fn create_wooden_fish_session( @@ -532,21 +531,11 @@ fn merge_action_into_draft( if let Some(asset) = payload.background_asset.clone() { draft.background_asset = Some(asset); } - } - if matches!( - scope, - WoodenFishDraftMergeScope::CompileDraft - | WoodenFishDraftMergeScope::GenerateHitSound - | WoodenFishDraftMergeScope::ReplaceHitSound - ) { - if let Some(value) = payload - .hit_sound_prompt - .as_ref() - .filter(|value| !value.trim().is_empty()) - { - draft.hit_sound_prompt = Some(value.trim().to_string()); + if let Some(asset) = payload.back_button_asset.clone() { + draft.back_button_asset = Some(asset); } } + draft.hit_sound_prompt = None; if matches!(scope, WoodenFishDraftMergeScope::GenerateHitSound) { draft.hit_sound_asset = payload.hit_sound_asset.clone(); } else if matches!( @@ -577,6 +566,7 @@ fn merge_action_into_draft( { draft.hit_object_asset = None; draft.background_asset = None; + draft.back_button_asset = None; } if draft.floating_words.is_empty() { draft.floating_words = default_floating_words(); @@ -613,6 +603,9 @@ fn build_compile_input( let background_asset = draft.background_asset.clone().ok_or_else(|| { SpacetimeClientError::validation_failed("wooden fish background asset 缺少真实生成资产") })?; + let back_button_asset = draft.back_button_asset.clone().ok_or_else(|| { + SpacetimeClientError::validation_failed("wooden fish back button asset 缺少真实生成资产") + })?; Ok(WoodenFishDraftCompileInput { session_id: current.session_id.clone(), @@ -628,6 +621,7 @@ fn build_compile_input( hit_object_asset_json: Some(json_string(&hit_object_asset)?), background_asset_json: Some(json_string(&background_asset)?), hit_sound_asset_json: Some(json_string(&hit_sound_asset)?), + back_button_asset_json: Some(json_string(&back_button_asset)?), floating_words_json: Some(json_string(&draft.floating_words)?), cover_image_src: draft.cover_image_src.clone(), generation_status: Some("ready".to_string()), @@ -662,6 +656,7 @@ fn build_update_input( } else { None }, + back_button_asset_json: None, floating_words_json: Some(json_string(&draft.floating_words)?), cover_image_src: draft.cover_image_src.clone(), generation_status: None, @@ -716,10 +711,11 @@ fn default_draft() -> WoodenFishDraftResponse { theme_tags: vec!["休闲".to_string()], hit_object_prompt: DEFAULT_HIT_OBJECT_PROMPT.to_string(), hit_object_reference_image_src: None, - hit_sound_prompt: Some(DEFAULT_HIT_SOUND_PROMPT.to_string()), + hit_sound_prompt: None, floating_words: default_floating_words(), hit_object_asset: None, background_asset: None, + back_button_asset: None, hit_sound_asset: None, cover_image_src: None, generation_status: WoodenFishGenerationStatus::Draft, @@ -807,6 +803,7 @@ mod tests { let mut payload = action(WoodenFishActionType::CompileDraft); payload.hit_object_asset = Some(generated_hit_object_asset("generated-compile-object")); payload.background_asset = Some(generated_background_asset("generated-compile-background")); + payload.back_button_asset = Some(generated_back_button_asset("generated-compile-back")); payload.hit_sound_asset = Some(generated_hit_sound_asset("generated-compile-sound")); let (plan, draft) = @@ -840,6 +837,13 @@ mod tests { .unwrap_or("") .contains("generated-compile-background") ); + assert!( + input + .back_button_asset_json + .as_deref() + .unwrap_or("") + .contains("generated-compile-back") + ); assert_eq!(draft.generation_status, WoodenFishGenerationStatus::Ready); } @@ -849,6 +853,7 @@ mod tests { let mut payload = action(WoodenFishActionType::CompileDraft); payload.hit_object_asset = Some(generated_hit_object_asset("generated-compile-object")); payload.background_asset = Some(generated_background_asset("generated-compile-background")); + payload.back_button_asset = Some(generated_back_button_asset("generated-compile-back")); let error = match build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS) { @@ -869,6 +874,7 @@ mod tests { let mut payload = action(WoodenFishActionType::CompileDraft); payload.hit_object_asset = Some(generated_hit_object_asset("generated-compile-object")); payload.hit_sound_asset = Some(generated_hit_sound_asset("generated-compile-sound")); + payload.back_button_asset = Some(generated_back_button_asset("generated-compile-back")); let error = match build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS) { @@ -883,6 +889,27 @@ mod tests { ); } + #[test] + fn wooden_fish_compile_requires_real_back_button_asset_from_api_server() { + let session = session_with_draft(draft_without_assets()); + let mut payload = action(WoodenFishActionType::CompileDraft); + payload.hit_object_asset = Some(generated_hit_object_asset("generated-compile-object")); + payload.background_asset = Some(generated_background_asset("generated-compile-background")); + payload.hit_sound_asset = Some(generated_hit_sound_asset("generated-compile-sound")); + + let error = + match build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS) { + Ok(_) => panic!("compile-draft should not publish without back button asset"), + Err(error) => error, + }; + + assert!( + error + .to_string() + .contains("back button asset 缺少真实生成资产") + ); + } + #[test] fn wooden_fish_action_regenerate_hit_object_replaces_only_object_asset() { let session = session_with_draft(draft_with_assets()); @@ -890,6 +917,7 @@ mod tests { payload.hit_object_prompt = Some("新的敲击物".to_string()); payload.hit_object_asset = Some(generated_hit_object_asset("generated-object")); payload.background_asset = Some(generated_background_asset("generated-background")); + payload.back_button_asset = Some(generated_back_button_asset("generated-back")); let (plan, _draft) = build_wooden_fish_action_plan(&session, OWNER_USER_ID, &payload, NOW_MICROS) @@ -933,6 +961,13 @@ mod tests { .unwrap_or("") .contains("generated-background") ); + assert!( + input + .back_button_asset_json + .as_deref() + .unwrap_or("") + .contains("generated-back") + ); } #[test] @@ -967,6 +1002,13 @@ mod tests { ); } + #[test] + fn wooden_fish_default_draft_has_no_hit_sound_prompt() { + let draft = default_draft(); + + assert!(draft.hit_sound_prompt.is_none()); + } + fn action(action_type: WoodenFishActionType) -> WoodenFishActionRequest { WoodenFishActionRequest { action_type, @@ -978,6 +1020,7 @@ mod tests { hit_object_reference_image_src: None, hit_object_asset: None, background_asset: None, + back_button_asset: None, hit_sound_prompt: None, hit_sound_asset: None, floating_words: None, @@ -1032,6 +1075,21 @@ mod tests { } } + fn generated_back_button_asset(asset_id: &str) -> WoodenFishImageAsset { + WoodenFishImageAsset { + asset_id: asset_id.to_string(), + image_src: "/generated-wooden-fish-assets/real-profile/back-button/image.png" + .to_string(), + image_object_key: "generated-wooden-fish-assets/real-profile/back-button/image.png" + .to_string(), + asset_object_id: format!("{asset_id}-asset"), + generation_provider: "image2".to_string(), + prompt: "新的返回按钮".to_string(), + width: 1024, + height: 1024, + } + } + fn generated_hit_sound_asset(asset_id: &str) -> WoodenFishAudioAsset { WoodenFishAudioAsset { asset_id: asset_id.to_string(), @@ -1068,6 +1126,16 @@ mod tests { width: 1024, height: 1536, }), + back_button_asset: Some(WoodenFishImageAsset { + asset_id: "old-back".to_string(), + image_src: "/generated-wooden-fish-assets/old-back.png".to_string(), + image_object_key: "generated-wooden-fish-assets/old-back.png".to_string(), + asset_object_id: "old-back-asset".to_string(), + generation_provider: "image2".to_string(), + prompt: "旧返回按钮".to_string(), + width: 1024, + height: 1024, + }), hit_sound_asset: Some(WoodenFishAudioAsset { asset_id: "old-sound".to_string(), audio_src: "/generated-wooden-fish-assets/old-sound.mp3".to_string(), @@ -1093,10 +1161,11 @@ mod tests { theme_tags: vec!["旧标签".to_string()], hit_object_prompt: "旧敲击物".to_string(), hit_object_reference_image_src: None, - hit_sound_prompt: Some("旧音效".to_string()), + hit_sound_prompt: None, floating_words: default_floating_words(), hit_object_asset: None, background_asset: None, + back_button_asset: None, hit_sound_asset: None, cover_image_src: None, generation_status: WoodenFishGenerationStatus::Draft, diff --git a/server-rs/crates/spacetime-module/src/migration.rs b/server-rs/crates/spacetime-module/src/migration.rs index c2b2bc4b..443133f5 100644 --- a/server-rs/crates/spacetime-module/src/migration.rs +++ b/server-rs/crates/spacetime-module/src/migration.rs @@ -1159,6 +1159,43 @@ where fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde_json::Value { let mut next_value = value.clone(); + if table_name == "creation_entry_config" { + if let Some(object) = next_value.as_object_mut() { + // 中文注释:入口活动横幅字段晚于创作入口配置表加入,旧迁移包按运行态默认横幅兼容。 + object + .entry("event_title".to_string()) + .or_insert(serde_json::Value::Null); + object + .entry("event_description".to_string()) + .or_insert(serde_json::Value::Null); + object + .entry("event_cover_image_src".to_string()) + .or_insert(serde_json::Value::Null); + object + .entry("event_prize_pool_mud_points".to_string()) + .or_insert_with(|| serde_json::Value::from(58_000)); + object + .entry("event_starts_at_text".to_string()) + .or_insert(serde_json::Value::Null); + object + .entry("event_ends_at_text".to_string()) + .or_insert(serde_json::Value::Null); + } + } + if table_name == "creation_entry_type_config" { + if let Some(object) = next_value.as_object_mut() { + // 中文注释:入口分类字段晚于入口类型配置表加入,旧迁移包按未分类兼容。 + object + .entry("category_id".to_string()) + .or_insert(serde_json::Value::Null); + object + .entry("category_label".to_string()) + .or_insert(serde_json::Value::Null); + object + .entry("category_sort_order".to_string()) + .or_insert_with(|| serde_json::Value::from(0)); + } + } if table_name == "user_account" { if let Some(object) = next_value.as_object_mut() { // 中文注释:头像字段晚于认证拆表加入,旧迁移包按未设置头像兼容。 @@ -1271,6 +1308,10 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde object .entry("background_asset_json".to_string()) .or_insert(serde_json::Value::Null); + // 中文注释:敲木鱼返回按钮图晚于首版作品表加入,旧迁移包按未生成返回按钮兼容。 + object + .entry("back_button_asset_json".to_string()) + .or_insert(serde_json::Value::Null); } } next_value diff --git a/server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs b/server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs index 0ce27567..3ee5e0cf 100644 --- a/server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs +++ b/server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs @@ -11,6 +11,18 @@ pub struct CreationEntryConfig { pub(crate) modal_title: String, pub(crate) modal_description: String, pub(crate) updated_at: Timestamp, + #[default(None::)] + pub(crate) event_title: Option, + #[default(None::)] + pub(crate) event_description: Option, + #[default(None::)] + pub(crate) event_cover_image_src: Option, + #[default(DEFAULT_CREATION_ENTRY_EVENT_PRIZE_POOL_MUD_POINTS)] + pub(crate) event_prize_pool_mud_points: u64, + #[default(None::)] + pub(crate) event_starts_at_text: Option, + #[default(None::)] + pub(crate) event_ends_at_text: Option, } #[spacetimedb::table( @@ -28,6 +40,12 @@ pub struct CreationEntryTypeConfig { pub(crate) open: bool, pub(crate) sort_order: i32, pub(crate) updated_at: Timestamp, + #[default(None::)] + pub(crate) category_id: Option, + #[default(None::)] + pub(crate) category_label: Option, + #[default(0)] + pub(crate) category_sort_order: i32, } #[spacetimedb::procedure] @@ -88,6 +106,9 @@ fn upsert_creation_entry_type_config_in_tx( open: input.open, sort_order: input.sort_order, updated_at: now, + category_id: Some(normalize_category_id(&input.category_id)), + category_label: Some(normalize_category_label(&input.category_label)), + category_sort_order: input.category_sort_order, }; if ctx.db.creation_entry_type_config().id().find(&id).is_some() { ctx.db.creation_entry_type_config().id().update(row); @@ -120,6 +141,9 @@ fn get_or_seed_creation_entry_config_snapshot( visible: row.visible, open: row.open, sort_order: row.sort_order, + category_id: normalize_optional_category_id(row.category_id.as_deref()), + category_label: normalize_optional_category_label(row.category_label.as_deref()), + category_sort_order: row.category_sort_order, updated_at_micros: row.updated_at.to_micros_since_unix_epoch(), }) .collect::>(); @@ -141,6 +165,29 @@ fn get_or_seed_creation_entry_config_snapshot( title: header.modal_title, description: header.modal_description, }, + event_banner: CreationEntryEventBannerSnapshot { + title: normalize_optional_text( + header.event_title.as_deref(), + DEFAULT_CREATION_ENTRY_EVENT_TITLE, + ), + description: normalize_optional_text( + header.event_description.as_deref(), + DEFAULT_CREATION_ENTRY_EVENT_DESCRIPTION, + ), + cover_image_src: normalize_optional_text( + header.event_cover_image_src.as_deref(), + DEFAULT_CREATION_ENTRY_EVENT_COVER_IMAGE_SRC, + ), + prize_pool_mud_points: header.event_prize_pool_mud_points, + starts_at_text: normalize_optional_text( + header.event_starts_at_text.as_deref(), + DEFAULT_CREATION_ENTRY_EVENT_STARTS_AT_TEXT, + ), + ends_at_text: normalize_optional_text( + header.event_ends_at_text.as_deref(), + DEFAULT_CREATION_ENTRY_EVENT_ENDS_AT_TEXT, + ), + }, creation_types, updated_at_micros: header.updated_at.to_micros_since_unix_epoch(), }) @@ -164,6 +211,12 @@ fn seed_creation_entry_config_if_missing(ctx: &ReducerContext) { modal_title: DEFAULT_CREATION_ENTRY_MODAL_TITLE.to_string(), modal_description: DEFAULT_CREATION_ENTRY_MODAL_DESCRIPTION.to_string(), updated_at: now, + event_title: Some(DEFAULT_CREATION_ENTRY_EVENT_TITLE.to_string()), + event_description: Some(DEFAULT_CREATION_ENTRY_EVENT_DESCRIPTION.to_string()), + event_cover_image_src: Some(DEFAULT_CREATION_ENTRY_EVENT_COVER_IMAGE_SRC.to_string()), + event_prize_pool_mud_points: DEFAULT_CREATION_ENTRY_EVENT_PRIZE_POOL_MUD_POINTS, + event_starts_at_text: Some(DEFAULT_CREATION_ENTRY_EVENT_STARTS_AT_TEXT.to_string()), + event_ends_at_text: Some(DEFAULT_CREATION_ENTRY_EVENT_ENDS_AT_TEXT.to_string()), }); } @@ -348,6 +401,43 @@ fn default_creation_entry_type_configs(now: Timestamp) -> Vec String { + let normalized = value.trim(); + if normalized.is_empty() { + DEFAULT_CREATION_ENTRY_CATEGORY_ID.to_string() + } else { + normalized.to_string() + } +} + +fn normalize_category_label(value: &str) -> String { + let normalized = value.trim(); + if normalized.is_empty() { + DEFAULT_CREATION_ENTRY_CATEGORY_LABEL.to_string() + } else { + normalized.to_string() + } +} + +fn normalize_optional_category_id(value: Option<&str>) -> String { + normalize_optional_text(value, DEFAULT_CREATION_ENTRY_CATEGORY_ID) +} + +fn normalize_optional_category_label(value: Option<&str>) -> String { + normalize_optional_text(value, DEFAULT_CREATION_ENTRY_CATEGORY_LABEL) +} + +fn normalize_optional_text(value: Option<&str>, fallback: &str) -> String { + value + .map(str::trim) + .filter(|normalized| !normalized.is_empty()) + .unwrap_or(fallback) + .to_string() +} diff --git a/server-rs/crates/spacetime-module/src/wooden_fish.rs b/server-rs/crates/spacetime-module/src/wooden_fish.rs index 3b3982b2..a8ef6954 100644 --- a/server-rs/crates/spacetime-module/src/wooden_fish.rs +++ b/server-rs/crates/spacetime-module/src/wooden_fish.rs @@ -82,6 +82,7 @@ pub struct WoodenFishGalleryViewRow { pub hit_sound_prompt: Option, pub hit_object_asset: Option, pub background_asset: Option, + pub back_button_asset: Option, pub hit_sound_asset: Option, pub floating_words: Vec, pub cover_image_src: String, @@ -333,6 +334,11 @@ fn compile_wooden_fish_draft_tx( .as_deref() .map(parse_json) .transpose()?; + let back_button_asset = input + .back_button_asset_json + .as_deref() + .map(parse_json) + .transpose()?; let cover_image_src = input .cover_image_src .as_deref() @@ -361,6 +367,7 @@ fn compile_wooden_fish_draft_tx( floating_words: floating_words.clone(), hit_object_asset: hit_object_asset.clone(), background_asset: background_asset.clone(), + back_button_asset: back_button_asset.clone(), hit_sound_asset: hit_sound_asset.clone(), cover_image_src: cover_image_src.clone(), generation_status: input @@ -400,6 +407,7 @@ fn compile_wooden_fish_draft_tx( updated_at: compiled_at, published_at: None, background_asset_json: background_asset.as_ref().map(to_json_string), + back_button_asset_json: back_button_asset.as_ref().map(to_json_string), }; upsert_work(ctx, row); let config = config_from_draft(&draft); @@ -485,6 +493,14 @@ fn update_wooden_fish_work_tx( let asset = parse_json::(&value)?; next.background_asset_json = Some(to_json_string(&asset)); } + if let Some(value) = input + .back_button_asset_json + .as_deref() + .and_then(clean_optional) + { + let asset = parse_json::(&value)?; + next.back_button_asset_json = Some(to_json_string(&asset)); + } if let Some(value) = input .floating_words_json .as_deref() @@ -512,7 +528,7 @@ fn publish_wooden_fish_work_tx( ) -> Result { let row = find_owned_work(ctx, &input.profile_id, &input.owner_user_id)?; if !is_publish_ready(&row) { - return Err("发布需要完整的敲击物图案、敲击音效和飘字配置".to_string()); + return Err("发布需要完整的敲击物图案、背景、返回按钮、敲击音效和飘字配置".to_string()); } let published_at = Timestamp::from_micros_since_unix_epoch(input.published_at_micros); replace_work( @@ -691,6 +707,7 @@ fn build_gallery_view_row( hit_sound_prompt: work.hit_sound_prompt, hit_object_asset: work.hit_object_asset, background_asset: work.background_asset, + back_button_asset: work.back_button_asset, hit_sound_asset: work.hit_sound_asset, floating_words: work.floating_words, cover_image_src: work.cover_image_src, @@ -744,6 +761,12 @@ fn build_work_snapshot(row: &WoodenFishWorkProfileRow) -> Result bool { .as_deref() .and_then(clean_optional) .is_some() + && row + .back_button_asset_json + .as_deref() + .and_then(clean_optional) + .is_some() && !row.hit_sound_asset_json.trim().is_empty() && !row.floating_words_json.trim().is_empty() && row.generation_status == WOODEN_FISH_GENERATION_READY @@ -1031,6 +1059,7 @@ fn draft_from_config( floating_words: normalize_floating_words(&config.floating_words), hit_object_asset: None, background_asset: None, + back_button_asset: None, hit_sound_asset: None, cover_image_src: None, generation_status: generation_status.to_string(), @@ -1051,6 +1080,7 @@ fn draft_from_work_snapshot(work: &WoodenFishWorkSnapshot) -> WoodenFishDraftSna floating_words: work.floating_words.clone(), hit_object_asset: work.hit_object_asset.clone(), background_asset: work.background_asset.clone(), + back_button_asset: work.back_button_asset.clone(), hit_sound_asset: work.hit_sound_asset.clone(), cover_image_src: clean_optional(&work.cover_image_src), generation_status: work.generation_status.clone(), @@ -1231,6 +1261,7 @@ fn clone_work(row: &WoodenFishWorkProfileRow) -> WoodenFishWorkProfileRow { hit_object_asset_json: row.hit_object_asset_json.clone(), background_asset_json: row.background_asset_json.clone(), hit_sound_asset_json: row.hit_sound_asset_json.clone(), + back_button_asset_json: row.back_button_asset_json.clone(), floating_words_json: row.floating_words_json.clone(), cover_image_src: row.cover_image_src.clone(), generation_status: row.generation_status.clone(), diff --git a/server-rs/crates/spacetime-module/src/wooden_fish/tables.rs b/server-rs/crates/spacetime-module/src/wooden_fish/tables.rs index 27899c15..74268598 100644 --- a/server-rs/crates/spacetime-module/src/wooden_fish/tables.rs +++ b/server-rs/crates/spacetime-module/src/wooden_fish/tables.rs @@ -47,6 +47,8 @@ pub struct WoodenFishWorkProfileRow { pub(crate) published_at: Option, #[default(None::)] pub(crate) background_asset_json: Option, + #[default(None::)] + pub(crate) back_button_asset_json: Option, } #[spacetimedb::table( diff --git a/server-rs/crates/spacetime-module/src/wooden_fish/types.rs b/server-rs/crates/spacetime-module/src/wooden_fish/types.rs index 0bbeef03..2ab8c0e6 100644 --- a/server-rs/crates/spacetime-module/src/wooden_fish/types.rs +++ b/server-rs/crates/spacetime-module/src/wooden_fish/types.rs @@ -47,6 +47,7 @@ pub struct WoodenFishDraftCompileInput { pub hit_object_asset_json: Option, pub background_asset_json: Option, pub hit_sound_asset_json: Option, + pub back_button_asset_json: Option, pub floating_words_json: Option, pub cover_image_src: Option, pub generation_status: Option, @@ -66,6 +67,7 @@ pub struct WoodenFishWorkUpdateInput { pub hit_object_asset_json: Option, pub background_asset_json: Option, pub hit_sound_asset_json: Option, + pub back_button_asset_json: Option, pub floating_words_json: Option, pub cover_image_src: Option, pub generation_status: Option, @@ -210,6 +212,7 @@ pub struct WoodenFishDraftSnapshot { pub floating_words: Vec, pub hit_object_asset: Option, pub background_asset: Option, + pub back_button_asset: Option, pub hit_sound_asset: Option, pub cover_image_src: Option, pub generation_status: String, @@ -246,6 +249,7 @@ pub struct WoodenFishWorkSnapshot { pub hit_sound_prompt: Option, pub hit_object_asset: Option, pub background_asset: Option, + pub back_button_asset: Option, pub hit_sound_asset: Option, pub floating_words: Vec, pub cover_image_src: String, diff --git a/src/components/CustomWorldGenerationView.test.tsx b/src/components/CustomWorldGenerationView.test.tsx index 0e589b13..82ee365c 100644 --- a/src/components/CustomWorldGenerationView.test.tsx +++ b/src/components/CustomWorldGenerationView.test.tsx @@ -52,9 +52,9 @@ function createProgress( describe('CustomWorldGenerationView', () => { test.each(['拼图草稿生成进度', '抓大鹅草稿生成进度'])( - 'hides batch module and keeps wait/timer in one row for %s', + 'renders the circular hero and only the current step summary for %s', (progressTitle) => { - render( + const { container } = render( { onBack={() => {}} onEditSetting={() => {}} onRetry={() => {}} + backLabel="返回创作中心" settingDescription={null} settingActionLabel={null} progressTitle={progressTitle} />, ); + expect(container.firstChild).toBeTruthy(); + expect((container.firstChild as HTMLElement).className).toContain( + 'z-[1]', + ); + + const pageVideo = screen.getByTestId( + 'generation-page-background-video', + ) as HTMLVideoElement; + expect(pageVideo.parentElement?.className).toContain('z-0'); + expect(pageVideo.parentElement?.className).toContain('bg-transparent'); + expect(pageVideo.parentElement?.className).not.toContain('bg-[#fff4ea]'); + expect((container.firstChild as HTMLElement).contains(pageVideo)).toBe( + true, + ); + expect(pageVideo.autoplay).toBe(true); + expect(pageVideo.loop).toBe(true); + expect(pageVideo.muted).toBe(true); + expect(pageVideo.playsInline).toBe(true); + expect(pageVideo.getAttribute('preload')).toBe('auto'); + expect( + document.querySelector( + 'video[data-testid="generation-page-background-video"] source[type="video/mp4"]', + ), + ).toBeTruthy(); + expect( + screen.getByRole('button', { name: '返回创作中心' }), + ).toBeTruthy(); + expect( + screen.getByRole('button', { name: '返回创作中心' }).className, + ).toContain('text-xs'); + expect(screen.getByText('世界建设中')).toBeTruthy(); + expect(screen.getByText('世界建设中').className).toContain('text-xs'); + expect(screen.getByTestId('generation-hero-wait-card').className).toContain( + 'text-center', + ); + expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain( + 'text-center', + ); + expect(screen.getByTestId('generation-hero-wait-card').className).toContain( + 'bg-white/58', + ); + expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain( + 'bg-white/58', + ); + expect(screen.getByText('预计等待').className).toContain('text-[9px]'); + expect(screen.getByText('已耗时').className).toContain('text-[9px]'); + expect(screen.getByText('预计等待').parentElement?.className).toContain( + 'justify-center', + ); + expect(screen.getByText('已耗时').parentElement?.className).toContain( + 'justify-center', + ); + expect(screen.getByText('1 分 15 秒')).toBeTruthy(); + expect(screen.getByText('2 分 5 秒')).toBeTruthy(); + expect(screen.queryByText('预计还需 1 分 15 秒')).toBeNull(); + expect(screen.queryByText('已耗时 2 分 5 秒')).toBeNull(); + expect(screen.queryByText('计时')).toBeNull(); + expect(screen.getByTestId('generation-hero-progress-content').className).toContain( + 'justify-start', + ); + expect(screen.getByTestId('generation-hero-progress-content').className).toContain( + 'pt-[4%]', + ); + expect(screen.getByText('总进度').className).toContain('text-[9px]'); + expect(screen.getByText('42%').className).toContain('text-[1.15rem]'); + expect( + screen + .getByRole('progressbar', { name: progressTitle }) + .className, + ).toContain('w-[min(35rem,94vw)]'); + expect( + screen + .getByRole('progressbar', { name: progressTitle }) + .className, + ).toContain('sm:w-[52rem]'); + expect( + screen + .getByRole('progressbar', { name: progressTitle }) + .getAttribute('data-ring-start-degrees'), + ).toBe('225'); + expect( + screen + .getByRole('progressbar', { name: progressTitle }) + .getAttribute('data-ring-sweep-degrees'), + ).toBe('270'); + expect( + screen + .getByRole('progressbar', { name: progressTitle }) + .getAttribute('data-ring-gap-degrees'), + ).toBe('90'); + expect( + screen + .getByRole('progressbar', { name: progressTitle }) + .getAttribute('data-ring-fill-degrees'), + ).toBe('113'); + expect(screen.getByTestId('generation-hero-progress-ring').tagName).toBe( + 'svg', + ); + expect( + screen + .getByTestId('generation-hero-progress-ring') + .getAttribute('viewBox'), + ).toBe('0 0 400 400'); + expect( + screen + .getByTestId('generation-hero-progress-ring-track') + .getAttribute('r'), + ).toBe('166'); + expect( + screen + .getByTestId('generation-hero-progress-ring-track') + .getAttribute('stroke-width'), + ).toBe('18'); + expect( + screen + .getByTestId('generation-hero-progress-ring-fill') + .getAttribute('stroke-dasharray'), + ).toMatch(/^328\.\d{2} 1043\.\d{2}$/u); + expect( + screen.getByRole('progressbar', { name: progressTitle }), + ).toBeTruthy(); + expect( + screen + .getByRole('progressbar', { name: progressTitle }) + .getAttribute('aria-valuenow'), + ).toBe('42'); + expect(screen.getByText('当前步骤')).toBeTruthy(); + expect(screen.getByText('当前步骤').className).toContain('text-[10px]'); + expect(screen.getByText('编译草稿')).toBeTruthy(); + expect(screen.getByText('编译草稿').className).toContain('text-[14px]'); + expect(screen.getByText('进行中 50%')).toBeTruthy(); + expect(screen.getByText('进行中 50%').className).toContain('text-[11px]'); + expect( + screen.getByTestId('generation-current-step-card').className, + ).toContain('bg-white/58'); + expect( + screen.getByRole('progressbar', { name: '编译草稿 进度' }), + ).toBeTruthy(); + expect(screen.queryByText('收集设定')).toBeNull(); + expect(screen.queryByText('写回结果')).toBeNull(); expect(screen.queryByText('当前批次')).toBeNull(); - expect(screen.getByText('预计等待')).toBeTruthy(); - expect(screen.getByText('计时')).toBeTruthy(); - - const statsNode = screen - .getByText('预计等待') - .closest('.custom-world-generation-stats'); - expect(statsNode?.className).toContain( - 'custom-world-generation-stats--two-column', - ); - expect(statsNode?.getAttribute('style')).toContain( - 'grid-template-columns: repeat(2, minmax(0, 1fr))', - ); - - const stepNodes = [ - screen.getByText('收集设定'), - screen.getByText('编译草稿'), - screen.getByText('写回结果'), - ].map((node) => node.closest('.custom-world-generation-step')); - - expect(stepNodes.every(Boolean)).toBe(true); - expect(stepNodes[0]?.getAttribute('style')).toContain( - '--generation-step-delay: 0ms', - ); - expect(stepNodes[1]?.getAttribute('style')).toContain( - '--generation-step-delay: 90ms', - ); - expect(stepNodes[2]?.getAttribute('style')).toContain( - '--generation-step-delay: 180ms', - ); + expect(screen.queryByText('正在整理当前设定步骤')).toBeNull(); }, ); - test('keeps batch module for other generation pages', () => { + test('keeps the setting information panel as compact information cards', () => { render( {}} onEditSetting={() => {}} onRetry={() => {}} + backLabel="返回创作中心" settingDescription={null} settingActionLabel={null} + settingTitle="当前大鱼吃小鱼信息" progressTitle="大鱼吃小鱼草稿生成进度" />, ); - expect(screen.getByText('当前批次')).toBeTruthy(); - expect( - screen - .getByText('预计等待') - .closest('.custom-world-generation-stats') - ?.className, - ).not.toContain('custom-world-generation-stats--two-column'); + expect(screen.getByText('当前大鱼吃小鱼信息')).toBeTruthy(); + expect(screen.getByText('当前大鱼吃小鱼信息').className).toContain('text-[13px]'); + expect(screen.getByText('题材')).toBeTruthy(); + expect(screen.getByText('题材').className).toContain('text-[9px]'); + expect(screen.getByText('火锅')).toBeTruthy(); + expect(screen.getByText('火锅').className).toContain('text-[13px]'); + expect(screen.getByText('素材数量')).toBeTruthy(); + expect(screen.getByText('20 种素材')).toBeTruthy(); + expect(screen.queryByText('大鱼吃小鱼题材')).toBeNull(); + expect(screen.getByTestId('generation-page-background-video')).toBeTruthy(); }); }); diff --git a/src/components/CustomWorldGenerationView.tsx b/src/components/CustomWorldGenerationView.tsx index b540a4f0..9a00c842 100644 --- a/src/components/CustomWorldGenerationView.tsx +++ b/src/components/CustomWorldGenerationView.tsx @@ -1,9 +1,12 @@ -import { motion } from 'motion/react'; -import type { CSSProperties } from 'react'; -import { useEffect, useState } from 'react'; +import { ArrowLeft } from 'lucide-react'; import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime'; import type { CustomWorldStructuredAnchorEntry } from '../services/customWorldAgentGenerationProgress'; +import { + GenerationCurrentStepCard, + GenerationPageBackdrop, + GenerationProgressHero, +} from './GenerationProgressHero'; interface CustomWorldGenerationViewProps { settingText: string; @@ -81,6 +84,19 @@ function getStepStatusLabel(step: { status: string }) { return '待处理'; } +function resolveCurrentGenerationStep( + progress: CustomWorldGenerationProgress | null, +) { + const steps = progress?.steps ?? []; + return ( + steps.find((step) => step.status === 'active') ?? + steps[progress?.activeStepIndex ?? -1] ?? + steps.find((step) => step.status === 'pending') ?? + steps.at(-1) ?? + null + ); +} + function buildFallbackRenderKey( value: string | null | undefined, fallback: string, @@ -89,49 +105,6 @@ function buildFallbackRenderKey( return normalizedValue ? normalizedValue : fallback; } -function useIsMobileGenerationLayout() { - const [isMobile, setIsMobile] = useState(() => { - if ( - typeof window === 'undefined' || - typeof window.matchMedia !== 'function' - ) { - return false; - } - - return window.matchMedia('(max-width: 639px)').matches; - }); - - useEffect(() => { - if ( - typeof window === 'undefined' || - typeof window.matchMedia !== 'function' - ) { - return undefined; - } - - const mediaQuery = window.matchMedia('(max-width: 639px)'); - const syncMobileLayout = () => { - setIsMobile(mediaQuery.matches); - }; - - syncMobileLayout(); - - if (typeof mediaQuery.addEventListener === 'function') { - mediaQuery.addEventListener('change', syncMobileLayout); - return () => { - mediaQuery.removeEventListener('change', syncMobileLayout); - }; - } - - mediaQuery.addListener(syncMobileLayout); - return () => { - mediaQuery.removeListener(syncMobileLayout); - }; - }, []); - - return isMobile; -} - export function CustomWorldGenerationView({ settingText, anchorEntries = [], @@ -155,42 +128,47 @@ export function CustomWorldGenerationView({ structuredEmptyText = '正在整理当前设定结构,请稍后。', hideBatchModule = false, }: CustomWorldGenerationViewProps) { - const isMobileGenerationLayout = useIsMobileGenerationLayout(); + void hideBatchModule; const progressValue = getProgressPercentage(progress); - const steps = progress?.steps ?? []; + const currentStep = resolveCurrentGenerationStep(progress); + const currentStepProgress = currentStep + ? getStepProgressPercentage(currentStep) + : progressValue; + const currentStepLabel = currentStep?.label ?? progress?.phaseLabel ?? '准备生成'; + const currentStepStatusLabel = currentStep + ? getStepStatusLabel(currentStep) + : isGenerating + ? '进行中' + : '待处理'; const hasStructuredAnchors = anchorEntries.length > 0; // 允许不同生成场景按需隐藏第二模块的说明和次级返回动作。 const normalizedSettingActionLabel = settingActionLabel?.trim() ?? ''; const normalizedSettingDescription = settingDescription?.trim() ?? ''; const hasSettingActionLabel = normalizedSettingActionLabel.length > 0; const hasSettingDescription = normalizedSettingDescription.length > 0; - const shouldHideBatchModule = - hideBatchModule || - progressTitle === '拼图草稿生成进度' || - progressTitle === '抓大鹅草稿生成进度'; const estimatedWaitText = progress?.estimatedRemainingMs != null - ? `预计还需 ${formatDuration(progress.estimatedRemainingMs)}` - : '正在校准预计等待时间'; + ? formatDuration(progress.estimatedRemainingMs) + : '校准中'; const elapsedText = - progress != null - ? `已耗时 ${formatDuration(progress.elapsedMs)}` - : '正在启动世界生成'; + progress != null ? formatDuration(progress.elapsedMs) : '启动中'; return (
-
+ +
-
+
{isGenerating ? activeBadgeLabel : error @@ -199,143 +177,26 @@ export function CustomWorldGenerationView({
-
-
-
-
-
- {progressTitle} -
-
- {progress?.phaseLabel ?? '正在启动世界生成'} -
-
- {progress?.phaseDetail ?? '正在初始化世界生成链路与阶段监控。'} -
-
-
-
- 总进度 -
-
- {progressValue}% -
-
-
+
+
+ -
- +
-
- {shouldHideBatchModule ? null : ( -
-
- 当前批次 -
-
- {progress?.batchLabel ?? '准备中'} -
-
- )} -
-
- 预计等待 -
-
- {estimatedWaitText} -
-
-
-
- 计时 -
-
- {elapsedText} -
-
-
- -
- {steps.map((step, index) => { - const stepProgress = getStepProgressPercentage(step); - - return ( - -
-
- {step.label} -
-
- {getStepStatusLabel(step)} {stepProgress}% -
-
-
- -
-
- {step.detail} -
-
- ); - })} -
- {error ? ( -
+
{error}
) : null} @@ -372,14 +233,14 @@ export function CustomWorldGenerationView({
-
-
+
+
-
+
{settingTitle}
{hasSettingDescription ? ( -
+
{normalizedSettingDescription}
) : null} @@ -396,26 +257,26 @@ export function CustomWorldGenerationView({ ) : null}
{hasStructuredAnchors ? ( -
+
{anchorEntries.map((entry, index) => (
-
+
{entry.label}
-
+
{entry.value}
))}
) : ( -
+
{settingText || structuredEmptyText}
)} diff --git a/src/components/GenerationProgressHero.tsx b/src/components/GenerationProgressHero.tsx new file mode 100644 index 00000000..9883d96c --- /dev/null +++ b/src/components/GenerationProgressHero.tsx @@ -0,0 +1,289 @@ +import { Clock3, Hourglass } from 'lucide-react'; +import { motion } from 'motion/react'; +import { useEffect, useId, useRef } from 'react'; + +import generationHeroVideo from '../../media/create_bg_video.mp4'; + +const GENERATION_PROGRESS_RING_START_DEGREES = 225; +const GENERATION_PROGRESS_RING_SWEEP_DEGREES = 270; +const GENERATION_PROGRESS_RING_VIEWBOX = 400; +const GENERATION_PROGRESS_RING_CENTER = GENERATION_PROGRESS_RING_VIEWBOX / 2; +const GENERATION_PROGRESS_RING_RADIUS = 166; +const GENERATION_PROGRESS_RING_STROKE_WIDTH = 18; +const GENERATION_PROGRESS_RING_SWEEP_RATIO = + GENERATION_PROGRESS_RING_SWEEP_DEGREES / 360; + +type GenerationProgressHeroProps = { + title: string; + phaseLabel: string; + progressValue: number; + estimatedWaitText: string; + elapsedText: string; +}; + +type GenerationCurrentStepCardProps = { + label: string; + statusLabel: string; + progressValue: number; +}; + +function clampGenerationProgress(value: number) { + return Math.max(0, Math.min(100, Math.round(value))); +} + +function buildGenerationRingMetrics(progressValue: number) { + const circumference = 2 * Math.PI * GENERATION_PROGRESS_RING_RADIUS; + const sweepLength = circumference * GENERATION_PROGRESS_RING_SWEEP_RATIO; + const progressLength = sweepLength * (progressValue / 100); + + return { + circumference, + progressLength, + sweepLength, + }; +} + +export function GenerationPageBackdrop() { + const videoRef = useRef(null); + + useEffect(() => { + const video = videoRef.current; + if (!video) { + return undefined; + } + + video.defaultMuted = true; + video.muted = true; + video.volume = 0; + + const isJsdom = + window.navigator.userAgent.toLowerCase().includes('jsdom'); + const tryPlay = () => { + if (isJsdom) { + return; + } + try { + const playPromise = video.play(); + if (playPromise && typeof playPromise.then === 'function') { + void playPromise.catch(() => {}); + } + } catch { + // 中文注释:测试环境和某些内核可能同步拒绝 play;失败时保留静音背景层,不阻断页面渲染。 + } + }; + + tryPlay(); + video.addEventListener('loadeddata', tryPlay); + video.addEventListener('canplay', tryPlay); + video.addEventListener('playing', tryPlay); + window.addEventListener('focus', tryPlay); + document.addEventListener('visibilitychange', tryPlay); + + return () => { + video.removeEventListener('loadeddata', tryPlay); + video.removeEventListener('canplay', tryPlay); + video.removeEventListener('playing', tryPlay); + window.removeEventListener('focus', tryPlay); + document.removeEventListener('visibilitychange', tryPlay); + }; + }, []); + + return ( +
+ +
+
+ ); +} + +export function GenerationProgressHero({ + title, + phaseLabel, + progressValue, + estimatedWaitText, + elapsedText, +}: GenerationProgressHeroProps) { + const safeProgress = clampGenerationProgress(progressValue); + const ringGradientId = useId().replace(/:/g, ''); + const ringMetrics = buildGenerationRingMetrics(safeProgress); + const ringDegrees = Math.round((safeProgress / 100) * 270); + const ringTrackDasharray = `${ringMetrics.sweepLength.toFixed(2)} ${ringMetrics.circumference.toFixed(2)}`; + const ringFillDasharray = `${ringMetrics.progressLength.toFixed(2)} ${ringMetrics.circumference.toFixed(2)}`; + + return ( +
+
+ {title} + {phaseLabel ? ` ${phaseLabel}` : ''} +
+
+
+
+ +
+ 预计等待 +
+
+
+ {estimatedWaitText} +
+
+ +
+
+
+ 已耗时 +
+ +
+
+ {elapsedText} +
+
+ +
+ +
+
+ 总进度 +
+
+ {safeProgress}% +
+
+
+
+
+ ); +} + +export function GenerationCurrentStepCard({ + label, + statusLabel, + progressValue, +}: GenerationCurrentStepCardProps) { + const safeProgress = clampGenerationProgress(progressValue); + const isActive = statusLabel === '进行中'; + + return ( +
+
+
+
+ 当前步骤 +
+
+ {label} +
+
+
+
+ {statusLabel} {safeProgress}% +
+ {isActive ? ( +
+
+
+ +
+
+ ); +} diff --git a/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx b/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx index 9a341f2b..02af134b 100644 --- a/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx +++ b/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx @@ -50,7 +50,7 @@ describe('BarkBattleGeneratingView', () => { updatedAt: '2026-05-14T10:01:00.000Z', }); - render( + const { container } = render( {}} @@ -59,9 +59,142 @@ describe('BarkBattleGeneratingView', () => { />, ); + expect(container.firstChild).toBeTruthy(); + expect((container.firstChild as HTMLElement).className).toContain('z-[1]'); + expect(screen.getByText('总进度')).toBeTruthy(); + expect(screen.getByText('总进度').className).toContain('text-[9px]'); + const pageVideo = screen.getByTestId( + 'generation-page-background-video', + ) as HTMLVideoElement; + expect(pageVideo.parentElement?.className).toContain('z-0'); + expect(pageVideo.parentElement?.className).toContain('bg-transparent'); + expect(pageVideo.parentElement?.className).not.toContain('bg-[#fff4ea]'); + expect((container.firstChild as HTMLElement).contains(pageVideo)).toBe( + true, + ); + expect(pageVideo.autoplay).toBe(true); + expect(pageVideo.loop).toBe(true); + expect(pageVideo.muted).toBe(true); + expect(pageVideo.playsInline).toBe(true); + expect(pageVideo.getAttribute('preload')).toBe('auto'); + expect( + document.querySelector( + 'video[data-testid="generation-page-background-video"] source[type="video/mp4"]', + ), + ).toBeTruthy(); + expect(screen.getByRole('button', { name: '返回编辑' }).className).toContain( + 'text-xs', + ); + expect(screen.getByText('生成中').className).toContain('text-[11px]'); + expect(screen.getByText('当前步骤')).toBeTruthy(); + expect(screen.getByText('当前步骤').className).toContain('text-[10px]'); + expect(screen.getByTestId('generation-hero-wait-card').className).toContain( + 'text-center', + ); + expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain( + 'text-center', + ); + expect(screen.getByTestId('generation-hero-wait-card').className).toContain( + 'bg-white/58', + ); + expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain( + 'bg-white/58', + ); + expect(screen.getByText('预计等待').className).toContain('text-[9px]'); + expect(screen.getByText('已耗时').className).toContain('text-[9px]'); + expect(screen.getByText('预计等待').parentElement?.className).toContain( + 'justify-center', + ); + expect(screen.getByText('已耗时').parentElement?.className).toContain( + 'justify-center', + ); + expect(screen.getByText('3 分钟')).toBeTruthy(); + expect(screen.getByText('1 秒')).toBeTruthy(); + expect(screen.queryByText('预计还需 3 分钟')).toBeNull(); + expect(screen.queryByText('已耗时 1 秒')).toBeNull(); + expect(screen.getByTestId('generation-hero-progress-content').className).toContain( + 'justify-start', + ); + expect(screen.getByTestId('generation-hero-progress-content').className).toContain( + 'pt-[4%]', + ); expect(screen.getByText('玩家形象')).toBeTruthy(); - expect(screen.getByText('对手形象')).toBeTruthy(); - expect(screen.getByText('竞技背景')).toBeTruthy(); + expect(screen.getByText('进行中 36%')).toBeTruthy(); + expect(screen.getByText('进行中 36%').className).toContain('text-[11px]'); + expect(screen.getByText('总进度').className).toContain('text-[9px]'); + expect(screen.getByText('0%').className).toContain('text-[1.15rem]'); + expect( + screen + .getByRole('progressbar', { name: '汪汪声浪素材生成进度' }) + .className, + ).toContain('w-[min(35rem,94vw)]'); + expect( + screen + .getByRole('progressbar', { name: '汪汪声浪素材生成进度' }) + .className, + ).toContain('sm:w-[52rem]'); + expect( + screen + .getByRole('progressbar', { name: '汪汪声浪素材生成进度' }) + .getAttribute('aria-valuenow'), + ).toBe('0'); + expect( + screen + .getByRole('progressbar', { name: '汪汪声浪素材生成进度' }) + .getAttribute('data-ring-start-degrees'), + ).toBe('225'); + expect( + screen + .getByRole('progressbar', { name: '汪汪声浪素材生成进度' }) + .getAttribute('data-ring-sweep-degrees'), + ).toBe('270'); + expect( + screen + .getByRole('progressbar', { name: '汪汪声浪素材生成进度' }) + .getAttribute('data-ring-gap-degrees'), + ).toBe('90'); + expect( + screen + .getByRole('progressbar', { name: '汪汪声浪素材生成进度' }) + .getAttribute('data-ring-fill-degrees'), + ).toBe('0'); + expect(screen.getByTestId('generation-hero-progress-ring').tagName).toBe( + 'svg', + ); + expect( + screen + .getByTestId('generation-hero-progress-ring') + .getAttribute('viewBox'), + ).toBe('0 0 400 400'); + expect( + screen + .getByTestId('generation-hero-progress-ring-track') + .getAttribute('r'), + ).toBe('166'); + expect( + screen + .getByTestId('generation-hero-progress-ring-track') + .getAttribute('stroke-width'), + ).toBe('18'); + expect( + screen + .getByTestId('generation-hero-progress-ring-fill') + .getAttribute('stroke-dasharray'), + ).toMatch(/^0\.00 1043\.\d{2}$/u); + expect( + screen.getByRole('progressbar', { name: '玩家形象 进度' }), + ).toBeTruthy(); + expect( + screen + .getByRole('progressbar', { name: '玩家形象 进度' }) + .getAttribute('aria-valuenow'), + ).toBe('36'); + expect( + screen.getByTestId('generation-current-step-card').className, + ).toContain('bg-white/58'); + expect(screen.getByText('预览信息').className).toContain('text-[13px]'); + expect(screen.queryByText('对手形象')).toBeNull(); + expect(screen.queryByText('竞技背景')).toBeNull(); expect(onComplete).not.toHaveBeenCalled(); resolveGeneration({ diff --git a/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx b/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx index b1402bea..2a1a293f 100644 --- a/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx +++ b/src/components/bark-battle-creation/BarkBattleGeneratingView.tsx @@ -1,4 +1,4 @@ -import { AlertCircle, ArrowLeft, CheckCircle2, Loader2, Sparkles } from 'lucide-react'; +import { ArrowLeft } from 'lucide-react'; import { useEffect, useMemo, useRef, useState } from 'react'; import type { BarkBattleDraftConfig } from '../../../packages/shared/src/contracts/barkBattle'; @@ -12,6 +12,11 @@ import { generateAllBarkBattleImageAssets, updateBarkBattleDraftConfig, } from '../../services/bark-battle-creation'; +import { + GenerationCurrentStepCard, + GenerationPageBackdrop, + GenerationProgressHero, +} from '../GenerationProgressHero'; import { BarkBattlePreviewCard } from './BarkBattlePreviewCard'; type BarkBattleGeneratingViewProps = { @@ -110,6 +115,56 @@ function buildDraftGenerationKey(draft: BarkBattleDraftConfig) { ].join('|'); } +function getSlotStatusLabel(status: BarkBattleGeneratingSlotStatus) { + if (status === 'ready') { + return '完成'; + } + if (status === 'failed') { + return '失败'; + } + return '进行中'; +} + +function formatGenerationDuration(ms: number) { + const totalSeconds = Math.max(1, Math.ceil(Math.max(0, ms) / 1000)); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + + if (minutes <= 0) { + return `${seconds} 秒`; + } + + if (seconds === 0) { + return `${minutes} 分钟`; + } + + return `${minutes} 分 ${seconds} 秒`; +} + +function resolveBarkBattleProgressValue( + slotStatuses: Partial< + Record + >, +) { + const readyCount = GENERATION_STEPS.filter( + (step) => slotStatuses[step.slot] === 'ready', + ).length; + return Math.round((readyCount / GENERATION_STEPS.length) * 100); +} + +function resolveCurrentBarkBattleStep( + slotStatuses: Partial< + Record + >, +) { + return ( + GENERATION_STEPS.find((step) => slotStatuses[step.slot] === 'generating') ?? + GENERATION_STEPS.find((step) => slotStatuses[step.slot] === 'failed') ?? + GENERATION_STEPS.find((step) => slotStatuses[step.slot] !== 'ready') ?? + GENERATION_STEPS[GENERATION_STEPS.length - 1] + ); +} + export function BarkBattleGeneratingView({ draft, isBusy = false, @@ -125,10 +180,33 @@ export function BarkBattleGeneratingView({ const [slotStatuses, setSlotStatuses] = useState< Partial> >({}); + const [elapsedMs, setElapsedMs] = useState(0); const primaryFailureMessage = useMemo( () => resolvePrimaryFailureMessage(slotFailures), [slotFailures], ); + const progressValue = resolveBarkBattleProgressValue(slotStatuses); + const currentStep = resolveCurrentBarkBattleStep(slotStatuses); + const currentStepStatus = currentStep + ? (slotStatuses[currentStep.slot] ?? + (hasSlotAsset(previewDraft, currentStep.slot) ? 'ready' : 'generating')) + : 'generating'; + const currentStepProgress = + currentStepStatus === 'ready' ? 100 : currentStepStatus === 'failed' ? 100 : 36; + const currentStepLabel = currentStep?.label ?? '竞技素材'; + const currentStepStatusLabel = getSlotStatusLabel(currentStepStatus); + + useEffect(() => { + const startedAtMs = Date.now(); + const timerId = window.setInterval(() => { + setElapsedMs(Date.now() - startedAtMs); + }, 1000); + setElapsedMs(0); + + return () => { + window.clearInterval(timerId); + }; + }, [draft.draftId]); useEffect(() => { setPreviewDraft(draft); @@ -277,76 +355,54 @@ export function BarkBattleGeneratingView({ }, [draft, onComplete, onError]); return ( -
-
-
+
+ +
+
- + 生成中
-
-
-
-
- - 自动生成素材 -
-

- {draft.title || '未命名声浪竞技场'} -

-
+
+
+ -
- {GENERATION_STEPS.map((step) => { - const status = - slotStatuses[step.slot] ?? - (hasSlotAsset(previewDraft, step.slot) ? 'ready' : 'generating'); - const ready = status === 'ready'; - const failed = - status === 'failed' || Boolean(slotFailures[step.slot]); - const statusLabel = ready - ? `${step.label}已生成` - : failed - ? `${step.label}生成失败` - : `${step.label}生成中`; - return ( -
- - {step.label} - - {ready ? ( - - ) : failed ? ( - - ) : ( - - )} -
- ); - })} +
+
{error || primaryFailureMessage ? ( -
+
{error ?? primaryFailureMessage}
) : null}
- +
+
+ 预览信息 +
+ +
@@ -354,4 +410,3 @@ export function BarkBattleGeneratingView({ } export default BarkBattleGeneratingView; - diff --git a/src/components/common/CreativeImageInputPanel.tsx b/src/components/common/CreativeImageInputPanel.tsx index b5997cd7..5c6a7721 100644 --- a/src/components/common/CreativeImageInputPanel.tsx +++ b/src/components/common/CreativeImageInputPanel.tsx @@ -57,6 +57,7 @@ export type CreativeImageInputPanelProps = { imageModelPicker?: ReactNode; error?: string | null; inputError?: string | null; + showSubmitButton?: boolean; submitLabel: string; submitCostLabel?: string | null; submitDisabled: boolean; @@ -100,6 +101,7 @@ export function CreativeImageInputPanel({ imageModelPicker = null, error = null, inputError = null, + showSubmitButton = true, submitLabel, submitCostLabel = null, submitDisabled, @@ -389,27 +391,31 @@ export function CreativeImageInputPanel({
-
- -
+ {showSubmitButton ? ( +
+ +
+ ) : null} {previewReferenceImage ? (
, ); - expect(screen.getByRole('button', { name: '全部 2' })).toBeTruthy(); - expect(screen.getByRole('button', { name: '草稿 1' })).toBeTruthy(); - expect(screen.getByRole('button', { name: '已发布 1' })).toBeTruthy(); + expect(screen.getByRole('tab', { name: '全部 2' })).toBeTruthy(); + expect(screen.getByRole('tab', { name: '草稿 1' })).toBeTruthy(); + expect(screen.getByRole('tab', { name: '已发布 1' })).toBeTruthy(); expect(screen.getByText('竖屏声浪草稿')).toBeTruthy(); expect(screen.getByText('竖屏声浪已发布')).toBeTruthy(); - await user.click(screen.getByRole('button', { name: '草稿 1' })); + await user.click(screen.getByRole('tab', { name: '草稿 1' })); expect(screen.getByText('竖屏声浪草稿')).toBeTruthy(); expect(screen.queryByText('竖屏声浪已发布')).toBeNull(); - await user.click(screen.getByRole('button', { name: '已发布 1' })); + await user.click(screen.getByRole('tab', { name: '已发布 1' })); expect(screen.queryByText('竖屏声浪草稿')).toBeNull(); expect(screen.getByText('竖屏声浪已发布')).toBeTruthy(); @@ -880,6 +906,38 @@ test('creation hub published share icon is shown directly on the card header', ( expect(screen.queryByRole('button', { name: '删除' })).toBeNull(); }); +test('creation hub shows RPG published share icon without library entry', () => { + render( + {}} + onCreateType={noopCreateType} + onOpenDraft={() => {}} + onEnterPublished={() => {}} + entryConfig={testEntryConfig} + creationTypes={testCreationTypes} + />, + ); + + expect(screen.getByText('潮雾列岛已发布版')).toBeTruthy(); + expect(screen.getByRole('button', { name: '分享' })).toBeTruthy(); + expect(screen.queryByText('作者:玩家')).toBeNull(); +}); + test('creation hub left swipe draft reveals delete without opening card', () => { const onDeletePublished = vi.fn(); const onOpenDraft = vi.fn(); diff --git a/src/components/custom-world-home/CustomWorldCreationHub.test.tsx b/src/components/custom-world-home/CustomWorldCreationHub.test.tsx index ced0e82c..9f71189a 100644 --- a/src/components/custom-world-home/CustomWorldCreationHub.test.tsx +++ b/src/components/custom-world-home/CustomWorldCreationHub.test.tsx @@ -18,6 +18,14 @@ const testEntryConfig = { title: '选择创作类型', description: '先选玩法类型,再进入对应创作工作台。', }, + eventBanner: { + title: '泥点挑战', + description: '创作活动测试横幅。', + coverImageSrc: '/creation-type-references/puzzle.webp', + prizePoolMudPoints: 1000, + startsAtText: '2026-05-01', + endsAtText: '2026-05-31', + }, creationTypes: [ { id: 'rpg', @@ -28,6 +36,9 @@ const testEntryConfig = { visible: true, open: true, sortOrder: 10, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -39,17 +50,23 @@ const testEntryConfig = { visible: true, open: true, sortOrder: 30, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { id: 'match3d', title: '抓大鹅', subtitle: '3D 消除关卡', - badge: '可创建', + badge: '可创作', imageSrc: '/creation-type-references/match3d.webp', visible: true, open: true, sortOrder: 40, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -61,6 +78,9 @@ const testEntryConfig = { visible: false, open: true, sortOrder: 50, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -72,6 +92,9 @@ const testEntryConfig = { visible: false, open: false, sortOrder: 60, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -83,6 +106,9 @@ const testEntryConfig = { visible: true, open: false, sortOrder: 70, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, ], @@ -140,6 +166,96 @@ test('creation hub draft card renders compiled work summary fields', () => { expect(html).not.toContain('大鱼吃小鱼'); }); +test('creation start card renders reference-aligned banner and template metadata', () => { + const html = renderToStaticMarkup( + {}} + onCreateType={noopCreateType} + onOpenDraft={() => {}} + onEnterPublished={() => {}} + entryConfig={testEntryConfig} + creationTypes={testCreationTypes} + mode="start-only" + />, + ); + + expect(html).toContain('creation-event-banner'); + expect(html).toContain('creation-event-banner__track'); + expect(html).toContain('creation-event-banner__slide'); + expect(html).toContain('creation-event-banner__timebar'); + expect(html).toContain('拼图主题创作赛'); + expect(html).toContain('抓大鹅主题创作赛'); + expect(html).toContain('1,000'); + expect(html).toContain('泥点数'); + expect(html).not.toContain('泥点挑战'); + expect(html).toMatch( + /creation-event-banner__timebar[\s\S]*creation-event-banner__pager[\s\S]*creation-template-card/u, + ); + expect(html).toContain('creation-template-card__body'); + expect(html).toContain('creation-template-card__cost-badge'); + expect(html).toContain('拼图关卡创作'); + expect(html).toContain('10-20泥点数'); + expect(html).toContain('即将开放'); + expect(html).not.toContain('可创建'); + expect(html).not.toContain('可创作'); + expect(html).not.toContain('creation-event-banner__counter'); + expect(html).not.toContain('预计消耗 10-20 泥点'); + expect(html).not.toContain('platform-creation-reference-card'); +}); + +test('creation start card keeps typography in compact UI scale', () => { + const html = renderToStaticMarkup( + {}} + onCreateType={noopCreateType} + onOpenDraft={() => {}} + onEnterPublished={() => {}} + entryConfig={testEntryConfig} + creationTypes={testCreationTypes} + mode="start-only" + />, + ); + + expect(html).toMatch(/creation-template-card__title[^"]*\btext-sm\b/u); + expect(html).toMatch(/creation-template-card__subtitle[^"]*\btext-xs\b/u); + expect(html).toMatch( + /creation-template-card__cost-badge[^"]*\btext-\[11px\](?:\s|")/u, + ); + expect(html).not.toMatch( + /\b(text-lg|text-xl|sm:text-base|sm:text-lg|sm:text-xl|text-\[1\.08rem\])\b/u, + ); +}); + +test('creation start card removes the outer template list frame and tightens card grid', () => { + const html = renderToStaticMarkup( + {}} + onCreateType={noopCreateType} + onOpenDraft={() => {}} + onEnterPublished={() => {}} + entryConfig={testEntryConfig} + creationTypes={testCreationTypes} + mode="start-only" + />, + ); + + expect(html).toContain('creation-template-list'); + expect(html).toMatch(/creation-template-list__grid[^"]*\bgap-2\b/u); + expect(html).toMatch(/creation-template-card[^"]*\bmin-h-\[12\.5rem\]/u); + expect(html).not.toMatch( + /creation-template-list[^"]*\bborder\b[^"]*\bborder-\[#f0dfd6\]/u, + ); +}); + test('creation hub renders puzzle works in the same unified list with puzzle tag', () => { const html = renderToStaticMarkup( 查看详情<'); }); + +test('creation hub root keeps the remap theme hook without the page card shell', () => { + const html = renderToStaticMarkup( + {}} + onCreateType={noopCreateType} + onOpenDraft={() => {}} + onEnterPublished={() => {}} + entryConfig={testEntryConfig} + creationTypes={testCreationTypes} + mode="works-only" + />, + ); + + expect(html).toContain('platform-remap-surface'); + expect(html).not.toContain('platform-page-stage'); +}); + +test('creation hub draft tabs use discover-style channel labels', () => { + const html = renderToStaticMarkup( + {}} + onCreateType={noopCreateType} + onOpenDraft={() => {}} + onEnterPublished={() => {}} + entryConfig={testEntryConfig} + creationTypes={testCreationTypes} + puzzleItems={[ + { + workId: 'puzzle:works-tab', + profileId: 'puzzle-profile-works-tab', + ownerUserId: 'user-1', + authorDisplayName: '测试作者', + levelName: '测试草稿', + summary: '测试草稿', + themeTags: [], + coverImageSrc: null, + publicationStatus: 'draft', + updatedAt: '2026-05-07T00:00:00.000Z', + publishedAt: null, + playCount: 0, + remixCount: 0, + likeCount: 0, + publishReady: false, + }, + ]} + onOpenPuzzleDetail={() => {}} + />, + ); + + expect(html).toContain('platform-mobile-home-channel'); + expect(html).toContain('platform-mobile-home-channel--active'); + expect(html).not.toContain('platform-tab--active'); +}); diff --git a/src/components/custom-world-home/CustomWorldCreationHub.tsx b/src/components/custom-world-home/CustomWorldCreationHub.tsx index e84f786f..ccd20cd5 100644 --- a/src/components/custom-world-home/CustomWorldCreationHub.tsx +++ b/src/components/custom-world-home/CustomWorldCreationHub.tsx @@ -355,7 +355,7 @@ export function CustomWorldCreationHub({ const showWorkShelf = mode !== 'start-only'; return ( -
+
{showStartCard ? ( void; }; +type CreationEventBannerCard = CreationEntryConfig['eventBanner']; + +function shouldShowCreationBadge(badge: string) { + const normalizedBadge = badge.trim(); + return normalizedBadge !== '可创建' && normalizedBadge !== '可创作'; +} + export function CustomWorldCreationStartCard({ busy = false, error = null, @@ -22,30 +30,161 @@ export function CustomWorldCreationStartCard({ creationTypes, onCreateType, }: CustomWorldCreationStartCardProps) { - // 创作首页首屏卡带与创作类型弹层保持同一份展示口径, - // 避免某个玩法只在其中一个入口被隐藏而出现状态漂移。 - const visibleCreationTypes = getVisiblePlatformCreationTypes(creationTypes); + const creationTypeGroups = useMemo( + () => groupVisiblePlatformCreationTypes(creationTypes), + [creationTypes], + ); + const [activeCategoryId, setActiveCategoryId] = useState(null); + const activeGroup = + creationTypeGroups.find((group) => group.id === activeCategoryId) ?? + creationTypeGroups[0] ?? + null; + const visibleCreationTypes = activeGroup?.items ?? []; + const eventBanners = useMemo( + () => [ + { + ...entryConfig.eventBanner, + title: '拼图主题创作赛', + description: '用拼图关卡接住本周主题。', + coverImageSrc: '/creation-type-references/puzzle.webp', + prizePoolMudPoints: 1000, + }, + { + ...entryConfig.eventBanner, + title: '抓大鹅主题创作赛', + description: '把抓大鹅关卡做成主题挑战。', + coverImageSrc: '/creation-type-references/match3d.webp', + prizePoolMudPoints: 1000, + }, + ], + [entryConfig.eventBanner], + ); + const [activeBannerIndex, setActiveBannerIndex] = useState(0); + + function handleBannerScroll(event: UIEvent) { + const { clientWidth, scrollLeft } = event.currentTarget; + if (clientWidth <= 0) { + return; + } + + const nextIndex = Math.max( + 0, + Math.min(eventBanners.length - 1, Math.round(scrollLeft / clientWidth)), + ); + setActiveBannerIndex((currentIndex) => + currentIndex === nextIndex ? currentIndex : nextIndex, + ); + } return ( - // 移动端限制模块高度,模板入口改为横向滚动,避免挤占作品列表首屏空间。 -
-
-
-
-
- {entryConfig.startCard.title} -
-
- {entryConfig.startCard.description} -
- - {busy - ? entryConfig.startCard.busyBadge - : entryConfig.startCard.idleBadge} - +
+
+
+ {eventBanners.map((banner, index) => { + const prizePoolText = + banner.prizePoolMudPoints.toLocaleString('zh-CN'); + + return ( +
+ +
+
+
+
+ + {banner.title} +
+
+ {banner.description} +
+
+ + + + 奖池 + + {prizePoolText} + + 泥点数 +
+
+ +
+
+ + 开始时间  {banner.startsAtText} + + | + + 结束时间  {banner.endsAtText} + +
+ +
+
+
+ ); + })} +
+
+ +
+
+ {creationTypeGroups.map((group) => { + const selected = group.id === activeGroup?.id; + return ( + + ); + })}
-
+
{visibleCreationTypes.map((item) => { const disabled = item.locked || busy; @@ -57,47 +196,35 @@ export function CustomWorldCreationStartCard({ onClick={() => { onCreateType(item.id); }} - className={`platform-creation-reference-card platform-interactive-card relative flex min-h-[4.6rem] w-[11.25rem] shrink-0 snap-start flex-col overflow-hidden rounded-[1.15rem] border p-0 text-left transition sm:min-h-[8.5rem] sm:w-auto sm:rounded-[1.5rem] xl:min-h-[6.4rem] ${ + className={`creation-template-card platform-interactive-card relative flex min-h-[12.5rem] flex-col overflow-hidden rounded-[1rem] border bg-white p-0 text-left transition sm:min-h-[15rem] sm:rounded-[1.2rem] ${ item.locked - ? 'cursor-not-allowed border-white/10 bg-white/8 text-zinc-300/70' - : 'border-white/18 bg-white/16 text-white' + ? 'cursor-not-allowed border-[#eadbd3] text-[#725b4d] opacity-72' + : 'border-[#eadbd3] text-[#2f211b] hover:border-[#dc9a72] hover:shadow-[0_16px_34px_rgba(174,111,73,0.14)]' } ${busy && !item.locked ? 'opacity-70' : ''}`} > - -
-
- {item.locked ? ( - +
+ + {shouldShowCreationBadge(item.badge) ? ( + {item.badge} ) : null} - {item.locked ? ( - · - ) : ( - - )} + + + 10-20泥点数 +
-
-
+
+
{item.title}
-
+
{item.subtitle}
@@ -107,11 +234,11 @@ export function CustomWorldCreationStartCard({
{error ? ( -
+
{error}
) : null} -
+
); } diff --git a/src/components/custom-world-home/CustomWorldWorkCard.tsx b/src/components/custom-world-home/CustomWorldWorkCard.tsx index 0d5cf69e..24fde099 100644 --- a/src/components/custom-world-home/CustomWorldWorkCard.tsx +++ b/src/components/custom-world-home/CustomWorldWorkCard.tsx @@ -729,8 +729,6 @@ export function CustomWorldWorkCard({ {item.summary}
-
作者:{item.authorDisplayName}
- {isPublished ? (
{item.pointIncentive ? ( diff --git a/src/components/custom-world-home/CustomWorldWorkTabs.tsx b/src/components/custom-world-home/CustomWorldWorkTabs.tsx index 1b928cb7..1eb6cc92 100644 --- a/src/components/custom-world-home/CustomWorldWorkTabs.tsx +++ b/src/components/custom-world-home/CustomWorldWorkTabs.tsx @@ -23,7 +23,11 @@ export function CustomWorldWorkTabs({ onChange, }: CustomWorldWorkTabsProps) { return ( -
+
{FILTER_OPTIONS.map((option) => { const count = option.id === 'draft' @@ -36,10 +40,10 @@ export function CustomWorldWorkTabs({ diff --git a/src/components/custom-world-home/creationWorkShelf.test.ts b/src/components/custom-world-home/creationWorkShelf.test.ts index 767fa893..1db7d6d6 100644 --- a/src/components/custom-world-home/creationWorkShelf.test.ts +++ b/src/components/custom-world-home/creationWorkShelf.test.ts @@ -175,6 +175,39 @@ test('buildCreationWorkShelfItems keeps separate bark battle draft and published ); }); +test('buildCreationWorkShelfItems falls back to deterministic RPG public work code when library entry is missing', () => { + const items = buildCreationWorkShelfItems({ + rpgItems: [ + { + workId: 'rpg-work-published', + sourceType: 'published_profile', + status: 'published', + title: '潮雾列岛已发布版', + subtitle: '旧灯塔与失控航路', + summary: '已经发布的群岛世界作品。', + coverImageSrc: null, + updatedAt: '2026-04-20T10:00:00.000Z', + publishedAt: '2026-04-20T10:00:00.000Z', + stage: 'published', + stageLabel: '已发布', + playableNpcCount: 3, + landmarkCount: 4, + sessionId: null, + profileId: 'world-public-1', + canResume: false, + canEnterWorld: true, + }, + ], + bigFishItems: [], + puzzleItems: [], + }); + + expect(items).toHaveLength(1); + expect(items[0]?.publicWorkCode).toBe('CW-00000001'); + expect(items[0]?.sharePath).toContain('/works/detail?work=CW-00000001'); + expect(items[0]?.canShare).toBe(true); +}); + test('buildCreationWorkShelfItems gives bark battle draft cover from character or reference fallback', () => { const items = buildCreationWorkShelfItems({ rpgItems: [], @@ -1009,7 +1042,7 @@ test('bark battle draft generating state follows pending assets or missing three }); -test('CustomWorldWorkCard renders author for draft and published works', () => { +test('CustomWorldWorkCard hides author on shelf draft and published cards', () => { const buildItem = ( status: CreationWorkShelfItem['status'], authorDisplayName: string, @@ -1074,8 +1107,8 @@ test('CustomWorldWorkCard renders author for draft and published works', () => { }), ); - expect(draftHtml).toContain('作者:草稿作者'); - expect(publishedHtml).toContain('作者:发布作者'); + expect(draftHtml).not.toContain('作者:草稿作者'); + expect(publishedHtml).not.toContain('作者:发布作者'); }); test('getCreationWorkShelfItemTime parses backend seconds.microsZ values', () => { diff --git a/src/components/custom-world-home/creationWorkShelf.ts b/src/components/custom-world-home/creationWorkShelf.ts index 4831de64..e1fecab8 100644 --- a/src/components/custom-world-home/creationWorkShelf.ts +++ b/src/components/custom-world-home/creationWorkShelf.ts @@ -11,6 +11,7 @@ import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/co import { buildPublicWorkStagePath } from '../../routing/appPageRoutes'; import { buildBabyObjectMatchPublicWorkCode, + buildCustomWorldPublicWorkCode, buildBarkBattlePublicWorkCode, buildBigFishPublicWorkCode, buildJumpHopPublicWorkCode, @@ -353,7 +354,10 @@ function mapRpgWorkToShelfItem( ? libraryEntries.find((entry) => entry.profileId === item.profileId) : null; const publicWorkCode = - item.status === 'published' ? (libraryEntry?.publicWorkCode ?? null) : null; + item.status === 'published' + ? (libraryEntry?.publicWorkCode?.trim() || + (item.profileId ? buildCustomWorldPublicWorkCode(item.profileId) : null)) + : null; const badges: CreationWorkShelfBadge[] = [ buildStatusBadge(item.status), { id: 'type', label: 'RPG', tone: 'neutral' }, diff --git a/src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx b/src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx index 16e1b08e..583c19ad 100644 --- a/src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx +++ b/src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx @@ -18,6 +18,14 @@ const entryConfig = { title: '选择创作类型', description: '', }, + eventBanner: { + title: '泥点挑战', + description: '创作活动测试横幅。', + coverImageSrc: '/creation-type-references/puzzle.webp', + prizePoolMudPoints: 1000, + startsAtText: '2026-05-01', + endsAtText: '2026-05-31', + }, creationTypes: [ { id: 'wooden-fish', @@ -28,6 +36,9 @@ const entryConfig = { visible: true, open: true, sortOrder: 10, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, ], diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index 65bcbc89..e0d871ec 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -1,4 +1,4 @@ -import { ArrowRight, Loader2 } from 'lucide-react'; +import { Loader2 } from 'lucide-react'; import { AnimatePresence, motion } from 'motion/react'; import { type Dispatch, @@ -208,6 +208,7 @@ import { buildWoodenFishGenerationAnchorEntries, createMiniGameDraftGenerationState, type MiniGameDraftGenerationKind, + type MiniGameDraftGenerationPhase, type MiniGameDraftGenerationState, } from '../../services/miniGameDraftGenerationProgress'; import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient'; @@ -387,7 +388,6 @@ import { PlatformEntryCreationTypeModal } from './PlatformEntryCreationTypeModal import type { PlatformCreationTypeId } from './platformEntryCreationTypes'; import { derivePlatformCreationTypes, - getVisiblePlatformCreationTypes, isPlatformCreationTypeOpen, isPlatformCreationTypeVisible, } from './platformEntryCreationTypes'; @@ -1742,30 +1742,120 @@ function createPendingDraftShelfState( }; } -function parseDraftGenerationStartedAtMs(value: string | null | undefined) { - const parsedMs = value ? Date.parse(value) : Number.NaN; - return Number.isFinite(parsedMs) ? parsedMs : Date.now(); -} - -function createMiniGameDraftGenerationStateFromStartedAt( +function createMiniGameDraftGenerationStateForRestoredDraft( kind: MiniGameDraftGenerationKind, - startedAtMs: number, metadata?: MiniGameDraftGenerationState['metadata'], ): MiniGameDraftGenerationState { return { ...createMiniGameDraftGenerationState(kind), - startedAtMs, ...(metadata ? { metadata } : {}), }; } +function rebaseMiniGameDraftGenerationStateForDisplay( + state: MiniGameDraftGenerationState, +): MiniGameDraftGenerationState { + const rebasedStartedAtMs = Date.now(); + + if (state.kind === 'puzzle') { + const puzzleAiRedraw = state.metadata?.puzzleAiRedraw; + return { + ...state, + startedAtMs: rebasedStartedAtMs, + finishedAtMs: undefined, + metadata: + typeof puzzleAiRedraw === 'boolean' ? { puzzleAiRedraw } : undefined, + }; + } + + return { + ...state, + startedAtMs: rebasedStartedAtMs, + finishedAtMs: undefined, + }; +} + +function rebaseMiniGameDraftBackgroundCompileTaskForDisplay< + T extends PuzzleBackgroundCompileTask | Match3DBackgroundCompileTask, +>( + task: T, +): T { + return { + ...task, + generationState: rebaseMiniGameDraftGenerationStateForDisplay( + task.generationState, + ), + }; +} + function createPuzzleDraftGenerationStateFromPayload( payload: CreatePuzzleAgentSessionRequest | null | undefined, + session: PuzzleAgentSessionSnapshot | null | undefined = null, ): MiniGameDraftGenerationState { + const puzzleProgressPercent = + session?.draft && !session.draft.formDraft + ? session.progressPercent + : undefined; + return { ...createMiniGameDraftGenerationState('puzzle'), metadata: { puzzleAiRedraw: payload?.aiRedraw ?? true, + puzzleActivePhaseId: + typeof puzzleProgressPercent === 'number' ? 'compile' : undefined, + puzzleActiveStepStartedAtMs: + typeof puzzleProgressPercent === 'number' ? Date.now() : undefined, + puzzleProgressPercent, + }, + }; +} + +function resolvePuzzlePhaseFromSessionProgress( + state: MiniGameDraftGenerationState, + session: PuzzleAgentSessionSnapshot, +): MiniGameDraftGenerationPhase { + if (session.progressPercent >= 96) { + return 'puzzle-select-image'; + } + if (session.progressPercent >= 94) { + return 'puzzle-ui-assets'; + } + if (session.progressPercent >= 88) { + return state.metadata?.puzzleAiRedraw === false + ? 'puzzle-level-scene' + : 'puzzle-cover-image'; + } + + return 'compile'; +} + +function mergePuzzleSessionProgressIntoGenerationState( + state: MiniGameDraftGenerationState, + session: PuzzleAgentSessionSnapshot, +): MiniGameDraftGenerationState { + const isCompiledGenerationSession = Boolean( + session.draft && !session.draft.formDraft, + ); + + const nextPhaseId = isCompiledGenerationSession + ? resolvePuzzlePhaseFromSessionProgress(state, session) + : state.metadata?.puzzleActivePhaseId; + const shouldResetActiveStepStart = + isCompiledGenerationSession && + nextPhaseId != null && + nextPhaseId !== state.metadata?.puzzleActivePhaseId; + + return { + ...state, + metadata: { + ...state.metadata, + puzzleActivePhaseId: nextPhaseId, + puzzleActiveStepStartedAtMs: shouldResetActiveStepStart + ? Date.now() + : state.metadata?.puzzleActiveStepStartedAtMs, + puzzleProgressPercent: isCompiledGenerationSession + ? session.progressPercent + : state.metadata?.puzzleProgressPercent, }, }; } @@ -2801,8 +2891,6 @@ export function PlatformEntryFlowShellImpl({ useState(() => Date.now()); const [puzzleFormDraftPayload, setPuzzleFormDraftPayload] = useState(null); - const [activeCreationFormType, setActiveCreationFormType] = - useState('puzzle'); const [puzzleOnboardingPrompt, setPuzzleOnboardingPrompt] = useState(''); const [puzzleOnboardingPhase, setPuzzleOnboardingPhase] = useState('input'); @@ -4485,7 +4573,6 @@ export function PlatformEntryFlowShellImpl({ enterCreateTab, setSelectionStage, onSessionOpened: () => { - setActiveCreationFormType('match3d'); setShowCreationTypeModal(false); }, onActionComplete: async ({ payload, response, setSession }) => { @@ -4884,7 +4971,6 @@ export function PlatformEntryFlowShellImpl({ enterCreateTab, setSelectionStage, onSessionOpened: () => { - setActiveCreationFormType('puzzle'); sessionController.setCreationTypeError(null); setPuzzleCreationError(null); setShowCreationTypeModal(false); @@ -5022,9 +5108,11 @@ export function PlatformEntryFlowShellImpl({ ]); markPendingDraftGenerating('puzzle', session.sessionId); selectionStageRef.current = 'puzzle-generating'; + activePuzzleGenerationSessionIdRef.current = session.sessionId; setSelectionStage('puzzle-generating'); const nextGenerationState = createPuzzleDraftGenerationStateFromPayload( formPayload ?? buildPuzzleFormPayloadFromSession(session), + session, ); setPuzzleGenerationState(nextGenerationState); setPuzzleBackgroundCompileTasks((current) => ({ @@ -5048,7 +5136,7 @@ export function PlatformEntryFlowShellImpl({ const generationState = puzzleBackgroundCompileTasks[session.sessionId]?.generationState ?? puzzleGenerationState ?? - createPuzzleDraftGenerationStateFromPayload(formPayload); + createPuzzleDraftGenerationStateFromPayload(formPayload, session); const recovered = await recoverCompletedPuzzleDraftGeneration({ sessionId: session.sessionId, payload: formPayload, @@ -5160,6 +5248,7 @@ export function PlatformEntryFlowShellImpl({ const puzzleSession = puzzleFlow.session; const puzzleError = puzzleFlow.error; + const setPuzzleSession = puzzleFlow.setSession; const setPuzzleError = puzzleFlow.setError; puzzleErrorSetterRef.current = setPuzzleError; const isPuzzleBusy = puzzleFlow.isBusy; @@ -5373,6 +5462,75 @@ export function PlatformEntryFlowShellImpl({ isMiniGameDraftGenerating( activePuzzleBackgroundCompileTask?.generationState ?? null, ); + const puzzleGenerationViewPhase = puzzleGenerationViewState?.phase ?? null; + const shouldPollPuzzleGenerationSession = + selectionStage === 'puzzle-generating' && + activePuzzleGenerationSessionId != null && + isMiniGameDraftGenerating(puzzleGenerationViewState); + + useEffect(() => { + if (!shouldPollPuzzleGenerationSession || !activePuzzleGenerationSessionId) { + return undefined; + } + + let isDisposed = false; + + const pollPuzzleDraftGenerationSession = async () => { + try { + const { session: latestSession } = await getPuzzleAgentSession( + activePuzzleGenerationSessionId, + ); + if (isDisposed) { + return; + } + + setPuzzleSession(latestSession); + setPuzzleBackgroundCompileTasks((current) => { + const task = current[activePuzzleGenerationSessionId]; + if (!task) { + return current; + } + + return { + ...current, + [activePuzzleGenerationSessionId]: { + ...task, + session: latestSession, + generationState: mergePuzzleSessionProgressIntoGenerationState( + task.generationState, + latestSession, + ), + }, + }; + }); + setPuzzleGenerationState((current) => + current + ? mergePuzzleSessionProgressIntoGenerationState( + current, + latestSession, + ) + : current, + ); + } catch { + // 中文注释:拼图长 action 仍以主请求结果为准;轮询失败只保留当前进度展示。 + } + }; + + void pollPuzzleDraftGenerationSession(); + const timerId = window.setInterval(() => { + void pollPuzzleDraftGenerationSession(); + }, 3_000); + + return () => { + isDisposed = true; + window.clearInterval(timerId); + }; + }, [ + activePuzzleGenerationSessionId, + puzzleGenerationViewPhase, + shouldPollPuzzleGenerationSession, + setPuzzleSession, + ]); const match3DGeneratingSessionId = selectionStage === 'match3d-generating' ? match3dSession?.sessionId : null; @@ -5468,6 +5626,103 @@ export function PlatformEntryFlowShellImpl({ squareHoleFlow, ]); + const openMatch3DWorkspace = useCallback(() => { + setMatch3DRun(null); + setMatch3DProfile(null); + setMatch3DRuntimeProfile(null); + setMatch3DGenerationState(null); + setMatch3DError(null); + setStreamingMatch3DReplyText(''); + setIsStreamingMatch3DReply(false); + enterCreateTab(); + setShowCreationTypeModal(false); + setSelectionStage('match3d-agent-workspace'); + }, [ + enterCreateTab, + setIsStreamingMatch3DReply, + setMatch3DError, + setSelectionStage, + setStreamingMatch3DReplyText, + ]); + + const openJumpHopWorkspace = useCallback(() => { + setJumpHopError(null); + setJumpHopSession(null); + setJumpHopWork(null); + setJumpHopRun(null); + setJumpHopGenerationState(null); + enterCreateTab(); + setShowCreationTypeModal(false); + setSelectionStage('jump-hop-workspace'); + }, [enterCreateTab, setSelectionStage]); + + const openWoodenFishWorkspace = useCallback(() => { + setWoodenFishError(null); + setWoodenFishSession(null); + setWoodenFishWork(null); + setWoodenFishRun(null); + setWoodenFishGenerationState(null); + enterCreateTab(); + setShowCreationTypeModal(false); + setSelectionStage('wooden-fish-workspace'); + }, [enterCreateTab, setSelectionStage]); + + const openPuzzleWorkspace = useCallback(() => { + enterCreateTab(); + setShowCreationTypeModal(false); + setPuzzleCreationError(null); + setPuzzleError(null); + setSelectionStage('puzzle-agent-workspace'); + }, [ + enterCreateTab, + setPuzzleCreationError, + setPuzzleError, + setSelectionStage, + ]); + + const openBarkBattleWorkspace = useCallback(() => { + setBarkBattleDraftConfig(null); + setBarkBattlePublishedConfig(null); + setBarkBattleRuntimeMode('draft'); + setBarkBattleRuntimeReturnStage('platform'); + setBarkBattleError(null); + setBarkBattleGenerationPartialFailed(false); + setIsBarkBattleBusy(false); + enterCreateTab(); + setShowCreationTypeModal(false); + selectionStageRef.current = 'bark-battle-workspace'; + setSelectionStage('bark-battle-workspace'); + }, [enterCreateTab, setSelectionStage]); + + const openVisualNovelWorkspace = useCallback(() => { + enterCreateTab(); + setShowCreationTypeModal(false); + setVisualNovelError(null); + setSelectionStage('visual-novel-agent-workspace'); + }, [ + enterCreateTab, + setSelectionStage, + setVisualNovelError, + ]); + + const openBabyObjectMatchWorkspace = useCallback(() => { + if (!isBabyObjectMatchVisible) { + sessionController.setCreationTypeError(EDUTAINMENT_HIDDEN_MESSAGE); + return; + } + + enterCreateTab(); + setShowCreationTypeModal(false); + setBabyObjectMatchError(null); + setSelectionStage('baby-object-match-workspace'); + }, [ + enterCreateTab, + isBabyObjectMatchVisible, + sessionController, + setBabyObjectMatchError, + setSelectionStage, + ]); + const leaveCreativeAgentWorkspace = useCallback(() => { const sessionId = creativeAgentSession?.sessionId?.trim(); if (sessionId && creativeAgentSession?.stage !== 'target_ready') { @@ -5550,7 +5805,10 @@ export function PlatformEntryFlowShellImpl({ return; } - const generationState = createPuzzleDraftGenerationStateFromPayload(payload); + const generationState = createPuzzleDraftGenerationStateFromPayload( + payload, + nextSession, + ); setPuzzleBackgroundCompileTasks((current) => ({ ...current, [nextSession.sessionId]: { @@ -6134,7 +6392,6 @@ export function PlatformEntryFlowShellImpl({ setMatch3DFormDraftPayload(null); setMatch3DBackgroundCompileTasks({}); activeMatch3DGenerationSessionIdRef.current = null; - setActiveCreationFormType('puzzle'); setMatch3DWorks([]); setMatch3DGalleryEntries([]); setMatch3DRun(null); @@ -6209,6 +6466,16 @@ export function PlatformEntryFlowShellImpl({ selectionStage !== 'platform' && selectionStage !== 'work-detail' && selectionStage !== 'detail' && + selectionStage !== 'agent-workspace' && + selectionStage !== 'big-fish-agent-workspace' && + selectionStage !== 'match3d-agent-workspace' && + selectionStage !== 'square-hole-agent-workspace' && + selectionStage !== 'jump-hop-workspace' && + selectionStage !== 'wooden-fish-workspace' && + selectionStage !== 'puzzle-agent-workspace' && + selectionStage !== 'bark-battle-workspace' && + selectionStage !== 'visual-novel-agent-workspace' && + selectionStage !== 'baby-object-match-workspace' && selectionStage !== 'creative-agent-workspace' && selectionStage !== 'puzzle-gallery-detail' ) { @@ -6221,7 +6488,6 @@ export function PlatformEntryFlowShellImpl({ resetAutoSaveTrackingToIdle, resetRpgSessionViewState, selectionStage, - setActiveCreationFormType, setBigFishError, setIsStreamingMatch3DReply, setIsStreamingSquareHoleReply, @@ -6250,6 +6516,11 @@ export function PlatformEntryFlowShellImpl({ return; } + if (type === 'baby-object-match' && !isBabyObjectMatchVisible) { + sessionController.setCreationTypeError(EDUTAINMENT_HIDDEN_MESSAGE); + return; + } + if (type === 'rpg') { runProtectedAction(() => { void sessionController.openRpgAgentWorkspace(); @@ -6265,10 +6536,9 @@ export function PlatformEntryFlowShellImpl({ } if (type === 'match3d') { - enterCreateTab(); - setShowCreationTypeModal(false); - setActiveCreationFormType('match3d'); - setMatch3DError(null); + runProtectedAction(() => { + void openMatch3DWorkspace(); + }); return; } @@ -6280,91 +6550,60 @@ export function PlatformEntryFlowShellImpl({ } if (type === 'jump-hop') { - enterCreateTab(); - setShowCreationTypeModal(false); - setActiveCreationFormType('jump-hop'); - setJumpHopError(null); - setJumpHopSession(null); - setJumpHopWork(null); - setJumpHopRun(null); - setJumpHopGenerationState(null); + runProtectedAction(() => { + void openJumpHopWorkspace(); + }); return; } if (type === 'wooden-fish') { - enterCreateTab(); - setShowCreationTypeModal(false); - setActiveCreationFormType('wooden-fish'); - setWoodenFishError(null); - setWoodenFishSession(null); - setWoodenFishWork(null); - setWoodenFishRun(null); - setWoodenFishGenerationState(null); + runProtectedAction(() => { + void openWoodenFishWorkspace(); + }); return; } if (type === 'puzzle') { - enterCreateTab(); - setShowCreationTypeModal(false); - setActiveCreationFormType('puzzle'); - setPuzzleCreationError(null); - setPuzzleError(null); + runProtectedAction(() => { + void openPuzzleWorkspace(); + }); return; } if (type === 'bark-battle') { - enterCreateTab(); - setShowCreationTypeModal(false); - setActiveCreationFormType('bark-battle'); - setBarkBattleError(null); + runProtectedAction(() => { + void openBarkBattleWorkspace(); + }); return; } if (type === 'visual-novel') { - enterCreateTab(); - setShowCreationTypeModal(false); - setActiveCreationFormType('visual-novel'); - setVisualNovelError(null); + runProtectedAction(() => { + void openVisualNovelWorkspace(); + }); return; } if (type === 'baby-object-match') { - if (!isBabyObjectMatchVisible) { - sessionController.setCreationTypeError(EDUTAINMENT_HIDDEN_MESSAGE); - return; - } - - enterCreateTab(); - setShowCreationTypeModal(false); - setActiveCreationFormType('baby-object-match'); - setBabyObjectMatchError(null); - return; + runProtectedAction(() => { + void openBabyObjectMatchWorkspace(); + }); } }, [ - openBigFishAgentWorkspace, - enterCreateTab, isBabyObjectMatchVisible, - openSquareHoleAgentWorkspace, + openBarkBattleWorkspace, + openBigFishAgentWorkspace, + openBabyObjectMatchWorkspace, + openJumpHopWorkspace, + openMatch3DWorkspace, prepareCreationLaunch, - runProtectedAction, + openPuzzleWorkspace, + openSquareHoleAgentWorkspace, + openVisualNovelWorkspace, + openWoodenFishWorkspace, sessionController, - setActiveCreationFormType, - setBarkBattleError, - setMatch3DError, - setPuzzleCreationError, - setPuzzleError, - setJumpHopError, - setJumpHopGenerationState, - setJumpHopRun, - setJumpHopSession, - setJumpHopWork, - setWoodenFishError, - setWoodenFishGenerationState, - setWoodenFishRun, - setWoodenFishSession, - setWoodenFishWork, - setVisualNovelError, + runProtectedAction, ], ); @@ -7731,8 +7970,6 @@ export function PlatformEntryFlowShellImpl({ hitObjectReferenceImageSrc: payload?.hitObjectReferenceImageSrc ?? created.session.draft?.hitObjectReferenceImageSrc, - hitSoundPrompt: - payload?.hitSoundPrompt ?? created.session.draft?.hitSoundPrompt, hitSoundAsset: payload?.hitSoundAsset ?? created.session.draft?.hitSoundAsset, floatingWords: @@ -7785,7 +8022,7 @@ export function PlatformEntryFlowShellImpl({ }, [compileWoodenFishSession, setSelectionStage, woodenFishSession]); const regenerateWoodenFishAsset = useCallback( - async (actionType: 'regenerate-hit-object' | 'generate-hit-sound') => { + async (actionType: 'regenerate-hit-object') => { if (!woodenFishSession?.sessionId) { setSelectionStage('wooden-fish-workspace'); return; @@ -7807,7 +8044,6 @@ export function PlatformEntryFlowShellImpl({ hitObjectPrompt: woodenFishSession.draft?.hitObjectPrompt, hitObjectReferenceImageSrc: woodenFishSession.draft?.hitObjectReferenceImageSrc, - hitSoundPrompt: woodenFishSession.draft?.hitSoundPrompt, hitSoundAsset: woodenFishSession.draft?.hitSoundAsset, floatingWords: woodenFishSession.draft?.floatingWords, }, @@ -7821,9 +8057,7 @@ export function PlatformEntryFlowShellImpl({ } catch (error) { const errorMessage = resolveRpgCreationErrorMessage( error, - actionType === 'regenerate-hit-object' - ? '重新生成敲击物图案失败。' - : '生成敲击音效失败。', + '重新生成敲击物图案失败。', ); setWoodenFishError(errorMessage); setWoodenFishGenerationState( @@ -7846,6 +8080,54 @@ export function PlatformEntryFlowShellImpl({ ], ); + const updateWoodenFishWorkMeta = useCallback( + async (payload: { + workTitle: string; + workDescription: string; + themeTags: string[]; + }) => { + const sessionId = woodenFishSession?.sessionId?.trim(); + const profileId = + woodenFishWork?.summary.profileId?.trim() || + woodenFishSession?.draft?.profileId?.trim() || + ''; + if (!sessionId || !profileId) { + setWoodenFishError('敲木鱼草稿尚未生成可保存作品信息。'); + setSelectionStage('wooden-fish-result'); + return false; + } + + setIsWoodenFishBusy(true); + setWoodenFishError(null); + try { + const response = await woodenFishClient.executeAction(sessionId, { + actionType: 'update-work-meta', + profileId, + workTitle: payload.workTitle, + workDescription: payload.workDescription, + themeTags: payload.themeTags, + }); + setWoodenFishSession(response.session); + setWoodenFishWork(response.work ?? woodenFishWork); + return true; + } catch (error) { + setWoodenFishError( + resolveRpgCreationErrorMessage(error, '保存敲木鱼作品信息失败。'), + ); + setSelectionStage('wooden-fish-result'); + return false; + } finally { + setIsWoodenFishBusy(false); + } + }, + [ + setSelectionStage, + woodenFishSession?.draft?.profileId, + woodenFishSession?.sessionId, + woodenFishWork, + ], + ); + const publishWoodenFishDraft = useCallback(async () => { const profileId = woodenFishWork?.summary.profileId?.trim(); if (!profileId) { @@ -7958,33 +8240,6 @@ export function PlatformEntryFlowShellImpl({ [setSelectionStage], ); - const restartWoodenFishRuntimeRun = useCallback(async () => { - const profileId = - woodenFishRun?.profileId?.trim() ?? - woodenFishWork?.summary.profileId?.trim(); - if (!profileId) { - await startWoodenFishTestRunFromProfile(); - return; - } - - setIsWoodenFishBusy(true); - setWoodenFishError(null); - try { - const response = await woodenFishClient.startRun(profileId); - setWoodenFishRun(response.run); - } catch (error) { - setWoodenFishError( - resolveRpgCreationErrorMessage(error, '重新开始敲木鱼失败。'), - ); - } finally { - setIsWoodenFishBusy(false); - } - }, [ - startWoodenFishTestRunFromProfile, - woodenFishRun?.profileId, - woodenFishWork?.summary.profileId, - ]); - const checkpointWoodenFishRuntimeRun = useCallback( async (payload: { totalTapCount: number; @@ -8000,31 +8255,6 @@ export function PlatformEntryFlowShellImpl({ [woodenFishRun?.runId], ); - const finishWoodenFishRuntimeRun = useCallback( - async (payload: { - totalTapCount: number; - wordCounters: WoodenFishRunResponse['run']['wordCounters']; - }) => { - const runId = woodenFishRun?.runId; - if (!runId) { - return; - } - setIsWoodenFishBusy(true); - setWoodenFishError(null); - try { - const response = await woodenFishClient.finishRun(runId, payload); - setWoodenFishRun(response.run); - } catch (error) { - setWoodenFishError( - resolveRpgCreationErrorMessage(error, '结束敲木鱼失败。'), - ); - } finally { - setIsWoodenFishBusy(false); - } - }, - [woodenFishRun?.runId], - ); - const executePuzzleAction = puzzleFlow.executeAction; const executePuzzleBackgroundAction = useCallback( @@ -10506,9 +10736,24 @@ export function PlatformEntryFlowShellImpl({ item.sourceSessionId === puzzleSession?.sessionId && isMiniGameDraftGenerating(activeGenerationState) ) { + if (!activeGenerationState) { + return; + } + const rebasedGenerationState = + rebaseMiniGameDraftGenerationStateForDisplay(activeGenerationState); enterCreateTab(); selectionStageRef.current = 'puzzle-generating'; activePuzzleGenerationSessionIdRef.current = item.sourceSessionId; + setPuzzleGenerationState(rebasedGenerationState); + if (backgroundTask) { + setPuzzleBackgroundCompileTasks((current) => ({ + ...current, + [backgroundTask.session.sessionId]: { + ...backgroundTask, + generationState: rebasedGenerationState, + }, + })); + } setSelectionStage('puzzle-generating'); return; } @@ -10517,9 +10762,15 @@ export function PlatformEntryFlowShellImpl({ backgroundTask && isMiniGameDraftGenerating(backgroundTask.generationState) ) { - puzzleFlow.setSession(backgroundTask.session); - setPuzzleFormDraftPayload(backgroundTask.payload); - setPuzzleGenerationState(backgroundTask.generationState); + const rebasedTask = + rebaseMiniGameDraftBackgroundCompileTaskForDisplay(backgroundTask); + puzzleFlow.setSession(rebasedTask.session); + setPuzzleFormDraftPayload(rebasedTask.payload); + setPuzzleGenerationState(rebasedTask.generationState); + setPuzzleBackgroundCompileTasks((current) => ({ + ...current, + [rebasedTask.session.sessionId]: rebasedTask, + })); if (backgroundTask.error) { setPuzzleError(backgroundTask.error); } @@ -10535,21 +10786,32 @@ export function PlatformEntryFlowShellImpl({ const { session: latestSession } = await getPuzzleAgentSession( item.sourceSessionId, ); + const payload = buildPuzzleFormPayloadFromSession(latestSession); + const generationState = createMiniGameDraftGenerationStateForRestoredDraft( + 'puzzle', + { + puzzleAiRedraw: payload.aiRedraw ?? true, + puzzleProgressPercent: + latestSession.draft && !latestSession.draft.formDraft + ? latestSession.progressPercent + : undefined, + }, + ); puzzleFlow.setSession(latestSession); - setPuzzleFormDraftPayload( - buildPuzzleFormPayloadFromSession(latestSession), - ); + setPuzzleFormDraftPayload(payload); setPuzzleGenerationState( - createMiniGameDraftGenerationStateFromStartedAt( - 'puzzle', - parseDraftGenerationStartedAtMs(item.updatedAt), - { - puzzleAiRedraw: - buildPuzzleFormPayloadFromSession(latestSession).aiRedraw ?? - true, - }, - ), + rebaseMiniGameDraftGenerationStateForDisplay(generationState), ); + setPuzzleBackgroundCompileTasks((current) => ({ + ...current, + [latestSession.sessionId]: { + session: latestSession, + payload, + generationState: + rebaseMiniGameDraftGenerationStateForDisplay(generationState), + error: null, + }, + })); enterCreateTab(); selectionStageRef.current = 'puzzle-generating'; activePuzzleGenerationSessionIdRef.current = item.sourceSessionId; @@ -10679,9 +10941,24 @@ export function PlatformEntryFlowShellImpl({ item.sourceSessionId === match3dSession?.sessionId && isMiniGameDraftGenerating(activeGenerationState) ) { + if (!activeGenerationState) { + return; + } + const rebasedGenerationState = + rebaseMiniGameDraftGenerationStateForDisplay(activeGenerationState); enterCreateTab(); selectionStageRef.current = 'match3d-generating'; activeMatch3DGenerationSessionIdRef.current = item.sourceSessionId; + setMatch3DGenerationState(rebasedGenerationState); + if (backgroundTask) { + setMatch3DBackgroundCompileTasks((current) => ({ + ...current, + [backgroundTask.session.sessionId]: { + ...backgroundTask, + generationState: rebasedGenerationState, + }, + })); + } setSelectionStage('match3d-generating'); return; } @@ -10690,9 +10967,15 @@ export function PlatformEntryFlowShellImpl({ backgroundTask && isMiniGameDraftGenerating(backgroundTask.generationState) ) { - setMatch3DSession(backgroundTask.session); - setMatch3DFormDraftPayload(backgroundTask.payload); - setMatch3DGenerationState(backgroundTask.generationState); + const rebasedTask = + rebaseMiniGameDraftBackgroundCompileTaskForDisplay(backgroundTask); + setMatch3DSession(rebasedTask.session); + setMatch3DFormDraftPayload(rebasedTask.payload); + setMatch3DGenerationState(rebasedTask.generationState); + setMatch3DBackgroundCompileTasks((current) => ({ + ...current, + [rebasedTask.session.sessionId]: rebasedTask, + })); if (backgroundTask.error) { setMatch3DError(backgroundTask.error); } @@ -10710,12 +10993,11 @@ export function PlatformEntryFlowShellImpl({ setMatch3DSession(latestSession); setMatch3DFormDraftPayload(null); setMatch3DProfile(null); - setMatch3DGenerationState( - createMiniGameDraftGenerationStateFromStartedAt( - 'match3d', - parseDraftGenerationStartedAtMs(item.updatedAt), - ), - ); + const generationState = + rebaseMiniGameDraftGenerationStateForDisplay( + createMiniGameDraftGenerationStateForRestoredDraft('match3d'), + ); + setMatch3DGenerationState(generationState); enterCreateTab(); selectionStageRef.current = 'match3d-generating'; activeMatch3DGenerationSessionIdRef.current = item.sourceSessionId; @@ -11692,11 +11974,7 @@ export function PlatformEntryFlowShellImpl({ onBack={() => { setActiveRecommendRuntimeKind(null); }} - onRestart={() => { - void restartWoodenFishRuntimeRun(); - }} onCheckpoint={checkpointWoodenFishRuntimeRun} - onFinish={finishWoodenFishRuntimeRun} /> ); } @@ -11863,7 +12141,6 @@ export function PlatformEntryFlowShellImpl({ resolveSquareHoleErrorMessage, reportBigFishObservedPlayTime, restartBigFishRun, - restartWoodenFishRuntimeRun, selectedPuzzleDetail, selectionStage, setMatch3DError, @@ -11887,7 +12164,6 @@ export function PlatformEntryFlowShellImpl({ visualNovelSession, visualNovelWork, checkpointWoodenFishRuntimeRun, - finishWoodenFishRuntimeRun, ]); useEffect(() => { @@ -12953,7 +13229,8 @@ export function PlatformEntryFlowShellImpl({ puzzleShelfError ?? puzzleError ?? (isVisualNovelCreationOpen ? visualNovelError : null) ?? - babyObjectMatchError) + babyObjectMatchError ?? + barkBattleError) } onRetry={() => { platformBootstrap.setPlatformError(null); @@ -13005,7 +13282,8 @@ export function PlatformEntryFlowShellImpl({ puzzleCreationError ?? puzzleError ?? (isVisualNovelCreationOpen ? visualNovelError : null) ?? - babyObjectMatchError + babyObjectMatchError ?? + barkBattleError } createBusy={ !creationEntryConfig || @@ -13149,208 +13427,9 @@ export function PlatformEntryFlowShellImpl({ ) : null} ); - const creationStartContent = ( -
-
-
-
- {getVisiblePlatformCreationTypes(creationEntryTypes).map((item) => { - const selected = item.id === activeCreationFormType; - const disabled = item.locked; - - return ( - - ); - })} -
-
- -
- {activeCreationFormType === 'match3d' ? ( - } - > - { - void executeMatch3DAction(payload); - }} - initialFormPayload={match3dFormDraftPayload} - onCreateFromForm={(payload) => { - runProtectedAction(() => { - void createMatch3DDraftFromForm(payload); - }); - }} - showBackButton={false} - title={null} - /> - - ) : activeCreationFormType === 'visual-novel' ? ( - } - > - { - runProtectedAction(() => { - void createVisualNovelDraftFromForm(payload); - }); - }} - showBackButton={false} - title={null} - /> - - ) : activeCreationFormType === 'baby-object-match' ? ( - } - > - { - void createBabyObjectMatchDraftFromForm(payload); - }} - showBackButton={false} - title={null} - /> - - ) : activeCreationFormType === 'bark-battle' ? ( - } - > - { - void createBarkBattleGeneratingDraft(payload); - }} - showBackButton={false} - title={null} - /> - - ) : activeCreationFormType === 'jump-hop' ? ( - } - > - { - void compileJumpHopSession(result, payload); - }} - /> - - ) : activeCreationFormType === 'wooden-fish' ? ( - } - > - { - void compileWoodenFishSession(result, payload); - }} - /> - - ) : ( - } - > - { - void submitPuzzleMessage(payload); - }} - onExecuteAction={(payload) => { - executePuzzleWorkspaceAction(payload); - }} - initialFormPayload={puzzleFormDraftPayload} - onCreateFromForm={(payload) => { - runProtectedAction(() => { - void createPuzzleDraftFromForm(payload); - }); - }} - onAutoSaveForm={(payload) => { - void savePuzzleFormDraft(payload); - }} - showBackButton={false} - title={null} - /> - - )} -
-
-
+ const creationStartContent = renderCreationHubContent( + 'start-only', + '正在加载创作大厅...', ); const draftHubContent = renderCreationHubContent( 'works-only', @@ -13835,7 +13914,7 @@ export function PlatformEntryFlowShellImpl({ )} - {selectionStage === 'match3d-agent-workspace' && match3dSession && ( + {selectionStage === 'match3d-agent-workspace' && ( { void executeMatch3DAction(payload); }} + initialFormPayload={match3dFormDraftPayload} + onCreateFromForm={(payload) => { + runProtectedAction(() => { + void createMatch3DDraftFromForm(payload); + }); + }} /> @@ -14233,6 +14318,29 @@ export function PlatformEntryFlowShellImpl({ )} + {selectionStage === 'bark-battle-workspace' && ( + + } + > + { + void createBarkBattleGeneratingDraft(payload); + }} + /> + + + )} + {selectionStage === 'square-hole-agent-workspace' && ( { void regenerateWoodenFishAsset('regenerate-hit-object'); }} - onGenerateHitSound={() => { - void regenerateWoodenFishAsset('generate-hit-sound'); - }} + onUpdateWorkMeta={updateWoodenFishWorkMeta} /> @@ -14696,11 +14802,7 @@ export function PlatformEntryFlowShellImpl({ onBack={() => { setSelectionStage(woodenFishRuntimeReturnStage); }} - onRestart={() => { - void restartWoodenFishRuntimeRun(); - }} onCheckpoint={checkpointWoodenFishRuntimeRun} - onFinish={finishWoodenFishRuntimeRun} /> @@ -15221,7 +15323,6 @@ export function PlatformEntryFlowShellImpl({ error={barkBattleError} onBack={() => { enterCreateTab(); - setActiveCreationFormType('bark-battle'); selectionStageRef.current = 'platform'; setSelectionStage('platform'); }} @@ -15254,7 +15355,6 @@ export function PlatformEntryFlowShellImpl({ } onBack={() => { enterCreateTab(); - setActiveCreationFormType('bark-battle'); selectionStageRef.current = 'platform'; setSelectionStage('platform'); }} @@ -15294,7 +15394,6 @@ export function PlatformEntryFlowShellImpl({ setSelectionStage('bark-battle-result'); } else { enterCreateTab(); - setActiveCreationFormType('bark-battle'); setSelectionStage('platform'); } }} diff --git a/src/components/platform-entry/platformEntryCreationTypes.test.ts b/src/components/platform-entry/platformEntryCreationTypes.test.ts index 00edf621..e1de45da 100644 --- a/src/components/platform-entry/platformEntryCreationTypes.test.ts +++ b/src/components/platform-entry/platformEntryCreationTypes.test.ts @@ -2,6 +2,7 @@ import { afterEach, expect, test, vi } from 'vitest'; import { derivePlatformCreationTypes, + groupVisiblePlatformCreationTypes, getVisiblePlatformCreationTypes, isPlatformCreationTypeOpen, isPlatformCreationTypeVisible, @@ -22,6 +23,9 @@ test('database entry config controls visibility open state and display order', ( visible: true, open: false, sortOrder: 30, + categoryId: 'recommended', + categoryLabel: '热门推荐', + categorySortOrder: 20, updatedAtMicros: 1, }, { @@ -33,6 +37,9 @@ test('database entry config controls visibility open state and display order', ( visible: true, open: true, sortOrder: 20, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -44,6 +51,9 @@ test('database entry config controls visibility open state and display order', ( visible: false, open: true, sortOrder: 10, + categoryId: 'festival', + categoryLabel: '节日主题', + categorySortOrder: 30, updatedAtMicros: 1, }, ]); @@ -79,6 +89,9 @@ test('visible platform creation types hide invisible cards and put locked cards visible: false, open: true, sortOrder: 1, + categoryId: 'hidden', + categoryLabel: '隐藏', + categorySortOrder: 99, updatedAtMicros: 1, }, { @@ -90,6 +103,9 @@ test('visible platform creation types hide invisible cards and put locked cards visible: true, open: false, sortOrder: 2, + categoryId: 'recommended', + categoryLabel: '热门推荐', + categorySortOrder: 20, updatedAtMicros: 1, }, { @@ -101,6 +117,9 @@ test('visible platform creation types hide invisible cards and put locked cards visible: true, open: true, sortOrder: 3, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, ]); @@ -131,6 +150,9 @@ test('edutainment switch hides baby object match creation entry from database co visible: true, open: true, sortOrder: 1, + categoryId: 'character', + categoryLabel: '角色创作', + categorySortOrder: 40, updatedAtMicros: 1, }, { @@ -142,6 +164,9 @@ test('edutainment switch hides baby object match creation entry from database co visible: true, open: true, sortOrder: 2, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, ]); @@ -160,6 +185,9 @@ test('edutainment switch hides baby object match creation entry from database co visible: true, open: true, sortOrder: 1, + categoryId: 'character', + categoryLabel: '角色创作', + categorySortOrder: 40, updatedAtMicros: 1, }, { @@ -171,6 +199,9 @@ test('edutainment switch hides baby object match creation entry from database co visible: true, open: true, sortOrder: 2, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, ]); @@ -194,6 +225,9 @@ test('baby object match entry is visible and open when database marks it creatab visible: true, open: true, sortOrder: 90, + categoryId: 'character', + categoryLabel: '角色创作', + categorySortOrder: 40, updatedAtMicros: 1, }, ]); @@ -208,3 +242,76 @@ test('baby object match entry is visible and open when database marks it creatab expect(isPlatformCreationTypeVisible(cards, 'baby-object-match')).toBe(true); expect(isPlatformCreationTypeOpen(cards, 'baby-object-match')).toBe(true); }); + +test('groups visible platform creation types by backend category metadata', () => { + const cards = derivePlatformCreationTypes([ + { + id: 'puzzle', + title: '秋日暖阳', + subtitle: '记录秋日的温暖时光', + badge: '热门', + imageSrc: '/creation-type-references/puzzle.webp', + visible: true, + open: true, + sortOrder: 30, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, + updatedAtMicros: 1, + }, + { + id: 'match3d', + title: '秋日小屋', + subtitle: '打造专属的秋日小屋', + badge: '精选', + imageSrc: '/creation-type-references/match3d.webp', + visible: true, + open: true, + sortOrder: 40, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, + updatedAtMicros: 1, + }, + { + id: 'visual-novel', + title: '视觉小说', + subtitle: '分支叙事体验', + badge: '敬请期待', + imageSrc: '/creation-type-references/visual-novel.webp', + visible: true, + open: false, + sortOrder: 60, + categoryId: 'festival', + categoryLabel: '节日主题', + categorySortOrder: 30, + updatedAtMicros: 1, + }, + { + id: 'hidden', + title: '隐藏入口', + subtitle: '隐藏', + badge: '隐藏', + imageSrc: '/creation-type-references/hidden.webp', + visible: false, + open: true, + sortOrder: 10, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, + updatedAtMicros: 1, + }, + ]); + + const groups = groupVisiblePlatformCreationTypes(cards); + + expect(groups.map((group) => group.label)).toEqual([ + '最近创作', + '节日主题', + ]); + expect(groups[0]?.items.map((item) => item.id)).toEqual([ + 'puzzle', + 'match3d', + ]); + expect(groups[1]?.items.map((item) => item.id)).toEqual(['visual-novel']); +}); diff --git a/src/components/platform-entry/platformEntryCreationTypes.ts b/src/components/platform-entry/platformEntryCreationTypes.ts index e52faf99..3aad4e59 100644 --- a/src/components/platform-entry/platformEntryCreationTypes.ts +++ b/src/components/platform-entry/platformEntryCreationTypes.ts @@ -10,9 +10,23 @@ export type PlatformCreationTypeCard = { badge: string; imageSrc: string; locked: boolean; + categoryId: string; + categoryLabel: string; + categorySortOrder: number; + sortOrder: number; hidden?: boolean; }; +export type PlatformCreationTypeGroup = { + id: string; + label: string; + sortOrder: number; + items: PlatformCreationTypeCard[]; +}; + +const FALLBACK_CREATION_CATEGORY_ID = 'recent'; +const FALLBACK_CREATION_CATEGORY_LABEL = '最近创作'; + export function getVisiblePlatformCreationTypes( creationTypes: readonly PlatformCreationTypeCard[], ) { @@ -41,6 +55,50 @@ export function isPlatformCreationTypeOpen( ); } +function normalizeCategoryId(value: string) { + const normalized = value.trim(); + return normalized || FALLBACK_CREATION_CATEGORY_ID; +} + +function normalizeCategoryLabel(value: string) { + const normalized = value.trim(); + return normalized || FALLBACK_CREATION_CATEGORY_LABEL; +} + +export function groupVisiblePlatformCreationTypes( + creationTypes: readonly PlatformCreationTypeCard[], +): PlatformCreationTypeGroup[] { + const groups = new Map(); + + for (const item of getVisiblePlatformCreationTypes(creationTypes)) { + const categoryId = normalizeCategoryId(item.categoryId); + const categoryLabel = normalizeCategoryLabel(item.categoryLabel); + const existing = groups.get(categoryId); + + if (existing) { + existing.items.push(item); + if (item.categorySortOrder < existing.sortOrder) { + existing.sortOrder = item.categorySortOrder; + } + continue; + } + + groups.set(categoryId, { + id: categoryId, + label: categoryLabel, + sortOrder: item.categorySortOrder, + items: [item], + }); + } + + return [...groups.values()].sort((left, right) => { + if (left.sortOrder !== right.sortOrder) { + return left.sortOrder - right.sortOrder; + } + return left.label.localeCompare(right.label, 'zh-Hans-CN'); + }); +} + /** * 创作入口卡片只做展示派生;配置事实源来自后端 API / SpacetimeDB,前端不再保留入口默认配置。 */ @@ -56,6 +114,10 @@ export function derivePlatformCreationTypes( badge: item.badge, imageSrc: item.imageSrc, locked: !item.open, + categoryId: normalizeCategoryId(item.categoryId), + categoryLabel: normalizeCategoryLabel(item.categoryLabel), + categorySortOrder: item.categorySortOrder, + sortOrder: item.sortOrder, hidden: !item.visible || (item.id === 'baby-object-match' && !isEdutainmentEntryEnabled()), diff --git a/src/components/platform-entry/platformEntryTypes.ts b/src/components/platform-entry/platformEntryTypes.ts index 7820728f..e34c9403 100644 --- a/src/components/platform-entry/platformEntryTypes.ts +++ b/src/components/platform-entry/platformEntryTypes.ts @@ -36,6 +36,7 @@ export type SelectionStage = | 'jump-hop-result' | 'jump-hop-runtime' | 'jump-hop-gallery-detail' + | 'bark-battle-workspace' | 'bark-battle-generating' | 'bark-battle-result' | 'bark-battle-runtime' diff --git a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx index 54240439..036b09fe 100644 --- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx +++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx @@ -35,8 +35,8 @@ import type { CustomWorldGalleryCard, CustomWorldLibraryEntry, } from '../../../packages/shared/src/contracts/runtime'; -import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; import { normalizeCustomWorldProfileRecord } from '../../data/customWorldLibrary'; +import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; import { readPublicWorkCodeFromLocationSearch, resolveSelectionStageFromPath, @@ -196,9 +196,30 @@ async function clickFirstAsyncButtonByName( async function openCreateTemplateHub(user: ReturnType) { await clickFirstButtonByName(user, '创作'); - expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy(); - expect(screen.getByRole('tab', { name: '拼图' })).toBeTruthy(); - expect(screen.getByText('拼图工作区:missing-session')).toBeTruthy(); + const panel = getPlatformTabPanel('create'); + await waitFor(() => { + expect(panel.getAttribute('aria-hidden')).toBe('false'); + }); + expect( + await within(panel).findByRole('tablist', { name: '玩法模板分类' }), + ).toBeTruthy(); + expect( + await within(panel).findByRole('button', { name: /拼图/u }), + ).toBeTruthy(); + expect(within(panel).queryByText('拼图工作区:missing-session')).toBeNull(); + return panel; +} + +async function findCreationTypeButton(name: string | RegExp) { + const matcher = + typeof name === 'string' ? new RegExp(name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'u') : name; + return within(getPlatformTabPanel('create')).findByRole('button', { name: matcher }); +} + +function queryCreationTypeButton(name: string | RegExp) { + const matcher = + typeof name === 'string' ? new RegExp(name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'u') : name; + return within(getPlatformTabPanel('create')).queryByRole('button', { name: matcher }); } async function openDraftHub(user: ReturnType) { @@ -208,7 +229,7 @@ async function openDraftHub(user: ReturnType) { expect(panel.getAttribute('aria-hidden')).toBe('false'); }); expect( - await within(panel).findByRole('button', { name: /全部/u }), + await within(panel).findByRole('tab', { name: /全部/u }), ).toBeTruthy(); } @@ -276,6 +297,14 @@ const testCreationEntryConfig = { title: '选择创作类型', description: '先选玩法类型,再进入对应创作工作台。', }, + eventBanner: { + title: '泥点挑战', + description: '创作活动测试横幅。', + coverImageSrc: '/creation-type-references/puzzle.webp', + prizePoolMudPoints: 1000, + startsAtText: '2026-05-01', + endsAtText: '2026-05-31', + }, creationTypes: [ { id: 'rpg', @@ -286,6 +315,9 @@ const testCreationEntryConfig = { visible: true, open: true, sortOrder: 10, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -297,6 +329,9 @@ const testCreationEntryConfig = { visible: true, open: true, sortOrder: 30, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -308,6 +343,9 @@ const testCreationEntryConfig = { visible: true, open: true, sortOrder: 40, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -319,6 +357,9 @@ const testCreationEntryConfig = { visible: true, open: true, sortOrder: 45, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -330,6 +371,9 @@ const testCreationEntryConfig = { visible: false, open: true, sortOrder: 50, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -341,6 +385,9 @@ const testCreationEntryConfig = { visible: false, open: false, sortOrder: 60, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -352,6 +399,9 @@ const testCreationEntryConfig = { visible: true, open: false, sortOrder: 70, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -363,6 +413,9 @@ const testCreationEntryConfig = { visible: false, open: true, sortOrder: 80, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, { @@ -374,6 +427,9 @@ const testCreationEntryConfig = { visible: true, open: true, sortOrder: 90, + categoryId: 'recent', + categoryLabel: '最近创作', + categorySortOrder: 10, updatedAtMicros: 1, }, ], @@ -3345,81 +3401,80 @@ test('create tab shows template tabs and embeds puzzle form by default', async ( await openCreateTemplateHub(user); - expect(screen.getByRole('tablist', { name: '选择模板' })).toBeTruthy(); - expect(screen.getByRole('tablist', { name: '选择模板' }).className).toContain( + expect(screen.getByRole('tablist', { name: '玩法模板分类' })).toBeTruthy(); + expect( + screen.getByRole('tablist', { name: '玩法模板分类' }).className, + ).toContain( 'scroll-px-3', ); expect( - screen.getByRole('tab', { name: '拼图' }).getAttribute('aria-selected'), + screen.getByRole('tab', { name: '最近创作' }).getAttribute('aria-selected'), ).toBe('true'); expect( - screen.getByRole('tab', { name: '拼图' }).querySelector('img')?.src, - ).toContain('/creation-type-references/puzzle.webp'); - expect( - screen.getByRole('tab', { name: '文字冒险' }).querySelector('img')?.src, - ).toContain('/creation-type-references/rpg.webp'); - expect( - screen.getByRole('tab', { name: '抓大鹅' }).querySelector('img')?.src, - ).toContain('/creation-type-references/match3d.webp'); - expect( - screen.getByRole('tab', { name: '汪汪声浪' }).querySelector('img')?.src, - ).toContain('/creation-type-references/bark-battle.webp'); - expect( - screen.getByRole('tab', { name: '宝贝识物' }).querySelector('img')?.src, - ).toContain('/child-motion-demo/picture-book-grass-stage.png'); - expect( - screen.getByRole('tab', { name: '拼图' }).querySelector('.text-white'), + await findCreationTypeButton('拼图'), ).toBeTruthy(); expect( - screen.getByRole('tab', { name: '拼图' }).querySelector('.text-inherit'), + await findCreationTypeButton('文字冒险'), + ).toBeTruthy(); + expect( + await findCreationTypeButton('抓大鹅'), + ).toBeTruthy(); + expect( + await findCreationTypeButton('汪汪声浪'), + ).toBeTruthy(); + expect( + await findCreationTypeButton('宝贝识物'), + ).toBeTruthy(); + expect( + queryCreationTypeButton('智能创作'), ).toBeNull(); + expect( + screen + .getByRole('tab', { name: '最近创作' }) + .querySelector('[class*="bg-[#d9793f]"]'), + ).toBeTruthy(); expect(screen.queryByRole('button', { name: /智能创作/u })).toBeNull(); expect(screen.queryByPlaceholderText('问一问陶泥儿')).toBeNull(); expect(screen.queryByRole('button', { name: /角色扮演/u })).toBeNull(); - expect(screen.queryByRole('tab', { name: /方洞挑战/u })).toBeNull(); - expect(screen.queryByRole('tab', { name: '视觉小说' })).toBeNull(); - expect(screen.getByRole('tab', { name: /抓大鹅/u })).toBeTruthy(); - expect(screen.getByRole('tab', { name: /汪汪声浪/u })).toBeTruthy(); - expect(screen.getByRole('tab', { name: /宝贝识物/u })).toBeTruthy(); expect(createRpgCreationSession).not.toHaveBeenCalled(); expect(match3dCreationClient.createSession).not.toHaveBeenCalled(); expect(createPuzzleAgentSession).not.toHaveBeenCalled(); }); -test('create tab switches match3d into the embedded entry form', async () => { +test('create tab opens match3d entry form from the template card', async () => { const user = userEvent.setup(); render(); await openCreateTemplateHub(user); - await user.click(screen.getByRole('tab', { name: '抓大鹅' })); + await user.click(await findCreationTypeButton('抓大鹅')); - expect( - screen.getByRole('tab', { name: '抓大鹅' }).getAttribute('aria-selected'), - ).toBe('true'); expect(await screen.findByText('抓大鹅工作区:missing-session')).toBeTruthy(); expect(createPuzzleAgentSession).not.toHaveBeenCalled(); expect(match3dCreationClient.createSession).not.toHaveBeenCalled(); }); -test('create tab switches bark battle into the embedded config form', async () => { +test('create tab opens puzzle entry form from the template card', async () => { const user = userEvent.setup(); render(); await openCreateTemplateHub(user); - await user.click(screen.getByRole('tab', { name: '汪汪声浪' })); + await user.click(await findCreationTypeButton('拼图')); + + expect(await screen.findByText('拼图工作区:missing-session')).toBeTruthy(); + expect(createPuzzleAgentSession).not.toHaveBeenCalled(); +}); + +test('create tab opens bark battle entry form from the template card', async () => { + const user = userEvent.setup(); + + render(); + + await openCreateTemplateHub(user); + await user.click(await findCreationTypeButton('汪汪声浪')); - expect( - screen.getByRole('tab', { name: '汪汪声浪' }).getAttribute('aria-selected'), - ).toBe('true'); expect(await screen.findByText('汪汪声浪配置表单')).toBeTruthy(); - expect(screen.getByTestId('bark-battle-editor-back-state').textContent).toBe( - 'back-hidden', - ); - expect(screen.getByTestId('bark-battle-editor-title-state').textContent).toBe( - 'title-hidden', - ); expect(screen.queryByText('汪汪声浪运行态')).toBeNull(); expect(createBarkBattleDraft).not.toHaveBeenCalled(); expect(publishBarkBattleWork).not.toHaveBeenCalled(); @@ -3431,7 +3486,7 @@ test('bark battle draft result can test before publish and publish to work detai render(); await openCreateTemplateHub(user); - await user.click(screen.getByRole('tab', { name: '汪汪声浪' })); + await user.click(await findCreationTypeButton('汪汪声浪')); await user.click(await screen.findByRole('button', { name: '生成草稿' })); expect(createBarkBattleDraft).toHaveBeenCalledWith({ @@ -3525,7 +3580,7 @@ test('bark battle form checks mud points before creating image assets', async () render(); await openCreateTemplateHub(user); - await user.click(screen.getByRole('tab', { name: '汪汪声浪' })); + await user.click(await findCreationTypeButton('汪汪声浪')); await user.click(await screen.findByRole('button', { name: '生成草稿' })); expect( @@ -3547,7 +3602,7 @@ test('bark battle draft is visible in draft shelf while image assets are generat render(); await openCreateTemplateHub(user); - await user.click(screen.getByRole('tab', { name: '汪汪声浪' })); + await user.click(await findCreationTypeButton('汪汪声浪')); await user.click(await screen.findByRole('button', { name: '生成草稿' })); expect(await screen.findByText('自动生成素材')).toBeTruthy(); @@ -3596,7 +3651,7 @@ test('published bark battle stays visible when refresh temporarily returns only render(); await openCreateTemplateHub(user); - await user.click(screen.getByRole('tab', { name: '汪汪声浪' })); + await user.click(await findCreationTypeButton('汪汪声浪')); await user.click(await screen.findByRole('button', { name: '生成草稿' })); await waitFor(() => { expect(updateBarkBattleDraftConfig).toHaveBeenCalledWith( @@ -3811,7 +3866,17 @@ test('persisted generating match3d draft opens generation progress after refresh 'match3d-session-generating', ); }); - expect(await screen.findByText('抓大鹅草稿生成进度')).toBeTruthy(); + expect( + await screen.findByRole('progressbar', { + name: '抓大鹅草稿生成进度', + }), + ).toBeTruthy(); + expect( + screen + .getByRole('progressbar', { name: '抓大鹅草稿生成进度' }) + .getAttribute('aria-valuenow'), + ).toBe('0'); + expect(screen.getByText('0%')).toBeTruthy(); expect(screen.queryByText('抓大鹅结果页')).toBeNull(); expect(getMatch3DWorkDetail).not.toHaveBeenCalledWith( 'match3d-profile-generating', @@ -4514,7 +4579,9 @@ test('match3d result back returns to platform creation page', async () => { expect(await screen.findByText('抓大鹅结果页')).toBeTruthy(); await user.click(screen.getByRole('button', { name: '返回' })); - expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy(); + expect( + await screen.findByRole('tablist', { name: '玩法模板分类' }), + ).toBeTruthy(); expect(screen.queryByText('抓大鹅结果页')).toBeNull(); }); @@ -6788,7 +6855,9 @@ test('puzzle draft result back button returns to creation hub', async () => { await user.click(screen.getByRole('button', { name: '返回' })); - expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy(); + expect( + await screen.findByRole('tablist', { name: '玩法模板分类' }), + ).toBeTruthy(); expect(await screen.findByText('拼图工作区:missing-session')).toBeTruthy(); expect( screen.queryByText('雨夜里有一只会发光的猫站在遗迹台阶上。'), @@ -6825,14 +6894,15 @@ test('persisted generating puzzle draft opens generation progress after refresh' }, ], }); - vi.mocked(getPuzzleAgentSession).mockResolvedValueOnce({ - session: buildMockPuzzleAgentSession({ - sessionId: 'puzzle-session-generating', - stage: 'collecting_anchors', - progressPercent: 42, - lastAssistantReply: '正在生成拼图草稿。', - updatedAt: '2026-05-18T12:00:00.000Z', - }), + const persistedGeneratingPuzzleSession = buildMockPuzzleAgentSession({ + sessionId: 'puzzle-session-generating', + stage: 'collecting_anchors', + progressPercent: 88, + lastAssistantReply: '正在生成拼图草稿。', + updatedAt: '2026-05-18T12:00:00.000Z', + }); + vi.mocked(getPuzzleAgentSession).mockResolvedValue({ + session: persistedGeneratingPuzzleSession, }); render(); @@ -6845,7 +6915,19 @@ test('persisted generating puzzle draft opens generation progress after refresh' 'puzzle-session-generating', ); }); - expect(await screen.findByText('拼图草稿生成进度')).toBeTruthy(); + expect( + await screen.findByRole('progressbar', { + name: '拼图草稿生成进度', + }), + ).toBeTruthy(); + expect( + Number( + screen + .getByRole('progressbar', { name: '拼图草稿生成进度' }) + .getAttribute('aria-valuenow'), + ), + ).toBe(0); + expect(screen.getByText('0%')).toBeTruthy(); expect(screen.queryByText('拼图结果页')).toBeNull(); }); @@ -7844,7 +7926,9 @@ test('running custom world draft generation can return to creation center with s expect(await screen.findByText('世界草稿生成进度')).toBeTruthy(); await user.click(screen.getByRole('button', { name: '返回创作中心' })); - expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy(); + expect( + await screen.findByRole('tablist', { name: '玩法模板分类' }), + ).toBeTruthy(); await openDraftHub(user); expect(await screen.findByText('潮雾列岛')).toBeTruthy(); @@ -8892,7 +8976,9 @@ test('agent draft result back button returns to creation hub without syncing res await user.click(screen.getByRole('button', { name: /返回创作/u })); await waitFor(() => { - expect(screen.getByRole('tablist', { name: '选择模板' })).toBeTruthy(); + expect( + screen.getByRole('tablist', { name: '玩法模板分类' }), + ).toBeTruthy(); }); expect( @@ -9215,14 +9301,16 @@ test('manual tab switch is preserved after platform bootstrap requests finish', render(); await clickFirstButtonByName(user, '创作'); - expect(await screen.findByRole('tablist', { name: '选择模板' })).toBeTruthy(); + expect( + await screen.findByRole('tablist', { name: '玩法模板分类' }), + ).toBeTruthy(); resolveGalleryRequest([]); await waitFor(() => { expect( within(getPlatformTabPanel('create')).getByRole('tablist', { - name: '选择模板', + name: '玩法模板分类', }), ).toBeTruthy(); }); diff --git a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx index 6e107061..a7c4e5a4 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx @@ -413,6 +413,11 @@ const originalUserAgent = navigator.userAgent; const originalMaxTouchPoints = navigator.maxTouchPoints; const originalRequestAnimationFrame = window.requestAnimationFrame; const originalCancelAnimationFrame = window.cancelAnimationFrame; +const DEFAULT_PROFILE_CREATED_AT = '2026-04-01T00:00:00.000Z'; + +function buildFreshProfileCreatedAt() { + return new Date().toISOString(); +} function dispatchPointerEvent( target: HTMLElement, @@ -670,6 +675,33 @@ function mockWechatMobileLayout() { }); } +function mockNarrowMobileLayout() { + Object.defineProperty(navigator, 'userAgent', { + configurable: true, + value: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit Mobile', + }); + Object.defineProperty(window, 'matchMedia', { + configurable: true, + writable: true, + value: vi.fn().mockImplementation((query: string) => { + const normalizedQuery = query.replace(/\s/g, ''); + return { + matches: + normalizedQuery.includes('max-width:767px') || + normalizedQuery.includes('max-width:768px'), + media: query, + onchange: null, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + addListener: vi.fn(), + removeListener: vi.fn(), + dispatchEvent: vi.fn(), + }; + }), + }); +} + function renderProfileView( onRechargeSuccess = vi.fn(), profileDashboardOverrides: Partial< @@ -690,7 +722,7 @@ function renderProfileView( loginMethod: 'password', bindingStatus: 'active', wechatBound: false, - createdAt: new Date().toISOString(), + createdAt: DEFAULT_PROFILE_CREATED_AT, ...userOverrides, }, canAccessProtectedData: true, @@ -1056,7 +1088,7 @@ afterEach(() => { loginMethod: 'password', bindingStatus: 'active', wechatBound: false, - createdAt: new Date().toISOString(), + createdAt: DEFAULT_PROFILE_CREATED_AT, }); mockQrCodeToDataUrl.mockResolvedValue('data:image/png;base64,QR'); mockRedirectToPaymentUrl.mockReset(); @@ -1094,7 +1126,9 @@ test('opens wallet ledger modal from narrative coin card', async () => { const user = userEvent.setup(); renderProfileView(); - await user.click(screen.getByRole('button', { name: /泥点\s*0/u })); + await user.click( + screen.getByRole('button', { name: /泥点余额\s*0/u }), + ); expect(await screen.findByText('泥点账单')).toBeTruthy(); expect(mockGetRpgProfileWalletLedger).toHaveBeenCalledTimes(1); @@ -1760,19 +1794,21 @@ test('profile native qr confirmation refreshes only after server reports paid', expect(onRechargeSuccess).toHaveBeenCalledTimes(1); }); -test('non-wechat profile shows reward code instead of recharge entry', async () => { +test('non-wechat profile opens reward code from recharge-shaped entry', async () => { const user = userEvent.setup(); renderProfileView(); const shortcutRegion = screen.getByRole('region', { name: '常用功能' }); expect( - within(shortcutRegion).queryByRole('button', { name: /充值/u }), - ).toBeNull(); + within(shortcutRegion).getByRole('button', { name: /泥点充值/u }), + ).toBeTruthy(); expect( within(shortcutRegion).getByRole('button', { name: /兑换码/u }), ).toBeTruthy(); - await user.click(within(shortcutRegion).getByRole('button', { name: /兑换码/u })); + await user.click( + within(shortcutRegion).getByRole('button', { name: /泥点充值/u }), + ); expect(await screen.findByPlaceholderText('输入兑换码')).toBeTruthy(); expect(mockGetRpgProfileRechargeCenter).not.toHaveBeenCalled(); }); @@ -1821,7 +1857,7 @@ test('profile played works card shows count unit', () => { }); const playedCard = screen.getByRole('button', { - name: /玩过\s*1个/u, + name: /已玩游戏数量\s*1个/u, }); expect(within(playedCard).getByText('1个')).toBeTruthy(); @@ -1832,18 +1868,120 @@ test('profile stats cards are centered without update timestamp', () => { updatedAt: '2026-05-03T08:01:00Z', }); - const walletCard = screen.getByRole('button', { name: /泥点\s*0/u }); - const playTimeCard = screen.getByRole('button', { name: /游戏时长/u }); - const playedCard = screen.getByRole('button', { name: /玩过\s*0个/u }); + const walletCard = screen.getByRole('button', { + name: /泥点余额\s*0/u, + }); + const playTimeCard = screen.getByRole('button', { name: /游戏时长|累计游戏时长/u }); + const playedCard = screen.getByRole('button', { name: /已玩游戏数量\s*0个/u }); for (const card of [walletCard, playTimeCard, playedCard]) { - expect(card.className).toContain('items-center'); - expect(card.className).toContain('justify-center'); + expect(card.className).toContain('platform-profile-stat-card'); expect(card.className).toContain('text-center'); } expect(screen.queryByText(/更新于/u)).toBeNull(); }); +test('mobile profile page matches the reference layout sections', async () => { + mockWechatMobileLayout(); + + const { container } = renderProfileView(vi.fn(), { + walletBalance: 70, + totalPlayTimeMs: 0, + playedWorldCount: 0, + }, { createdAt: buildFreshProfileCreatedAt() }); + + const profilePage = container.querySelector('.platform-profile-page'); + expect(profilePage).toBeTruthy(); + expect(profilePage?.classList.contains('platform-page-stage')).toBe(true); + expect(profilePage?.querySelector('.platform-profile-scene-decor')).toBeTruthy(); + expect(profilePage?.classList.contains('platform-profile-page')).toBe(true); + expect(profilePage?.getAttribute('style') ?? '').not.toContain('overflow: hidden'); + + const membershipCard = screen.getByRole('button', { name: '查看权益' }); + expect(membershipCard.className).toContain('platform-profile-membership-card'); + expect( + within(membershipCard).getByText('普通用户').className, + ).toContain('platform-profile-membership-card__title'); + expect(within(membershipCard).getByText('普通用户')).toBeTruthy(); + expect(within(membershipCard).getByText('升级会员,享专属特权与福利')).toBeTruthy(); + + const statPanel = screen.getByRole('region', { name: '我的数据' }); + expect(statPanel.className).toContain('platform-profile-stats-panel'); + expect(statPanel.querySelector('.platform-profile-stats-grid')).toBeTruthy(); + expect(within(statPanel).getByRole('button', { name: /泥点余额\s*70/u })).toBeTruthy(); + expect(within(statPanel).getByRole('button', { name: /累计游戏时长\s*0小时/u })).toBeTruthy(); + expect(within(statPanel).getByRole('button', { name: /已玩游戏数量\s*0个/u })).toBeTruthy(); + expect( + within(statPanel).getByRole('button', { name: /泥点余额\s*70/u }).className, + ).toContain('platform-profile-stat-card'); + + const dailyTask = screen.getByRole('button', { name: /每日任务/u }); + expect(dailyTask.className).toContain('platform-profile-daily-task-card'); + expect(dailyTask.querySelector('.platform-profile-daily-task-card__title')).toBeTruthy(); + expect(dailyTask.querySelector('.platform-profile-daily-task-card__desc')).toBeTruthy(); + expect(dailyTask.querySelector('.platform-profile-daily-task-card__progress')).toBeTruthy(); + expect(dailyTask.textContent).toContain('完成任务可领取 10 泥点'); + expect(within(dailyTask).getByText('0 / 1')).toBeTruthy(); + + const shortcutRegion = screen.getByRole('region', { name: '常用功能' }); + expect( + shortcutRegion.querySelector('.platform-profile-shortcut-grid'), + ).toBeTruthy(); + expect( + shortcutRegion.querySelectorAll('.platform-profile-shortcut-button'), + ).toHaveLength(5); + expect( + shortcutRegion + .querySelector('.platform-profile-shortcut-grid') + ?.classList.contains('platform-profile-shortcut-grid'), + ).toBe(true); + for (const label of [ + '泥点充值', + '邀请好友', + '兑换码', + '玩家社区', + '反馈与建议', + ]) { + expect( + within(shortcutRegion).getByRole('button', { name: new RegExp(label, 'u') }), + ).toBeTruthy(); + } + + const settingsRegion = screen.getByRole('region', { name: '设置入口' }); + for (const label of ['主题设置', '账号与安全', '通用设置']) { + expect( + within(settingsRegion).getByRole('button', { name: new RegExp(label, 'u') }), + ).toBeTruthy(); + } + + const secondaryShortcuts = screen.getByRole('region', { + name: '次级入口', + }); + expect( + within(secondaryShortcuts).getByRole('button', { name: /存档/u }), + ).toBeTruthy(); + expect( + await within(secondaryShortcuts).findByRole('button', { + name: /填邀请码/u, + }), + ).toBeTruthy(); + + const profileHeader = profilePage?.querySelector('.platform-profile-header'); + expect(profileHeader).toBeTruthy(); + expect(profileHeader?.querySelector('.platform-profile-header__identity-row')).toBeTruthy(); + expect(profileHeader?.querySelector('.platform-profile-header__name')).toBeTruthy(); + expect(profileHeader?.querySelector('.platform-profile-header__code')).toBeTruthy(); + + const legalRegion = screen.getByRole('region', { name: '法律信息' }); + expect(legalRegion.className).toContain('platform-profile-legal-strip'); + expect(legalRegion.textContent).toContain('用户协议'); + expect(legalRegion.textContent).toContain('隐私政策'); + expect(legalRegion.textContent).toContain('免责声明'); + expect(legalRegion.textContent).toContain(ICP_RECORD_NUMBER); + expect(legalRegion.textContent).toContain('2026025677'); + expect(legalRegion.querySelector('.platform-profile-legal-strip__link')).toBeTruthy(); +}); + test('desktop account entry uses saved avatar image when available', () => { mockDesktopLayout(); const avatarUrl = 'data:image/png;base64,AAAA'; @@ -1886,27 +2024,33 @@ test('wallet ledger modal shows empty and error states', async () => { mockGetRpgProfileWalletLedger.mockResolvedValueOnce({ entries: [] }); renderProfileView(); - await user.click(screen.getByRole('button', { name: /泥点\s*0/u })); - expect(await screen.findByText('暂无账单记录')).toBeTruthy(); + await user.click(screen.getByRole('button', { name: /泥点余额\s*0/u })); + expect(await screen.findByText('泥点账单')).toBeTruthy(); + await waitFor(() => { + expect(screen.getByText('暂无账单记录')).toBeTruthy(); + }); await user.click(screen.getByLabelText('关闭泥点账单')); mockGetRpgProfileWalletLedger.mockRejectedValueOnce(new Error('加载失败')); - await user.click(screen.getByRole('button', { name: /泥点\s*0/u })); + await user.click(screen.getByRole('button', { name: /泥点余额\s*0/u })); - expect(await screen.findByText('加载失败')).toBeTruthy(); + expect(await screen.findByText('泥点账单')).toBeTruthy(); + await waitFor(() => { + expect(screen.getByText('加载失败')).toBeTruthy(); + }); expect(screen.getByText('重新加载')).toBeTruthy(); }); test('profile invite shortcut shows reward subtitle and invited users', async () => { const user = userEvent.setup(); - renderProfileView(); + renderProfileView(vi.fn(), {}, { createdAt: buildFreshProfileCreatedAt() }); const inviteButton = screen.getByRole('button', { name: /邀请好友/u }); - expect(within(inviteButton).getByText('双方得30')).toBeTruthy(); + expect(within(inviteButton).getByText('双方得 30 泥点')).toBeTruthy(); const communityButton = screen.getByRole('button', { name: /玩家社区/u }); - expect(within(communityButton).getByText('每日领福利')).toBeTruthy(); + expect(within(communityButton).getByText('交流心得 领取福利')).toBeTruthy(); await user.click(inviteButton); @@ -1922,21 +2066,25 @@ test('profile invite shortcut shows reward subtitle and invited users', async () }); test('profile redeem invite shortcut sits between invite and community for fresh accounts', async () => { - renderProfileView(); + renderProfileView( + vi.fn(), + {}, + { createdAt: buildFreshProfileCreatedAt() }, + ); const inviteButton = screen.getByRole('button', { name: /邀请好友/u }); const redeemButton = await screen.findByRole('button', { name: /填邀请码/u, }); const communityButton = screen.getByRole('button', { name: /玩家社区/u }); + const secondaryShortcuts = screen.getByRole('region', { + name: '次级入口', + }); + expect(inviteButton).toBeTruthy(); + expect(communityButton).toBeTruthy(); expect( - inviteButton.compareDocumentPosition(redeemButton) & - Node.DOCUMENT_POSITION_FOLLOWING, - ).toBeTruthy(); - expect( - redeemButton.compareDocumentPosition(communityButton) & - Node.DOCUMENT_POSITION_FOLLOWING, + within(secondaryShortcuts).getByRole('button', { name: /填邀请码/u }), ).toBeTruthy(); expect(within(redeemButton).getByText('新用户奖励')).toBeTruthy(); }); @@ -2006,7 +2154,11 @@ test('profile redeem invite modal submits code and hides shortcut after success' const user = userEvent.setup(); const onRechargeSuccess = vi.fn(); - renderProfileView(onRechargeSuccess); + renderProfileView( + onRechargeSuccess, + {}, + { createdAt: buildFreshProfileCreatedAt() }, + ); await user.click(await screen.findByRole('button', { name: /填邀请码/u })); const input = await screen.findByLabelText('邀请码'); @@ -2050,11 +2202,10 @@ test('profile page shows legal entries and ICP record link', async () => { const shortcutRegion = screen.getByRole('region', { name: '常用功能' }); expect( - shortcutRegion.querySelector('.grid')?.className.includes('grid-cols-3'), + shortcutRegion + .querySelector('.platform-profile-shortcut-grid') + ?.classList.contains('platform-profile-shortcut-grid'), ).toBe(true); - expect( - within(shortcutRegion).getByRole('button', { name: /每日任务/u }), - ).toBeTruthy(); expect( within(shortcutRegion).getByRole('button', { name: /邀请好友/u }), ).toBeTruthy(); @@ -2064,6 +2215,24 @@ test('profile page shows legal entries and ICP record link', async () => { expect( within(shortcutRegion).getByRole('button', { name: /反馈/u }), ).toBeTruthy(); + const dailyTask = screen.getByRole('button', { name: /每日任务/u }); + expect(dailyTask).toBeTruthy(); + expect(dailyTask.textContent).toContain('完成任务可领取 10 泥点'); + + const settingsRegion = screen.getByRole('region', { name: '设置入口' }); + expect( + within(settingsRegion).getByRole('button', { name: /存档/u }), + ).toBeTruthy(); + + const secondaryShortcuts = screen.getByRole('region', { + name: '次级入口', + }); + expect( + within(secondaryShortcuts).getByRole('button', { name: /存档/u }), + ).toBeTruthy(); + expect( + within(secondaryShortcuts).queryByRole('button', { name: /填邀请码/u }), + ).toBeNull(); const legalRegion = screen.getByRole('region', { name: '法律信息' }); expect( @@ -2138,6 +2307,83 @@ test('logged in draft bottom tab shows unread marker', () => { expect(draftButton.querySelector('.platform-nav-unread-dot')).toBeTruthy(); }); +test('logged in create tab shows real wallet balance beside the brand', () => { + mockNarrowMobileLayout(); + + const { container } = render( + action(), + openSettingsModal: vi.fn(), + openAccountModal: vi.fn(), + setCurrentUser: vi.fn(), + logout: vi.fn(async () => undefined), + musicVolume: 0.42, + setMusicVolume: vi.fn(), + platformTheme: 'light', + setPlatformTheme: vi.fn(), + isHydratingSettings: false, + isPersistingSettings: false, + settingsError: null, + }} + > + 创作内容
} + /> + , + ); + + const topbar = container.querySelector('.platform-mobile-topbar'); + expect(topbar).toBeTruthy(); + expect( + topbar?.querySelector('.platform-mobile-create-wallet-chip'), + ).toBeTruthy(); + expect(topbar?.textContent).toContain('陶泥儿'); + expect(topbar?.textContent).toContain('1,234泥点'); +}); + test('mobile discover search submits public work code', async () => { const user = userEvent.setup(); const onSearchPublicCode = vi.fn(); @@ -2248,6 +2494,15 @@ test('mobile discover keeps edutainment works in the last dedicated channel only throw new Error('缺少发现面板'); } + const discoverStage = discoverPanel.querySelector( + '.platform-mobile-home-stage', + ); + expect(discoverStage).toBeTruthy(); + expect(discoverStage?.classList.contains('platform-remap-surface')).toBe( + true, + ); + expect(discoverStage?.classList.contains('platform-page-stage')).toBe(false); + const channels = Array.from( discoverPanel.querySelectorAll('.platform-mobile-home-channel'), ).map((button) => button.textContent); @@ -3135,7 +3390,6 @@ test('desktop logged in home syncs mobile home modules without square or latest expect(screen.queryByText('作品广场')).toBeNull(); expect(screen.queryByText('公开作品')).toBeNull(); expect(screen.queryByText('PZ-EPUBLIC1')).toBeNull(); - expect(screen.getAllByText('拼图').length).toBeGreaterThan(0); expect(screen.queryByText('1777110165.990127Z')).toBeNull(); }); diff --git a/src/components/rpg-entry/RpgEntryHomeView.tsx b/src/components/rpg-entry/RpgEntryHomeView.tsx index 8d804fc2..30017806 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.tsx @@ -11,7 +11,7 @@ import { Coins, Compass, Copy, - FileText, + Crown, Gamepad2, GitFork, Heart, @@ -20,9 +20,11 @@ import { Palette, Pencil, Plus, + ScanLine, Search, Settings, Share2, + ShieldCheck, SlidersHorizontal, Sparkles, Star, @@ -45,6 +47,16 @@ import { useState, } from 'react'; +import profileClockImage from '../../../media/profile/_Image (1).png'; +import profileGamepadImage from '../../../media/profile/_Image (2).png'; +import profileStillLifeImage from '../../../media/profile/_Image (3).png'; +import profileCoinsImage from '../../../media/profile/_Image (4).png'; +import profileInviteImage from '../../../media/profile/_Image (5).png'; +import profileGiftImage from '../../../media/profile/_Image (6).png'; +import profileCommunityImage from '../../../media/profile/_Image (7).png'; +import profileFeedbackImage from '../../../media/profile/_Image (8).png'; +import profileMascotImage from '../../../media/profile/_Image (9).png'; +import profilePointImage from '../../../media/profile/_Image.png'; import communityQqQrImage from '../../../media/social-media-group/qq.png'; import communityWechatQrImage from '../../../media/social-media-group/wechat.png'; import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth'; @@ -215,8 +227,12 @@ const MOBILE_PAGE_STAGE_CLASS = 'platform-page-stage platform-remap-surface min-w-0 space-y-4 overflow-hidden pb-2'; const MOBILE_RECOMMEND_PAGE_STAGE_CLASS = 'platform-page-stage min-w-0 space-y-4 overflow-hidden pb-2'; +const MOBILE_DISCOVER_PAGE_STAGE_CLASS = + 'platform-remap-surface min-w-0 space-y-4 overflow-hidden pb-2'; const DESKTOP_PAGE_STAGE_CLASS = 'platform-page-stage platform-remap-surface min-w-0 space-y-5 pb-4'; +const DESKTOP_DISCOVER_PAGE_STAGE_CLASS = + 'platform-remap-surface min-w-0 space-y-5 pb-4'; const DESKTOP_LAYOUT_QUERY = '(min-width: 1024px)'; const PLATFORM_HOME_TABS: PlatformHomeTab[] = [ 'home', @@ -2384,12 +2400,14 @@ function ProfileStatCard({ value, onClick, icon, + imageSrc, }: { cardKey: ProfileDashboardCardKey; label: string; value: string; onClick?: ((cardKey: ProfileDashboardCardKey) => void) | null; icon: ComponentType<{ className?: string }>; + imageSrc?: string; }) { const Icon = icon; @@ -2397,16 +2415,23 @@ function ProfileStatCard({ ); @@ -2426,11 +2451,13 @@ function ProfileShortcutButton({ subLabel, icon, onClick, + imageSrc, }: { label: string; subLabel?: ReactNode; icon: ComponentType<{ className?: string }>; onClick?: (() => void) | null; + imageSrc?: string; }) { const Icon = icon; @@ -2438,16 +2465,20 @@ function ProfileShortcutButton({ + ); +} + +function ProfileSecondaryShortcutButton({ + label, + subLabel, + icon, + onClick, +}: { + label: string; + subLabel?: string; + icon: ComponentType<{ className?: string }>; + onClick: () => void; +}) { + const Icon = icon; + + return ( + + ); +} + function ProfileLegalSection({ onOpenDocument, }: { @@ -2462,33 +2559,21 @@ function ProfileLegalSection({ }) { return (
-
- 法律信息 -
-
+
{LEGAL_DOCUMENTS.map((document, index) => ( ))}
@@ -2496,7 +2581,7 @@ function ProfileLegalSection({ href={ICP_RECORD_URL} target="_blank" rel="noreferrer" - className="mt-3 block text-center text-xs font-semibold text-[var(--platform-text-soft)] transition hover:text-[var(--platform-cool-text)]" + className="platform-profile-legal-strip__record" > {ICP_RECORD_NUMBER} @@ -5369,7 +5454,7 @@ export function RpgEntryHomeView({ ); const mobileDiscoverContent: ReactNode = ( -
+
+
{visibleDiscoverChannels.map((channel) => { const active = discoverChannel === channel.id; @@ -5839,30 +5924,55 @@ export function RpgEntryHomeView({ const savesContent: ReactNode = draftTabContent ?? fallbackDraftContent; const profileContent: ReactNode = ( -
+
{authUi?.user ? ( <> -
-
-
+
+
+ + +
+ +
+
@@ -5877,28 +5987,27 @@ export function RpgEntryHomeView({ } /> -
+
-
+
{authUi.user.displayName}
-
- 陶泥号 {publicUserCode} +
+ 陶泥号: {publicUserCode}
- -
-
-
+ + +
+
{isLoadingDashboard ? ( <> @@ -5944,23 +6054,26 @@ export function RpgEntryHomeView({ <> @@ -5968,23 +6081,26 @@ export function RpgEntryHomeView({ <> @@ -5992,101 +6108,125 @@ export function RpgEntryHomeView({
+ +
-
+
- 领10 - - - } - icon={Star} - onClick={openTaskCenterPanel} - /> - - 0 - ? `${saveEntries.length}个可继续` - : '继续游玩' - } - icon={Archive} - onClick={() => setProfilePopupPanel('saveArchives')} - /> - {showRechargeEntry ? ( - - ) : null} - 双方得30 - - - } + subLabel="双方得 30 泥点" icon={UserPlus} + imageSrc={profileInviteImage} onClick={() => openProfilePopupPanel('invite')} /> - {canShowReferralRedeemShortcut ? ( - openProfilePopupPanel('redeem')} - /> - ) : null} + openProfilePopupPanel('community')} />
-
- + /> + setProfilePopupPanel('saveArchives')} + /> +
+ +
+ 0 + ? `${saveEntries.length}个可继续` + : '继续游玩' + } + icon={Archive} + onClick={() => setProfilePopupPanel('saveArchives')} + /> + {canShowReferralRedeemShortcut ? ( + openProfilePopupPanel('redeem')} + /> + ) : null}
@@ -6566,7 +6706,19 @@ export function RpgEntryHomeView({ {!isMobileRecommendTab ? (
- {!isAuthenticated ? ( + {isAuthenticated && activeTab === 'create' ? ( + + ) : !isAuthenticated ? ( + ) : null} ) : null}
-