主題是一份離線 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 / 字串串接;不要箭頭函式、樣板字串、可選串連 ?.、解構——舊 Android WebView 也要能跑
—— 效能紅線(實機多為中低階 Android)——
- 動畫只用 transform / opacity 的 CSS transition 或 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 節)。