跳轉到

主題系統

本檔角色:視覺 / 音訊主題(theme)——受控 CSS、樣式 token、資產映射、分類註冊表、fallback、選用、社群 PR。 對應模組(實作):程式架構/themes.md。 設計動機:樣式 / 圖檔 / 圖示 /LOGO/ 音樂與程式碼脫鉤——程式碼只保證流程與功能,並公開版本化的穩定 theme part;外觀交給各主題自己的 token、資產與受控 CSS。新增主題不改 Angular/ 共用 SCSS/ThemeService,可客製差異、可分類、逐主題留歷史。

1. 定位與鐵則

  • 只換 Skin、不換語意與行為:DOM 結構 / 頁面流程 / 資訊層級 / 文案(i18n 管)不歸主題;主題可透過受控 token 與 theme.css 改變字體角色、元件形狀、邊框、陰影、表面、裝飾、主視覺構圖位置與受控動態,但不得改變閱讀順序、權限、必要操作、核心 CTA 可達性或共用 RWD 安全下限。Skin Layer 與套用強度見 美術資源/主題外觀與 RWD.md
  • 不碰 gameplay 資訊視覺(保留 token):過熱漸變色、警告色、對手 / 自車辨識色等玩法資訊 token 列入保留清單 THEME_RESERVED_TOKENSui.md §14)——主題不可覆蓋(CI 擋 + runtime 防禦忽略)。
  • 品牌錨點固定:OG 圖 /PWA manifest icons/SEO 爬蟲所見 = 官方預設(manifest 為 origin 級、無法 per-user);app 內 LOGO/ 圖示可主題化。
  • 純 client 資源:不上鏈、不影響配對、不進版本六欄位;任何主題 / 分類變更 = patch版本規範.md §3 純資源家族、§4 src/themes/public/assets/themes/ path 規則)。
  • 主題 ⊥ i18n:文字歸 i18n、外觀歸主題,兩軸正交(4 語系 × N 主題);分類顯示名走 i18n dict(§4)、主題自身名稱內嵌多語系(§5)。

2. 模型總覽

受控 CSS + token + manifest + fallback 四件組:

  1. 受控 CSS:Angular 只公開版本化的 [data-theme-part='…'] 穩定部位;主題以自己資料夾內的 theme.css 組合表面、形狀、裝飾與主視覺,CI 禁止依賴內部 class。
  2. token:共用程式與基礎樣式引用 CSS custom properties(色彩 / 字體 / 間距…)與已註冊的真實資產 key family(brand-emblemlanding-*scene-*empty-*core-loop-*race-countdown-*race-start*sfx-*bgm-*)。
  3. manifest:每主題一份 theme.json 宣告 Style API 版本、可選 stylesheet、token 覆蓋、資產映射、受控字型角色與僅顯示層使用的 renderProfile§5§5.3)。
  4. fallback:Default CSS、token 與資產是完整基線;非 default 主題可省略 stylesheet、token 或資產項目,缺項保留 / 解析到 Default——輕主題(只換色票)與全套主題(含獨立 CSS、插畫、BGM)使用同一模型。

3. 目錄結構(自包含+分類註冊表)

src/themes/
├── categories.json        ← 分類註冊表(合法分類唯一來源)
├── retired-ids.json       ← 退役 id 清單(僅授權/法律下架時登錄;CI 拒重用,§5.2)
├── generated/registry.ts  ← public manifests 的生成式 bundle bridge(禁手改)
└── *.ts                   ← ThemeService、stylesheet loader 與測試

public/assets/themes/
├── default/               ← 預設主題 = fallback 骨幹(CSS / token / 資產 key 完備;SW 永遠預快取)
│   ├── theme.json
│   ├── theme.css
│   └── assets/…
└── <theme-id>/            ← 每主題自包含
    ├── theme.json
    ├── theme.css           ← 可省略;省略表示只使用 Default CSS+自身 token/資產
    └── assets/…
  • 執行期由 /assets/themes/<id>/… 靜態服務;可部署 payload 位於 public/assets/themes/。i18n 則是 bundle lazy chunk,兩者不是同一資產模式。分類、退役 id 與 TypeScript 實作留在 src/themes/,不得公開。
  • 資產隔離鐵則:主題資料夾只放自己的資產、磁碟零混放;跨主題共用一律靠 fallback 到 default,禁止跨主題引用(theme A 不得指向 theme B 的檔案,validate 擋路徑越界)。

