主题是一份离线 ZIP 包:HTML/CSS/JS 负责锁屏、桌面背景与挂件,App 图标网格、Dock、翻页和点击行为由原生 Launcher 负责。创作流程:备好资源 → 创作者中心上传 → 实时预览 → 提交审核。按你的情况选一条路线开始:
不会写代码也能出主题:新建主题时选「轻主题」,上传壁纸、逐个替换系统内置 App 的图标即可,manifest 与打包由平台自动完成,与 ZIP 上传共用预览和审核流程。
做完轻主题还想升级成会动的氛围(自绘时钟 / 问候语 / 贴纸动效)?同一套壁纸和图标可以直接复用,见下一节用 AI 生成主题。
想要会动的氛围(自绘时钟 / 问候语 / 贴纸动效)但不想手写代码,走这一节;只换壁纸和图标用上一节的轻主题表单就够了。好主题的质感主要来自壁纸和图标这些美术资源,纯代码画不出来——先按下面的清单把资源备好(自己画、找素材、用生图工具都行,风格完全由你定,只需要符合规格和命名),再把资源清单连同风格描述发给代码 AI,让它拼成主题包。
| 资源 | 路径 / 命名 | 必要性 | 规格与构图 |
|---|---|---|---|
| 壁纸 | assets/wallpaper.png | 强烈建议 | 竖屏 1170×2532 以上。上 1/3 留给时钟 / 问候语(避免放主体元素),最底部约 15% 留给 Dock 托盘 |
| 夜间壁纸 | assets/wallpaper_night.png | 可选 | 与白天版同构图的夜色版;代码里按时间切换(18:00–6:00) |
| App 图标一套 | icons/<appId>.png | 建议 | 256×256 正方形,图形铺满画布、四周不留空白边(真机统一切 22% 圆角,不用预切)。具体有哪些、文件名怎么写见表格下方清单;建议整套统一工艺、每个 App 有可辨识的独立造型 |
| 装饰贴纸 | assets/sticker-*.png | 可选 | 透明底 PNG,给氛围层点缀(角标 / 胶带 / 吉祥物等) |
图标共 18 个,文件名用括号里的 App ID(如 icons/wechat.png):微聊(wechat)、信息(sms)、电话(phone_call)、纪念回忆(memorial_memory)、小号(alt_identity)、Ta的记忆(memory_of_me)、论坛(forum)、钱包(wallet)、拾光购物(shopping_shine)、小月历(xiaoyueli)、灵感(punchline_dict)、Shine指南(shine_guide)、暗网(darknet)、链接微信(wechat_link)、应用商店(store)、主题商店(theme_store)、手机设置(settings)、关闭(close)。完整对照表见 自定义 App 图标;不想画全可以只画常用的,但请放一张 icons/default.png 给没画的 App 兜底,避免个别入口退化成文字 / emoji 图标。
在 Prompt 末尾描述风格、列出你备好的资源文件,AI 会输出 manifest.json / desktop.html / main.js 三个文件。没有资源也能用:资源清单写「无资源」,AI 会全部用 CSS 实现。
你是一名 Shine 虚拟手机「桌面主题」开发者。我会描述想要的主题风格、并告诉你我已准备好哪些美术资源,请输出一个完整的主题包,共 3 个文件:manifest.json、desktop.html、main.js。
输出格式:每个文件前用一行「===== 文件名 =====」分隔,文件内容原样输出;不要用 markdown 代码块包裹,不要任何解释或前后缀。我会把这 3 个文件和资源目录一起压缩成 ZIP 上传。
—— 主题是什么 ——
主题是叠在虚拟手机桌面底层的「氛围层」H5 页面:只画背景氛围、时钟、问候语、装饰贴纸。
App 图标网格、Dock、翻页、点击全部由原生 Launcher 叠在上层负责——严禁自建 App 网格、严禁调用 openApp / saveLayout。
—— 美术资源约定(质感的主要来源,优先用我提供的图)——
- ZIP 里可能有 assets/wallpaper.png(壁纸)、assets/wallpaper_night.png(夜间壁纸,可选)、
assets/sticker-*.png(装饰贴纸)——都用相对路径引用(CSS background-image 或 <img>),
壁纸作全屏背景铺满;有夜间壁纸时用 data.system.time.hour 判断(<6 || >=18 算夜间)切换
- icons/<appId>.png 是 App 图标覆盖图:原生 Launcher 自动读取,不需要在 manifest 或代码里引用
- manifest 的 shell.backdrop_image 填壁纸路径,WebView 首绘前由原生垫底防白屏
- 我没提供的资源不要编造文件名去引用;缺的部分用 CSS 渐变 / 纹理 / 内联 SVG 补
- 禁止任何网络请求:不要 fetch / XHR / 远程图片 / 远程字体 / CDN,所有资源必须在 ZIP 内
—— manifest.json 固定骨架(在此基础上微调,不要改结构;下面的 // 注释是给你的说明,输出的 manifest.json 必须是合法纯 JSON、不能带任何注释)——
{
"schema_version": 3, // 固定 3,不要改
"name": "<主题名>",
"version": "1.0.0",
"entries": { "desktop": "desktop.html" }, // 氛围层入口页,固定
"scripts": ["main.js"], // 平台注入 ThemeSDK 后加载
"shell": {
"status_bar": "light", // 状态栏字色:light=白字(深壁纸) dark=黑字(浅壁纸)
"backdrop_image": "assets/wallpaper.png" // 有壁纸时必填:首绘前原生垫底防白屏
},
"launcher": {
"grid": { "cols": 4, "anchor": { "left": 0.04, "right": 0.04 } },
// 图标网格 4 列,左右各留 4% 边距
"segments": [
{ "type": "zone", "id": "hero", "selector": "[data-theme-zone=\"hero\"]", "fallback_top": 0.3 },
// 量取 hero 占位高度,App 网格从它下方开始
{ "type": "apps", "fill": true } // 剩余空间铺满 App 图标
],
"dock": { "height": 84, "padding_h": 12, "bottom": 0.05 },
// 底部 Dock:高度 / 水平内边距 / 离底比例
"icon_style": "flat", // 图标外观,见下
"overflow_pages": { "grid_top": "full" },// 第2页起网格从顶排
"page_dots": "theme" // 翻页圆点配色跟随主题
}
}
- status_bar 按壁纸明暗选:深壁纸 light(白字)、浅壁纸 dark(黑字)
- icon_style 五选一:plain(原图直出)| flat | polaroid(相纸)| pill | glass,跟主题气质走
- 浅色壁纸请在 launcher 里加 "label": { "color": "#<深色>", "shadow": "light" },否则 App 名默认白字看不清
—— desktop.html 约定 ——
- 只放装饰 DOM;首屏专属顶部信息(时钟 / 问候 / 角色名)包在 <header data-theme-zone="hero"> 里,Launcher 会量它的高度、让 App 网格从它下方开始
- 所有 CSS 内联在 <style> 里;壁纸 / 贴纸用相对路径引用包内图片,其余装饰用 CSS 渐变 / 内联 SVG
- 安全区(重要):Android WebView 的 env(safe-area-inset-*) 恒为 0,顶部容器必须写
padding-top: calc(12px + max(env(safe-area-inset-top, 0px), var(--shine-safe-area-top, 0px)));
- 不要写 <script> 或 <script src>,逻辑全部放 main.js(平台注入 ThemeSDK 后按 manifest.scripts 加载)
—— main.js 约定 ——
(function () {
'use strict';
function render(data) { // dataChanged:首帧 / 角色变化 / 翻页时全量渲染
// 用 data.system.time.hour/minute 画时钟
// data.character.name 是持机人(用户在剧里的面具)名,问候语用它;
// 称呼对方角色用 data.character.worldName,别混用
// system.launcherPage > 0 时把 hero 的 opacity 设 '0'(翻页淡出,纯背景氛围可常显)
ThemeSDK.reportLayout(); // hero 高度 / 显隐变化后必须重报
}
function lightRefresh() { // dataUpdated:每分钟时钟 tick(翻页走 dataChanged),只改 textContent 与显隐
var data = ThemeSDK.getData(); // 这里不要 reportLayout、不要重绘图片
}
ThemeSDK.on('dataChanged', render);
ThemeSDK.on('dataUpdated', lightRefresh);
ThemeSDK.ready(); // 必须调用,否则真机 3 秒内回退原生桌面
ThemeSDK.reportLayout();
})();
- 可用数据:data.system.time { hour, minute }、data.system.launcherPage(0 = 首屏)、
data.character { name: 持机人(用户面具)名, worldName: 对方角色名, worldTitle: 当前世界名(老 App 缺失时用 worldName 兜底)}
- 语法一律 ES5:var / function / 字符串拼接;不要箭头函数、模板字符串、可选链 ?.、解构——旧安卓 WebView 也要能跑
—— 性能红线(真机多为中低端安卓)——
- 动画只用 transform / opacity 的 CSS 过渡或 animation;禁止大面积 backdrop-filter
- 禁止 requestAnimationFrame 循环和 setInterval 高频重绘;时钟更新靠 dataUpdated 推送,不要自己起定时器
- 大图只加载一次(背景铺满即可),不要在 JS 里反复改图片 src
—— 我想要的主题风格 ——
[在这里描述,比如:奶油色手账风,顶部大时钟 + 手写感问候语,壁纸下半有拼贴装饰,图标用相纸风格]
—— 我准备好的资源(已放进 ZIP,按此引用)——
[列出你实际准备的文件,例:
assets/wallpaper.png、assets/wallpaper_night.png、assets/sticker-moon.png、
icons/ 里已放齐 18 张图标。
一张图都没有就写「无资源」,让 AI 全部用 CSS 实现]把 AI 输出的三个文件和 assets/、icons/ 放进同一个文件夹压缩成 ZIP,到新建条目上传,右侧预览翻两页看效果(对照上架前清单自查)。不满意直接在同一段 AI 对话里继续说「时钟再大一点」「换成深色」迭代,再整包替换 ZIP;确认没问题后设预览图、定价并提交审核。
{
"schema_version": 3, // 固定写 3;别升 4,旧版 App 会拒载整包
"name": "拾光", // 主题名(商店与设置页展示)
"version": "3.1.0", // 语义化版本,更新包时递增
"entries": { "desktop": "desktop.html" }, // 氛围层入口页,固定 desktop.html
"scripts": ["main.js"], // 平台注入 ThemeSDK 后依次加载的脚本
"shell": { "status_bar": "dark" }, // 状态栏字色:dark=黑字(浅壁纸) light=白字(深壁纸)
"launcher": { // 原生 Launcher 的排版声明
"grid": { "cols": 4, "anchor": { "left": 0.04, "right": 0.04 } },
// 图标网格 4 列,左右各留 4% 边距
"segments": [ // 首屏自上而下的分段布局
{ "type": "zone", "id": "hero", "selector": "[data-theme-zone=\"hero\"]" },
// zone:量取氛围层 hero 占位的实际高度,网格避开它
{ "type": "apps", "rows": 1 }, // 一排 App 图标
{ "type": "widget", "id": "quote", "entry": "widgets/quote.html",
"span": { "cols": 4, "rows": 2 } }, // 挂件:独立 H5,占 4 列 × 2 行
{ "type": "apps", "fill": true } // 剩余空间铺满 App 图标
],
"overflow_pages": { "grid_top": "full" },// 第2页起网格顶边:full=从顶排 same=与首屏同顶边
"dock": { "height": 84, "padding_h": 12, "style": "shelf", "bottom": 0.065 },
// 底部 Dock:高度 / 水平内边距 / 样式 / 离底比例
"icon_style": "polaroid", // 图标外观:plain|flat|polaroid|pill|glass
"icon_tokens": { "tilt": "random", "frame_radius": 7, "shadow": "warm" },
// 外观微调:随机倾斜 / 相框圆角 / 暖色阴影
"page_dots": "theme" // 翻页圆点配色跟随主题
}
}注释仅为讲解——manifest.json 是纯 JSON,实际文件里带 // 会解析失败,复制后请去掉。
plain — 原图直出:无外壳,与系统默认桌面图标一致;轻主题缺省flat — 扁平圆角:默认,浅色圆角底polaroid — 相纸:白框+暖影,适合墙面主题pill — 药丸:渐变圆角块,偏 iOS 风glass — 磨砂玻璃:半透明描边,适合深色壁纸新版 App 支持一组可选的 launcher token:App 名字色(浅底主题救白字)、图标外壳 icon_chrome / 小程序补外圈的 icon_chrome_fallback、Dock 托盘配色与标签开关。直接写进 schema 3 包即可(老 App 自动忽略;不要把 schema_version 升到 4,旧版 App 会拒载整包)。
// 以下字段均可选,直接写进 schema 3 包的 launcher 块;老 App 自动忽略
"launcher": {
"icon_style": "plain", // 新增 plain:原图直出(对齐默认桌面)
"label": { "color": "#5C4038", "shadow": "light" },
// App 名字色 + 阴影 none|light|dark|auto(浅底主题救白字)
"icon_chrome": { // 图片外壳:背板+覆层,无差别套所有 App
"back": "chrome/back.png", "upon": "chrome/upon.png",
"scale": 0.72, "radius": 10
},
"icon_chrome_fallback": { // 只套「主题没画图标」的 App——
"back": "chrome/back.png", "scale": 0.62 // 给小程序补统一外圈用这个,别用 icon_chrome(会卡中卡)
},
"dock": {
"style": "glass", // 老 App 的降级兜底样式
"labels": false, // Dock 是否显示 App 名
"surface": { "color": "#FFF9F2", "border_color": "#D8C2AE", "border_width": 1,
"radius": 18, "inset_h": 4, "padding_v": 8, "shadow": "warm" },
"background": "chrome/dock.png" // 图片托盘底板,优先于 surface
}
}在主题 ZIP 根目录新建 icons/,放入以 App ID 命名的图片,例如icons/wechat.png。无需在 manifest 中注册;原生 Launcher 会自动匹配。推荐位图(PNG/WebP/JPG,建议 256×256 正方形),真机统一切 22% 圆角、按显示尺寸解码,效果稳定;SVG 也支持但小尺寸下细节易糊,仅建议简单几何造型使用。同名多格式时按 png → webp → jpg → svg 取第一个。
| App 名称 | App ID | 图标文件 |
|---|---|---|
| 微聊 | wechat | icons/wechat.png |
| 信息 | sms | icons/sms.png |
| 电话 | phone_call | icons/phone_call.png |
| 纪念回忆 | memorial_memory | icons/memorial_memory.png |
| 小号 | alt_identity | icons/alt_identity.png |
| Ta的记忆 | memory_of_me | icons/memory_of_me.png |
| 论坛 | forum | icons/forum.png |
| 钱包 | wallet | icons/wallet.png |
| 拾光购物 | shopping_shine | icons/shopping_shine.png |
| 小月历 | xiaoyueli | icons/xiaoyueli.png |
| 灵感 | punchline_dict | icons/punchline_dict.png |
| Shine指南 | shine_guide | icons/shine_guide.png |
| 暗网 | darknet | icons/darknet.png |
| 链接微信 | wechat_link | icons/wechat_link.png |
| 入口名称 | 系统 ID | 图标文件 |
|---|---|---|
| 应用商店 | __store__ | icons/store.png |
| 主题商店 | __theme_store__ | icons/theme_store.png |
| 手机设置 | __settings__ | icons/settings.png |
| 关闭 | __close__ | icons/close.png |
匹配顺序:具体 App ID → icons/default.png(兜底,套用于未单独指定的系统 / 内置 App)→ 应用自带图标 → 系统图标或 Emoji。小程序(MiniApp)例外:始终用应用自己上传的图标、不参与主题化,default 也不会套在它头上。
// v3 氛围层 ThemeSDK(desktop.html + main.js)
// 1. 装饰占位 — 背景氛围全屏常显
// <div data-theme-zone="hero">...</div>
ThemeSDK.ready();
ThemeSDK.reportLayout();
// 2. 订阅数据 + 翻页淡出首屏专属顶部信息(问候/时钟/日期条)
// system.launcherPage:当前 Launcher 页码(0 = 首屏)
ThemeSDK.on('dataChanged', function (data) {
renderAtmosphere(data); // system.time / character
var page = (data.system && data.system.launcherPage) || 0;
document.querySelector('[data-theme-zone="hero"]').style.opacity =
page > 0 ? '0' : '1'; // 淡出避免透在第2页网格顶部;纯背景氛围(渐变/纹理)可常显
ThemeSDK.reportLayout(); // hero 高度/显隐变化后必须重报
});
// 3. 安全区(重要):平台自动注入 --shine-safe-area-{top,right,bottom,left}
// Android WebView 的 env(safe-area-inset-*) 恒为 0,顶部 UI 必须用 max() 兜底:
// padding-top: calc(12px + max(env(safe-area-inset-top, 0px),
// var(--shine-safe-area-top, 0px)));
// 4. 音乐能力(可选):氛围层可以自己画音乐胶囊/唱片机,想画在哪都行
//
// 数据:data.music = { hasTracks, isPlaying, isBuffering,
// trackId, title, artist, coverUrl }
// 低频推送(曲目/播放态变化时);不含播放进度,动效用 isPlaying 切 CSS。
//
// 点击:氛围层不直接收点击(保持纯展示、不挡翻页手势)。
// 给可点元素标 data-theme-action,平台按 reportLayout 上报的位置
// 盖透明原生点击层(仅第 0 页生效):
// <div class="chip-prev" data-theme-action="musicPrev"></div> <!-- 上一首 -->
// <div class="chip-play" data-theme-action="musicToggle"></div> <!-- 播放/暂停 -->
// <div class="chip-next" data-theme-action="musicNext"></div> <!-- 下一首 -->
// <div class="chip-info" data-theme-action="musicOpen"></div> <!-- 进播放页 -->
// 注意:两个 action 元素尽量不嵌套;元素显隐/移动后必须重调
// ThemeSDK.reportLayout(),否则点击区域停留在旧位置。
//
// 降级契约(必须处理):老版本 App / 角色机 / 用户关闭音乐卡时
// data.music 为 undefined —— 此时把音乐 UI 退化为纯装饰(点击层不会挂载):
ThemeSDK.on('dataChanged', function (data) {
var m = data.music;
var chip = document.getElementById('music-chip');
chip.className = !m ? 'chip off' : (m.isPlaying ? 'chip playing' : 'chip');
document.getElementById('chip-title').textContent =
!m ? '今日歌单' : (m.hasTracks ? (m.title || '音乐') : '添加音乐');
});
// 5. 世界能力(可选):切换故事 / 查看 TA 的手机也能画进氛围里
//
// 点击:同 data-theme-action 机制(透明原生点击层,仅第 0 页生效):
// <div id="world-card" data-theme-action="worldSwitch"></div> <!-- 切换故事 -->
// <div id="ta-phone" data-theme-action="taPhoneOpen"></div> <!-- 看 TA 的手机 -->
// 当前世界名读 character.worldTitle(老 App 用 worldName 兜底)。
// 主题画了 worldSwitch 入口后,原生「切换世界」悬浮球在首屏自动让位
// (第 1 页起悬浮球会回来兜底);没画则悬浮球全程兜底,不用担心入口丢失。
// 隐藏入口必须用 display:none(或移出文档流)并重调 reportLayout()——
// visibility:hidden / opacity:0 的元素位置仍会上报,会留下隐形可点区域。
//
// 降级契约(必须处理):老版本 App 不发 system.actions ——
// 数组里没有对应 action 时把入口画成纯装饰或隐藏(点击层不会挂载):
ThemeSDK.on('dataChanged', function (data) {
var acts = (data.system && data.system.actions) || [];
document.getElementById('world-card').className =
acts.indexOf('worldSwitch') >= 0 ? 'world-card' : 'world-card off';
});
// App 网格由 Flutter 原生叠在氛围之上;第2页图标从顶排,背景仍可见
// 禁止 openApp / saveLayout / 自建 App 网格App 列表字段:id / name / kind / icon / iconUrl / iconEmoji / badge。系统入口 id:__store__、__theme_store__、__settings__、__close__。初始化后必须调用 ThemeSDK.ready(),否则真机 3 秒内回退原生桌面。
// widgets/*.html — ThemeWidgetSDK(平台自动注入)
ThemeWidgetSDK.ready(function (data) {
var name = (data.character && data.character.name) || '访客';
document.getElementById('title').textContent = name;
});
// 数据:{ system, character },与氛围层相同字段
// 禁止:fetch / openApp / 外链
// manifest:{ "type":"widget", "entry":"widgets/quote.html",
// "span": { "cols": 4, "rows": 2 } }
//
// 桌面音乐不走挂件:氛围层自绘 + data.music + data-theme-action
// (见 ThemeSDK 篇第 4 节)。