テーマはオフラインの ZIP パッケージです:ロック画面・デスクトップ背景・ウィジェットは HTML/CSS/JS が担当し、アプリアイコンのグリッド、Dock、ページ送り、タップ動作はネイティブランチャーが担当します。制作フロー:素材を準備 → クリエイターセンターにアップロード → リアルタイムプレビュー → 審査に提出。状況に合わせてルートを選んでください:
壁紙(アイコンは任意)を用意して簡易テーマフォームから直接アップロードすれば、プラットフォームが自動でパッケージ化します。動く雰囲気が欲しければ AI にテーマパッケージを書かせましょう。
スタータープロジェクトをダウンロードして H5 の雰囲気レイヤーを編集し、manifest でレイアウトを宣言、ThemeSDK でデータを購読して、ZIP に固めてアップロード・プレビューします。
コードが書けなくてもテーマを出せます:テーマ新規作成時に「簡易テーマ」を選び、壁紙をアップロードして内蔵アプリのアイコンを 1 つずつ差し替えるだけ。manifest とパッケージ化はプラットフォームが自動で行い、プレビューと審査は ZIP アップロードと共通です。
簡易テーマを作ったあとで動く雰囲気(自作時計 / あいさつ / ステッカーアニメ)に格上げしたくなったら?同じ壁紙とアイコンをそのまま再利用できます。次のセクションAI でテーマ生成をご覧ください。
動く雰囲気(自作時計 / あいさつ / ステッカーアニメ)が欲しいけれどコードは書きたくない場合はこのセクションへ。壁紙とアイコンを替えるだけなら前セクションの簡易テーマフォームで十分です。良いテーマの質感は主に壁紙やアイコンなどのアート素材から生まれます——コードだけでは描けません。まず下のチェックリストに沿って素材を準備し(自作・素材探し・画像生成ツール、どれでも OK。スタイルは自由、規格と命名だけ守ってください)、素材リストとスタイルの説明をコーディング 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 個。ファイル名は括弧内のアプリ ID を使います(例 icons/wechat.png):WeTalk(wechat)、メッセージ(sms)、電話(phone_call)、思い出(memorial_memory)、サブアカ(alt_identity)、TAの記憶(memory_of_me)、フォーラム(forum)、ウォレット(wallet)、Shine ショッピング(shopping_shine)、カレンダー(xiaoyueli)、インスピレーション(punchline_dict)、Shine ガイド(shine_guide)、ダークウェブ(darknet)、WeChat 連携(wechat_link)、アプリストア(store)、テーマストア(theme_store)、設定(settings)、閉じる(close)。完全な対応表は アプリアイコンのカスタム を参照。全部描く必要はなく、よく使うものだけでも構いませんが、icons/default.png を 1 枚入れて未対応アプリのフォールバックにし、一部の入口がテキスト / 絵文字アイコンに退化しないようにしてください。
プロンプトの末尾にスタイルの説明と準備した素材ファイルの一覧を書くと、AI が manifest.json / desktop.html / main.js の 3 ファイルを出力します。素材なしでも使えます:素材リストに「素材なし」と書けば、AI がすべて CSS で実装します。
あなたは Shine のバーチャルスマホ「ホーム画面テーマ」の開発者です。私が望むテーマのスタイルと、用意済みのアート素材を伝えます。完全なテーマパッケージを出力してください。ファイルは 3 つ:manifest.json、desktop.html、main.js です。
出力形式:各ファイルの前に「===== ファイル名 =====」という 1 行の区切りを置き、ファイル内容をそのまま出力すること。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 は 5 択:plain(元画像そのまま)| flat | polaroid(ポラロイド)| pill | glass。テーマの雰囲気に合わせる
- 明るい壁紙では launcher に "label": { "color": "#<濃い色>", "shadow": "light" } を追加すること。しないと App 名が既定の白文字になり読めない
—— desktop.html の約束 ——
- 装飾 DOM のみ。1 ページ目専用のヘッダー(時計 / あいさつ / キャラクター名)は <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 = 1 ページ目)、
data.character { name: 持ち主(ユーザーのペルソナ)の名前, worldName: 相手キャラクターの名前, worldTitle: 現在のワールド名(旧 App にない場合は worldName でフォールバック)}
- 構文はすべて ES5:var / function / 文字列連結。アロー関数、テンプレートリテラル、オプショナルチェーン ?.、分割代入は禁止——古い Android WebView でも動くこと
—— パフォーマンスのレッドライン(実機の多くはローエンド〜ミドルレンジの Android)——
- アニメーションは transform / opacity の CSS transition か animation のみ。広範囲の backdrop-filter は禁止
- requestAnimationFrame のループや setInterval での高頻度再描画は禁止。時計の更新は dataUpdated の配信に任せ、自前のタイマーを起動しない
- 大きな画像の読み込みは 1 回だけ(背景として敷き詰めれば十分)。JS で画像の src を繰り返し変更しない
—— 私が望むテーマのスタイル ——
[ここに記述。例:クリーム色の手帳風。上部に大きな時計と手書き風のあいさつ、壁紙の下半分にコラージュ装飾、アイコンはポラロイド風]
—— 用意済みの素材(ZIP に格納済み。これに従って参照すること)——
[実際に用意したファイルを列挙。例:
assets/wallpaper.png、assets/wallpaper_night.png、assets/sticker-moon.png、
icons/ にはアイコン 18 枚をすべて格納済み。
素材が 1 つもない場合は「素材なし」と書き、AI にすべて CSS で実装させる]AI が出力した 3 ファイルと assets/、icons/ を同じフォルダに入れて ZIP に圧縮し、 から新規エントリを作ってアップロード。右側のプレビューで 2 ページめくって確認します(公開前チェックリスト と照合)。気に入らなければ同じ AI との会話で「時計をもっと大きく」「ダークにして」と続けて修正し、ZIP を丸ごと差し替えます。問題なければプレビュー画像を設定し、価格を決めて審査に提出してください。
{
"schema_version": 3, // 固定で 3。4 に上げると旧バージョンの App がパッケージ全体を拒否する
"name": "Shine", // テーマ名(ストアと設定ページに表示)
"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": [ // 1 ページ目の上から下へのセグメント構成
{ "type": "zone", "id": "hero", "selector": "[data-theme-zone=\"hero\"]" },
// zone:雰囲気レイヤーの hero プレースホルダーの実高さを計測し、グリッドが避ける
{ "type": "apps", "rows": 1 }, // App アイコン 1 行
{ "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=1 ページ目と同じ上端
"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 — すりガラス:半透明 + 縁取り。暗い壁紙向き新しいバージョンのアプリは、オプションの launcher token 群に対応しています:アプリ名の文字色(明るいテーマで白文字を救済)、アイコンシェル icon_chrome / MiniApp に外周を足す icon_chrome_fallback、Dock トレイの配色とラベルのオン / オフ。schema 3 パッケージにそのまま書けば OK です(旧アプリは自動的に無視。schema_version を 4 に上げないでください——旧バージョンのアプリはパッケージごと拒否します)。
// 以下のフィールドはすべて任意。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 // MiniApp に統一の外枠を付けるならこちら。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/ を作り、アプリ ID を名前にした画像を入れます。例:icons/wechat.png。manifest への登録は不要で、ネイティブランチャーが自動でマッチングします。ビットマップ推奨(PNG/WebP/JPG、256×256 の正方形推奨):実機では一律 22% の角丸が適用され、表示サイズでデコードされるため安定します。SVG も対応していますが小さいサイズではディテールがつぶれやすく、シンプルな幾何学形状のみ推奨です。同名で複数フォーマットがある場合は png → webp → jpg → svg の順で最初のものが使われます。
| アプリ名 | アプリ ID | アイコンファイル |
|---|---|---|
| WeTalk | 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 |
| Shine ショッピング | 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 連携 | 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 |
マッチ順:個別アプリ ID → icons/default.png(フォールバック。個別指定のないシステム / 内蔵アプリに適用)→ アプリ自身のアイコン → システムアイコンまたは絵文字。MiniApp は例外で、常に自身がアップロードしたアイコンを使いテーマの対象外です。default も適用されません。
// v3 雰囲気レイヤー ThemeSDK(desktop.html + main.js)
// 1. 装飾プレースホルダー — 背景の雰囲気は全画面で常時表示
// <div data-theme-zone="hero">...</div>
ThemeSDK.ready();
ThemeSDK.reportLayout();
// 2. データ購読 + ページ切り替え時に 1 ページ目専用ヘッダー(あいさつ/時計/日付バー)をフェードアウト
// system.launcherPage:現在の Launcher ページ番号(0 = 1 ページ目)
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. ワールド機能(任意):ストーリー切り替え / 相手のスマホを見る、も雰囲気に描き込める
//
// タップ:data-theme-action と同じ仕組み(透明なネイティブタップレイヤー、ページ 0 のみ有効):
// <div id="world-card" data-theme-action="worldSwitch"></div> <!-- ストーリー切り替え -->
// <div id="ta-phone" data-theme-action="taPhoneOpen"></div> <!-- 相手のスマホを見る -->
// 現在のワールド名は character.worldTitle から読む(旧 App では worldName でフォールバック)。
// テーマが worldSwitch の入口を描くと、ネイティブの「ワールド切り替え」フローティングボタンは
// 1 ページ目では自動的に退く(2 ページ目以降は戻ってきてフォールバックする)。
// 描かなければフローティングボタンが常時フォールバックするので、入口が失われる心配はない。
// 入口を隠すときは必ず 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 グリッドの構築アプリ一覧のフィールド: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 節を参照)。