4. 分類系統(資料可變、非程式 enum)

分類集合做成資料、不做成程式——現在無法預知會有哪些分類(淺色系 / 日系風格 / 端午節…),所以:

  • categories.json = 合法分類清單 [{ id, i18nKey }];顯示名走 i18n dict themes.category.<id>(四語系,語系清單.md §3)。
  • 起始註冊表僅 basic(基本),所有主題預設落此;主題累積後由 PR 新增分類 + 改各主題 category 欄——分類演化 = 純資料變更(patch、零程式變更),且分類變遷留在各主題自己的 git 歷史(社群討論與歷史累積的分辨單位)。
  • theme.json category 值必須存在於註冊表(CI 驗證,擋 typo 與分類碎裂)。
  • 分類粒度不設限:「端午節」分類下逐年累積 duanwu-2026-a1b2c3duanwu-2027-9d8e7f… 各為獨立主題、永久可選。
  • 機制一次完整、演化的是資料:註冊表 + 欄位 + 藝廊分組自始即完整功能,長大的只有註冊表內容。

5. theme.json Manifest

{
  "id": "duanwu-2026-a1b2c3", // = 資料夾名;<slug>-<6 位自產尾碼>(§5.2)、不可改(歷史錨點)
  "styleApiVersion": 1, // 必填;不支援的版本拒絕啟用
  "stylesheet": "theme.css", // 非 default 可省略;只能指向主題內安全 .css 相對路徑
  "name": { "zh-TW": "端午 2026", "zh-CN": "…", "en": "…", "ja": "…" }, // 內嵌多語系(主題名是內容、非 UI 框架文字;**`name.en` 必填**(§7)、其餘缺語系 → en)
  "category": "basic", // 必須存在於 categories.json
  "authors": ["<貢獻者>"],
  "tokens": { "--color-primary": "#e8590c" }, // CSS custom properties 覆蓋(保留 token 除外)
  "assets": { "brand-emblem": "assets/brand-emblem.svg", "bgm-home": "assets/home.ogg" }, // 已註冊 key → 主題內相對路徑或 null
  "fonts": [
    {
      "role": "base",
      "faces": [
        {
          "src": "assets/fonts/identity.woff2",
          "weight": "100 900",
          "style": "normal",
          "license": "assets/fonts/OFL-1.1.txt",
        },
      ],
    },
  ],
  "renderProfile": {
    // 可省略;省略/無效=安全 default PBR
    "version": 1,
    "materialMode": "toon",
    "toonSteps": 3,
    "lighting": {
      "ambientColor": "#bfd4e6",
      "ambientIntensity": 1,
      "keyColor": "#ffd1a3",
      "keyIntensity": 1.6,
    },
    "stage": {
      "groundColor": "#111820",
      "surfaceColor": "#252d35",
      "raisedColor": "#343e48",
      "metalColor": "#77828d",
      "accentColor": "#f26422",
      "markerColor": "#f0e6d2",
    },
  },
}

5.1 Style API 與受控 CSS

styleApiVersion: 1 的公開 API 是 Angular 模板上的 [data-theme-part='…']。完整機器名錄只由 scripts/theme-css.mjs 維護,本檔只記穩定語意;增加、移除或更名 part 必須在同一正式 PR 原子更新 registry、production emitter、builtin theme、測試與規範,不能由單一主題私自要求。themes:validate 雙向檢查每個 registry part 至少有一個 production emitter,且每個 production literal emitter 都已註冊;掃描 Angular HTML、component host metadata 與封閉的 literal themePart input,排除 specs、fixtures、dist 與 generated。真正動態 emitter 必須提供封閉來源映射,不得以全面忽略放行。主題選擇不覆寫某個活 part 仍屬合法。

game-scene-decoration 是 backdrop 與功能內容之間、覆蓋完整場景且不接收輸入的主題裝飾層;garage-decoration 是對應的 Garage shell 全覆蓋、aria-hiddenpointer-events: none 非互動裝飾表面。兩者可承載 background、gradient、texture、mask 與 pseudo-elements,但不保證固定角落、邊框、位置構圖或具體幾何。Garage 的 stage-artstage-vignette 維持核心結構層,不另公開專用 part。首頁 hero-art-surface 是 Hero artwork 的 viewport/clip surface;hero-art 才是實際圖像層。v1 名錄不包含 shell-accentpanel-cornerhero-art-frame,也不提供這些名稱的 alias。

app-backdrop 是所有路由唯一的固定 viewport 主題底層;公開頁、玩家頁與沉浸頁皆保留,頁面可用自己的不透明 DOM 背景、Canvas 或 Three.js 場景覆蓋。landing-info-surface 只負責首頁 Hero 下方資訊區的滿寬底色,不得替兩張資訊面板增加第三層外框。兩者的尺寸、stacking、pointer-event 與可存取性由共用層固定,主題只控制圖像、濾鏡、顏色與表面。

LOCAL 徽章是 local: 本機測試資產的共用語意標記,車位、Editor、零件 picker 與 Race Config 必須使用同一組 --o4-local-badge-* token;主題可改外觀但不得隱藏、改字或套到鏈上資產。

設定頁外層 tint 與 settings-shellsettings-navigationsettings-panel 的背景皆由主題控制; 共用層只負責內容填滿與互動。Default/ 月兔目前 tint = 0%、shell = 76–82%,局部控制表面 可維持 88–96%;其他主題可自行覆寫並接受 RWD/ 對比驗收,不需新增主題名稱專屬 class。

  • theme.css 只可用已註冊的 stable part selector;可搭配元素、屬性、狀態 pseudo-class/pseudo-element 與 :lang(),但不可選取 Angular/UI Kit 的內部 class,也不可用 [class…] 旁路。
  • 允許 @media@container@supports@keyframes;作者提供的 theme.css 仍禁止 @import@font-face、未知 at-rule、!importantposition: fixed。主題字型只能走下述結構化 manifest 契約,由 runtime 產生不可自行命名的 @font-face
  • url() 只能引用同一主題已由 manifest assetspreviewAsset 註冊、位於 assets/ 的相對路徑;禁止外部 URL、絕對路徑、data URL、query/fragment、跨主題與 ../
  • :scope 只可宣告主題私有的 --theme-* custom properties;主題 CSS 可讀 manifest token、共用 token 與自己的 --theme-*,不得憑空依賴其他未註冊全域變數。
  • Root scope 由 runtime 自動包裝;主題檔不得自行依賴 theme id、festivaldefault 等 class 或 ThemeService enum。
  • CSS 可重排已公開 part 在既有容器內的視覺位置,但不得以隱藏、遮蔽、改寫閱讀順序或移出可達範圍的方式改變功能;觸控下限、focus、安全區、文件無水平溢位等仍由共用 safety layer 最終守門。
  • Angular template 與 SVG 的結構、外部引用及重置頁例外只由 ui-frontend.md §3 定義;主題不得另開放 inline 幾何或資料 URL。

5.1.1 結構化主題字型

  • fonts 可省略;只允許唯一的 basemonodisplay 語意角色,每個角色含一個以上 face,單主題合計最多 THEME_FONT_MAX_FILES = 4 個 WOFF2、壓縮 bytes 合計最多 THEME_FONT_MAX_BYTES = 524288(512 KiB),且仍計入 20 MB 主題總預算。
  • 每個 face 精確宣告 srcweightstylelicensesrc 只能是同主題 assets/.woff2license 只能是同主題 .txt 且內容為 SIL Open Font License 1.1。這兩者由 fonts 直接形成 manifest 引用,不塞入需與 Default key 對齊的一般 assets 映射。
  • weight 只收 1–1000 整數或遞增的 "min max" range;style 只收 normal | italic。同角色、同 style 的 weight range 不得重疊;同一 WOFF2 不得重複引用。初版不開放作者自訂 family、src list、local()unicode-range、variation setting 或 metrics override。
  • runtime family 固定為由 theme id+role namespaced 的全域名稱,產生 font-display: swap@font-face;禁止外部、絕對、data、query/fragment、../、跨主題路徑與 font collection。字型 face 本身是 CSS 全域名稱,安全邊界靠不可碰撞的 runtime naming,而不是假設 @scope 能隔離 face。
  • basemono 分別映射既有 --font-family-base--font-family-monodisplay 映射 --o4-display-font;另提供 --theme-font-base--theme-font-mono--theme-font-display 給 stable part 使用。自帶字型永遠排在既有 locale-aware stack 前面;缺 glyph、下載 404 或瀏覽器解碼失敗都回退既有字型,display 未提供時回退 base
  • 主題提交不等待字型下載完成;首次啟用可發生受控 FOUT。字型失敗不得將整個主題標成 load-failed,也不得回退已成功提交的 stylesheet、token 或資產。

5.2 id 命名與生命週期

結構 = <slug>-<尾碼>:slug 為語意 kebab 名(作者自取、不要求唯一、系列慣例 <系列>-<年份>);尾碼為作者自產隨機 6 位 [a-z0-9]

  • 自動檢核themes:validate):格式正則 ^[a-z0-9]+(?:-[a-z0-9]+)*-[a-z0-9]{6}$ + 總長 ≤ 40——從字尾錨定,slug 長度不固定不影響檢核default 為唯一免尾碼保留 id。
  • 唯一性保證鏈:硬保證 = 資料夾名即 id、重複 CI 必紅(100% 機械);尾碼隨機 = 讓撞名機率趨近零的慣例——「是否真隨機」不需檢核,撞了重產一個即可。
  • 命名零仲裁:slug 不獨占(dark-aaaaaa 不擋 dark-bbbbbb)→ 無搶名、無先到先得問題;review 僅審 IP/ 蹭名(與顯示名同標準)。id ⊥ 顯示名:好聽的名字放 name(多語系、不要求全域唯一,藝廊以名稱 + 作者 + 分類消歧義)。
  • 不可改、不重用:id 被 settings persist/ 社群討論 /git 歷史三處引用——改名 = 視為新主題;主題原則永久保留(歷史累積),僅授權 / 法律問題下架,其 id 登錄 themes/retired-ids.json 永久退役、CI 拒絕重用(否則舊玩家 persist 設定會被偷換成不同主題);涉法律要求清除 git 歷史時走 GitHub DMCA 流程(repo 側機制,與 pinning provider 的 UGC DMCA 案件分屬兩層)。

5.3 車輛 renderProfile(僅顯示層)

renderProfile 只控制車庫展示、比賽 / 觀戰與離線縮圖的材質、燈光、背景 / 霧及可選描邊;改裝編輯器固定中性 PBR,避免風格化明暗遮蔽零件接點與材質判讀。完整契約如下:

  • version:正整數;只用於顯示快取失效。
  • materialModepbr | toontoonSteps 可省略,僅允許整數 2 | 3 | 4 | 5
  • outline 可省略:enabled: booleancolor#RGB/#RRGGBB/#RRGGBBAAstrength 介於 0–5、thickness 介於 0–4。
  • lighting 必填:ambientColorkeyColor 為上述十六進位色;ambientIntensitykeyIntensity 介於 0–4;rimColorrimIntensity 可省略,強度同為 0–4。
  • environment 可省略:backgroundColorfogColor 為上述十六進位色;fogNearfogFar 為非負有限數,兩者並存時必須 fogFar > fogNear
  • stage 可省略:groundColorsurfaceColorraisedColormetalColoraccentColormarkerColor 皆為上述十六進位色;控制 Hemisphere ground、showroom/workshop/arena 舞台材質、分隔線、起點標記與 grid helper。缺項逐欄回退安全 Default palette,不得由 presets.ts 判斷主題 id。

manifest 缺少或任何欄位無效時,runtime 整份退回 default PBR,不做部分接受。描邊不受支援、建構 / 渲染失敗或 GPU 壓力時只降級為原材質模式直接渲染;Toon 主題仍可用,不因 Outline 失敗讓頁面失效。

此設定不上鏈、不是車輛資料,不得進 gameplay、CID、loadout hash 或版本六欄位協商。切換主題會重用既有 GLB/ 組裝快取,只更新 render view、舞台與帶 themeId + renderProfile.version 的縮圖快取 key。

5.4 賽內倒數與起跑視覺資產

網站主題可完整擁有賽內 3、2、1 與起跑效果,但不得改變倒數語意或正式 GO 時序。每個主題 使用下列八個 manifest 槽;default 必須完整,其他主題省略時依一般規則回退 default:

動態槽 靜態槽 內容
race-countdown-3 race-countdown-3-static 數字 3
race-countdown-2 race-countdown-2-static 數字 2
race-countdown-1 race-countdown-1-static 數字 1
race-start race-start-static 發令喇叭、煙火、彩炮等無語系起跑效果
  • 全部使用 512×512、含 alpha 的 WebP。動態槽 2–18 幀、loop metadata 固定為 1;數字總時長 最多 1000ms、起跑最多 800ms,動態檔最多 768 KiB。靜態槽只能有一幀、最多 128 KiB。
  • 3/2/1 可使用 0–9 例外;race-start 禁止 GO、其他文字、商標與浮水印。亮度變化不得在 一秒內形成超過三次閃光;起跑效果只允許單次明亮爆發。
  • 動畫造型、粒子、速度、光影與緩動全部烘入 WebP;共用 CSS 只負責固定 HUD 位置、尺寸、 safe area、object-fit 與 z-index,不建立主題專屬 keyframes 或動畫 profile。
  • effective reduced motion 直接選靜態槽。一般模式載入失敗依序嘗試目前主題動態、目前主題 靜態、default 動態、default 靜態,最後才顯示 DOM 數字/本地化 GO;重複 URL 去除。
  • participant 與 spectator 各用自己的本機 active theme;圖片一律 aria-hidden,固定 aria-live 容器仍保留真實 3/2/1 與本地化 GO。每筆事件建立新的圖片世代,使後續回合 收到相同秒數仍從第一幀播放。
  • 下載、解碼、播放與失敗降級都只是呈現,不得 await、延後或取消 match-start、race-ready、 secondsLeft === 0 或 frame pump。可在進房或主題啟用時 best-effort 預載 bytes,但不得用 隱藏 <img> 預播動畫。

首批 default 與 moon-rabbit-workshop-2026-97c954 runtime 成品位於各自 assets/race-countdown/;提示詞、透明母圖、逐幀 PNG 與可重跑工程位於 美術資源/提示詞/賽內倒數與起跑動畫/release-input/ui/race-countdown/美術資源/實際使用圖/賽內倒數與起跑動畫/;前者保存圖像 bytes,後者只保存受版控的設定與生成工具。

6. 資產解析與快取

  • 解析順序:themes/<選中>/assets/<key> → 查無 → themes/default/assets/<key>default 必須完備(全 token/ 資產 key 覆蓋,CI 驗證——它是 fallback 底)。音訊 key 例外:名錄必列、值可 null= 該槽靜音;主題可寫 null 明確靜音蓋掉 default(§7程式架構/audio-system.md §3)。
  • SW 快取:default 的 stylesheet、一般資產、字型與授權檔列入 install 預快取;其他主題全部 cache-first lazy(選用時抓)——首載 bundle 不含非 default 主題資產,主題逐年累積不影響首載,repo 大小靠 per-theme 預算控(§7)。
  • 升版後自癒:client 升版 =CACHE_NAME 換新、lazy 快取的非 default 主題被清——升版後首次啟動背景重跑 setTheme(當前主題) 補滿快取;離線且資產缺 → 該 session 暫以 default 呈現、不改 persisted themeId(回線補抓後自動恢復)。
  • 降級原因可見:設定頁主題區必須以目前語系顯示精簡警告,區分所選 id 查無(unknown-id)、主題已退役(retired)與資源/樣式載入失敗(load-failed)。警告不得揭露內部例外;成功套用任一主題後立即清除。啟動時已發生的降級,以及設定頁內切換或重設造成的降級,都讀取 ThemeService.fallbackReason 的同一權威結果。
  • 切換即時生效(CSS variables swap+ 資產 URL 重解析;BGM= 當前槽重解析、異檔即 crossfade,程式架構/audio-system.md §4),不需 reload;3D 場景經 ThemeService token 鏡像同步(Three.js 不讀 CSS 變數,程式架構/themes.md §2)。

實作守門補充:正式主題名錄包含 default 與中秋「月兔工坊」;setTheme 使用遞增 request generation,只有最後一次請求可提交 token、persist 與音訊刷新,避免慢回應覆蓋玩家較新的選擇。SSR 與瀏覽器的主題根路徑都由 deployment basePath 推導;Angular 公開資產只複製 runtime manifest/ 圖像 / 音訊,不公開主題 .ts 或測試原始碼。

正式主題選單的 manifest 註冊表由 public/assets/themes/*/theme.json 自動生成;新增主題不手改 ThemeService。生成物內建完整 Default manifest(首啟 fallback)與非 Default 的小型選單摘要,不從 public/ 反向 import;WebP/SVG/ 音訊及非 Default 完整 manifest 仍依選用結果 lazy 載入。startbuild 都會在 Angular 編譯前重建 registry 與 Default token SCSS,避免部署新 payload 搭配舊 bundle;themes:validate 仍以 --check 比對已提交生成物,防止 PR 漏列或手改漂移。

7. Schema 驗證(CI,pnpm run themes:validate

  • id 格式 ^[a-z0-9]+(?:-[a-z0-9]+)*-[a-z0-9]{6}$+ 總長 ≤ 40(default 免尾碼例外);資料夾重複 = 紅(§5.2
  • styleApiVersion 必須是 runtime 支援版本;Default 的 stylesheet 必填,非 default 可省略;有填時必須是主題內安全 .css 相對路徑且檔案存在
  • theme.css 通過 Style API validator:只用已註冊 stable part、不得碰內部 class/ 危險 at-rule/!important/fixed 定位;URL 與 custom property 依 §5.1 白名單驗證
  • id 未列於 retired-ids.json(退役不重用)
  • 正式主題生成註冊表與所有非 default theme.json 一致(漏列 / 手改 / 排序漂移 = 紅)
  • category 存在於 categories.json
  • default 主題 token/ 資產 key 完備——機器定義:程式碼引用集 ⊆ default 提供集(CI 掃描原始碼的 var(--*)resolveAsset('key') 字面值);其他主題 keys ⊆ default keys(未知 key = 錯字防呆、直接紅)
  • name.en 必填(fallback 鏈「缺語系 → en」的前提;與 i18n「en 缺 key 升 error」同哲學)
  • token 值禁含 url((資產一律走 assets 映射,防繞過資產隔離)
  • SVG 資產 sanitize:禁 <script>/ 事件屬性(on*)/ 外部引用——主題 SVG 為不可信來源,<img>/CSS url 引用、禁 inlineui-frontend.md §3
  • 音訊 key(bgm-*sfx-*):default 必列全部槽位、值可 null(名錄完備、檔案可缺——程式架構/audio-system.md §6);槽位與保留清單權威為 src/audio-system/slots.ts,保留槽出現於非 default 主題 = 警告(供檔會被忽略);音訊檔解碼煙測(ogg/opus)
  • fonts schema、角色/face/descriptor 唯一性、4 檔/512 KiB 子預算、安全同主題路徑、檔案存在與 OFL 1.1 內容;WOFF2 須通過 signature、header 宣告長度、table directory/壓縮區段邊界及禁止 collection 的結構檢查,並在 CI 以 headless Chromium FontFace.load() 真實解碼,不能只驗 wOF2 magic bytes
  • 資產 key/token 引用一律字面值、禁動態組字串(lint;§7 完備掃描的前提,否則靜默漏報)
  • manifest 引用的檔案必須存在;資料夾內每個檔案必須被 manifest 引用(防垃圾累積)
  • 保留 token(THEME_RESERVED_TOKENS)未被覆蓋
  • 單主題大小 ≤ THEME_SIZE_MAX_MBui.md §14;BGM 為大頭)
  • 路徑越界(../)擋下
  • 官方預設主題另過 Lighthouse a11y 門檻(程式架構/seo.md §7

8. 選用與 persist

  • 設定頁「顯示」分類選主題(遊戲機制.md §9DisplaySettings.themeId,預設 'default'程式架構/settings.md)。
  • 本地 persist、不上鏈;域名遷移歸零重設(低價值類,部署資訊.md §8.1)。
  • 主題藝廊:依分類分組 + 名稱 / 作者排序。
  • persisted themeId 查無(主題已因授權 / 法律下架)→ 回 default 並提示(程式架構/themes.md §1 setTheme guard;不留懸空狀態)。

9. 社群 PR 流程(同 語系清單.md §10 模式)

官方 runtime 的正式主題只能透過 Git PR 納入,並受 repository review、CI 與資產授權檢查約束。其他使用者自行 fork、修改且不送 PR 的版本不在官方審查、相容性承諾或 runtime 信任範圍內。

  1. Fork repo → 自產 id(§5.2<slug>-<6 位隨機尾碼>)→ 新增 public/assets/themes/<id>/(theme.json + 可選 theme.css+assets)
  2. pnpm run themes:validate
  3. 提 PR → review(Style API/ 外部 URL 與內部 class 禁用 / 保留 token 尊重 / 視覺品質 /a11y 對比與鍵盤 focus/ 公開首頁、車庫、顯示設定、Race Config、Editor 五個代表頁的桌面、手機直向、手機橫向、高直向與超寬截圖(ui-frontend.md §14)/ 自帶字型的 OFL 檔、缺 glyph fallback、文字 overflow 與必要操作可達性 / 圖檔禁字:主題資產不得內含文字、數字與中立符號可(ui-frontend.md §16)/ 資產授權與大小:須為貢獻者原創或與專案 license 相容,並符合單主題預算;PR 模板要求聲明來源)
  4. merge → patch bump 隨版發佈
  5. 分類調整(新增分類 / 搬移主題)走同一管道

10. 跨模組對接

模組 內容
程式架構/themes.md ThemeService/token 套用/fallback 解析/SW 快取/validate 實作
程式架構/settings.md DisplaySettings.themeId(顯示分類)
語系清單.md · 程式架構/i18n.md 分類顯示名 dict themes.*;語系 × 主題正交
版本規範.md §3/§4 純資源 → patch;src/themes/public/assets/themes/ path 規則
美術資源.md 預設主題視覺 = 現行設計系統(design-system)
程式架構/audio-system.md bgm-*sfx-* 槽位名錄權威;音訊 key 三態(路徑/省略=fallback/null=靜音);保留 SFX 槽恆取 default
ui-frontend.md UI 側銜接:o4-* 元件庫 = token 消費層(全站強制 = 主題覆蓋率保證)、token 命名慣例、build 生成 :root 基準、Three.js token 鏡像
美術資源/主題外觀與 RWD.md Skin token/資產最小契約、頁面套用強度、Default/月兔方向、多比例 RWD 與代表頁驗收
ui.md §14 THEME_RESERVED_TOKENSTHEME_SIZE_MAX_MBTHEME_FONT_MAX_FILESTHEME_FONT_MAX_BYTES