Skip to content

Commit a935939

Browse files
feat: add Halo theme with theme music / 新增 Halo 主题及主题音乐 (#1292)
* feat: add Halo theme with theme music Add an opt-in Halo presentation theme to the icon picker. It uses Halo Infinite press-kit artwork, the official Halo logo, Halo Infinite's Industry UI font, and loops the Halo: Combat Evolved title theme with a header music toggle. Charts keep the dark palettes; no data, filters, or APIs change. 中文:新增 Halo 主题及主题音乐 在图标主题选择器中新增可选的 Halo 展示主题,使用 Halo Infinite 官方宣传图、 Halo 官方 logo 和 Halo Infinite 的 Industry 界面字体,并循环播放 Halo: Combat Evolved 标题音乐,页头提供音乐开关。图表沿用深色配色,不改动数据、筛选条件或 API。 * test(themes): guard inactive Halo resources and SEO 中文:为未激活的 Halo 主题补齐资源隔离与 SEO 测试,并自动覆盖新注册主题。 --------- Co-authored-by: functionstackx <47992694+functionstackx@users.noreply.github.com>
1 parent 562005f commit a935939

31 files changed

Lines changed: 834 additions & 40 deletions

‎docs/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Design rationale and non-obvious conventions. See [CLAUDE.md](../CLAUDE.md) for
44

55
## Docs
66

7-
- [Presentation Themes](./themes.md): Minecraft activation and assets, CS:GO and GTA styling, icon-only theme picker, dark-chart/export integration, and presentation-only API exclusion.
7+
- [Presentation Themes](./themes.md): Minecraft activation and assets, CS:GO, GTA and Halo styling, Halo theme music, icon-only theme picker, dark-chart/export integration, and presentation-only API exclusion.
88
- [Playable Minecraft](./minecraft-game.md): the opt-in game launched from the Minecraft theme, controls, saves, lifecycle and verification.
99

1010
- [Dashboard Read-only Views](./dashboard-readonly-views.md): API/filter inventory, source audit and verification scope

‎docs/themes.md‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ contrast branch.
4040
## CS:GO implementation
4141

4242
`src/lib/themes.ts` centralizes picker order
43-
(`light, dark, minecraft, csgo, gta, kart, doom`), dark-background classification, and the
44-
chart-palette alias used by CS:GO and GTA. CS:GO retains the
43+
(`light, dark, minecraft, csgo, gta, kart, doom, halo`), dark-background classification, and the
44+
chart-palette alias used by CS:GO, GTA, Mario Kart, DOOM and Halo. CS:GO retains the
4545
existing sans-serif font for chart legibility and preserves the dark theme's
4646
vendor, high-contrast, and overlay palettes. It does not recolor hardware
4747
series into team colors.
@@ -87,6 +87,35 @@ and the separately reported permission are recorded in
8787
`packages/app/public/decorative/gta/README.md`. These assets are not covered
8888
by the repository's code license.
8989

90+
## Halo implementation
91+
92+
Halo follows the GTA pattern: it is dark-classified everywhere (`isDarkTheme`),
93+
aliases onto the dark chart seed (`chartPaletteTheme`), and keeps the dark
94+
vendor, high-contrast, and overlay palettes. The picker shows it with a shield
95+
icon after DOOM. There is no playable game.
96+
97+
The chrome follows the Halo Infinite menus: deep navy near-opaque panels over
98+
the Zeta Halo press-kit artwork, Forerunner-cyan accents and selected-tab
99+
underlines, Industry (Halo Infinite's UI face) for the header, tabs and
100+
headings, and DM Sans body copy. `HaloThemeBanner` pairs the Master Chief
101+
"Ring Vista" artwork with the official Halo logo and a live-text "InferenceX"
102+
wordmark. `HaloDecorations` follows the root class and the embed attribute and
103+
mounts local responsive WebP images (`aria-hidden`, `pointer-events: none`)
104+
only while Halo is active.
105+
106+
`HaloMusic` loops the Halo: Combat Evolved title theme from a local MP3 while
107+
the theme is mounted. Music is on by default; if the browser blocks autoplay,
108+
playback starts on the next pointer or key press. The header `HaloToggles`
109+
button (behind the Halo-gated `HaloTogglesLazy` wrapper, also in the mobile
110+
menu) stores `halo-music` in `localStorage`. Leaving the theme unmounts the
111+
player and stops playback; embeds never mount it.
112+
113+
Fonts load from the lazy theme CSS only. Provenance and the maintainer-reported
114+
Bungie/Microsoft permission are recorded in
115+
`packages/app/src/app/fonts/HALO-FONTS.md` and
116+
`packages/app/public/decorative/halo/README.md`. These assets are not covered
117+
by the repository's code license.
118+
90119
## Playable Minecraft
91120

92121
The Minecraft banner launches an optional, full [playable Minecraft](./minecraft-game.md)
@@ -156,7 +185,7 @@ and Firefox CI matrix. It covers:
156185
loaded JS/CSS fingerprints for the optional engines and Minecraft game,
157186
font registration, and absence of optional theme/game DOM.
158187
- English/Chinese embeds with each optional theme saved, before and after
159-
hydration. Saved music/sound opt-ins must not activate outside Minecraft.
188+
hydration. Saved Minecraft and Halo music/sound opt-ins must not activate outside their themes.
160189
- Raw-HTML metadata and headings for normal and Googlebot requests, preload
161190
hints, and unchanged SEO metadata when selecting and leaving optional themes.
162191

@@ -165,6 +194,17 @@ establish zero shared theme-selector overhead, guarantee search rankings, or
165194
replace production Core Web Vitals measurements. The shared pre-paint colors
166195
remain intentional to avoid a flash when an opted-in theme is restored.
167196

197+
The isolation suites derive optional themes from `APP_THEMES`, so registering
198+
a new theme adds its embed and activation/teardown cases automatically.
199+
Halo is also included in the inactive-path asset and saved-preference guards.
200+
Keep artwork, font declarations, layout CSS and music behind the selection
201+
boundary. Do not add theme asset preload hints to shared layouts.
202+
The default-page checks seed saved music/sound opt-ins for every guarded theme;
203+
these preferences must not start media on light/dark pages or embeds.
204+
Crawler and theme-switch comparisons also cover robots, Open Graph and Twitter
205+
metadata. Run these tests on the implementation's final commit before merge;
206+
passing an earlier commit does not verify later changes.
207+
168208
- Direct selection of every theme from the picker, keyboard navigation,
169209
persistence after reload, and cleanup on exit.
170210
- No CS:GO image requests on a cold light/dark landing; images load only

‎packages/app/cypress/component/mode-toggle.cy.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const ICONS: Record<string, string> = {
1010
gta: 'lucide-car',
1111
kart: 'lucide-flag',
1212
doom: 'lucide-skull',
13+
halo: 'lucide-shield',
1314
};
1415

1516
function pick(theme: string) {
@@ -52,7 +53,7 @@ describe('ModeToggle', () => {
5253
});
5354

5455
it('selects any theme directly and closes the menu', () => {
55-
for (const theme of ['kart', 'doom', 'gta', 'minecraft', 'dark', 'csgo', 'light']) {
56+
for (const theme of ['kart', 'doom', 'halo', 'gta', 'minecraft', 'dark', 'csgo', 'light']) {
5657
pick(theme);
5758
cy.get('html').should('have.class', theme);
5859
cy.get('[data-testid="theme-menu"]').should('not.exist');

‎packages/app/cypress/e2e/optional-theme-isolation.cy.ts‎

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,47 @@
1+
import { APP_THEMES } from '../../src/lib/themes';
2+
3+
const optionalThemes = APP_THEMES.filter((theme) => !['light', 'dark', 'system'].includes(theme));
14
const featureCode =
2-
/THREE\.WebGLRenderer|WebGLRenderer:|LOS SANTOS 3D|minecraft-click\.mp3|ender-dragon\.mp3|Loading Luigi Circuit|inferencex-minecraft-worlds|mc-panorama-cube|\.mc-hotbar-wrap|\.csgo-scene|\.gta-scene|\.mc-dragon-flyacross|\.kart-scene|\.doom-scene|font-family:\s*["']?(?:Monocraft|Pricedown|ChaletComprime)/i;
3-
const featureAsset =
4-
/\/decorative\/(?:minecraft|csgo|gta|kart|doom)\/|minecraft-click\.mp3|Monocraft-|Pricedown|ChaletComprime|youtube(?:-nocookie)?\.com|ytimg\.com/i;
5+
/THREE\.WebGLRenderer|WebGLRenderer:|LOS SANTOS 3D|minecraft-click\.mp3|ender-dragon\.mp3|Loading Luigi Circuit|inferencex-minecraft-worlds|mc-panorama-cube|\.mc-hotbar-wrap|\.csgo-scene|\.gta-scene|\.mc-dragon-flyacross|\.kart-scene|\.doom-scene|\.halo-scene|halo-theme\.css|font-family:\s*["']?(?:Monocraft|Pricedown|ChaletComprime|Industry|Halo)/i;
6+
const featureAsset = new RegExp(
7+
`/decorative/(?:${optionalThemes.join('|')})/|minecraft-click\\.mp3|Monocraft-|Pricedown|ChaletComprime|Industry-|halo[^/]*\\.(?:woff2?|ttf|otf|mp3|ogg|wav)|youtube(?:-nocookie)?\\.com|ytimg\\.com`,
8+
'i',
9+
);
510
const featureElements =
611
'[data-testid="minecraft-game"], [data-testid="minecraft-play-banner"], [data-testid="kart-game"], [data-testid$="-theme-banner"], .mc-dragon-flyacross';
712
const isFeatureAsset = (url: string) => featureAsset.test(decodeURIComponent(url));
813

14+
function saveMediaOptIns(win: Window, enabled: boolean) {
15+
for (const theme of optionalThemes) {
16+
win.localStorage.setItem(`${theme}-music`, String(enabled));
17+
win.localStorage.setItem(`${theme}-sound`, String(enabled));
18+
}
19+
}
20+
921
function expectNoOptionalResources(requests: string[]) {
1022
cy.window().then((win) => {
1123
// Request interception catches in-flight requests too; resource timing only lists completed ones.
1224
expect(requests.filter(isFeatureAsset), 'optional requests started').to.deep.equal([]);
1325
const resources = win.performance.getEntriesByType('resource');
1426
expect(resources.filter((entry) => isFeatureAsset(entry.name))).to.have.length(0);
1527
expect(
16-
[...win.document.fonts].filter((font) => /monocraft|pricedown|chalet/i.test(font.family)),
28+
[...win.document.fonts].filter((font) =>
29+
/monocraft|pricedown|chalet|industry|halo/i.test(font.family),
30+
),
1731
).to.have.length(0);
1832
for (const resource of resources.filter((entry) => /\.(?:js|css)(?:\?|$)/.test(entry.name)))
1933
cy.request(resource.name).its('body').should('not.match', featureCode);
2034
});
2135
cy.get(featureElements).should('not.exist');
36+
cy.get('audio, video, iframe[src*="youtube"]').should('not.exist');
2237
}
2338
const seo = (doc: Document) => [
2439
doc.title,
2540
doc.querySelector('meta[name="description"]')?.getAttribute('content'),
2641
doc.querySelector('link[rel="canonical"]')?.getAttribute('href'),
42+
...[
43+
...doc.querySelectorAll('meta[name="robots"], meta[property^="og:"], meta[name^="twitter:"]'),
44+
].map((node) => node.outerHTML),
2745
...[...doc.querySelectorAll('link[hreflang], script[type="application/ld+json"]')].map(
2846
(node) => node.outerHTML,
2947
),
@@ -50,8 +68,7 @@ describe('optional themes stay off the default page', () => {
5068
if (theme === 'default') win.localStorage.removeItem('theme');
5169
else win.localStorage.setItem('theme', theme);
5270
// Stored opt-ins must not start media outside the selected theme.
53-
win.localStorage.setItem('minecraft-music', 'true');
54-
win.localStorage.setItem('minecraft-sound', 'true');
71+
saveMediaOptIns(win, true);
5572
},
5673
});
5774
if (theme === 'light' || theme === 'dark') cy.get('html').should('have.class', theme);
@@ -71,7 +88,7 @@ describe('optional themes stay off the default page', () => {
7188
});
7289
}
7390

74-
for (const theme of ['minecraft', 'csgo', 'gta', 'kart', 'doom']) {
91+
for (const theme of optionalThemes) {
7592
for (const prefix of ['', '/zh']) {
7693
it(`${prefix || 'English'} embed ignores saved ${theme} before and after hydration`, () => {
7794
const requests: string[] = [];
@@ -81,8 +98,7 @@ describe('optional themes stay off the default page', () => {
8198
cy.visit(`${prefix}/embed/model/deepseek-v4?theme=light`, {
8299
onBeforeLoad(win) {
83100
win.localStorage.setItem('theme', theme);
84-
win.localStorage.setItem('minecraft-music', 'true');
85-
win.localStorage.setItem('minecraft-sound', 'true');
101+
saveMediaOptIns(win, true);
86102
},
87103
});
88104
cy.get('html').should('have.class', 'light').and('have.attr', 'data-inferencex-embed');
@@ -115,11 +131,7 @@ describe('optional themes stay off the default page', () => {
115131
(link) => isFeatureAsset(link.getAttribute('href') ?? ''),
116132
),
117133
).to.have.length(0);
118-
expect(
119-
doc.querySelector(
120-
'[data-testid="kart-game"], [data-testid="gta-theme-banner"], [data-testid="csgo-theme-banner"], [data-testid="doom-theme-banner"]',
121-
),
122-
).to.equal(null);
134+
expect(doc.querySelector(featureElements)).to.equal(null);
123135
// Metadata and indexable headings are in raw HTML, without running JavaScript.
124136
cy.request(route).then((normal) => {
125137
const normalDoc = new DOMParser().parseFromString(normal.body, 'text/html');
@@ -134,8 +146,7 @@ describe('optional themes stay off the default page', () => {
134146
cy.visit('/about', {
135147
onBeforeLoad(win) {
136148
win.localStorage.setItem('theme', 'light');
137-
win.localStorage.setItem('minecraft-music', 'false');
138-
win.localStorage.setItem('minecraft-sound', 'false');
149+
saveMediaOptIns(win, false);
139150
},
140151
});
141152
// The server-rendered trigger is visible before its click handler hydrates.
@@ -144,7 +155,7 @@ describe('optional themes stay off the default page', () => {
144155
.and('contain', 'currently');
145156
cy.document().then((doc) => {
146157
const baseline = seo(doc);
147-
for (const theme of ['csgo', 'gta', 'minecraft', 'kart', 'doom']) {
158+
for (const theme of optionalThemes) {
148159
cy.get('[data-testid="theme-toggle"]').click();
149160
cy.get(`[data-testid="theme-option-${theme}"]`).click();
150161
if (theme === 'minecraft') cy.get('canvas').should('exist');
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Halo assets
2+
3+
These are Microsoft / Bungie / 343 Industries (Halo Studios) assets, not assets
4+
covered by this repository's code license. On 2026-10-07 the requesting
5+
maintainer reported permission from Bungie and Microsoft to use Halo assets,
6+
fonts and theme music in InferenceX. That is a maintainer-reported permission,
7+
not an independently reviewed license or a claim of endorsement by Microsoft,
8+
Bungie or Halo Studios.
9+
10+
All files were downloaded on 2026-10-07. Images are official Halo Infinite
11+
press-kit releases, fetched from Halopedia's press-kit mirror.
12+
13+
## Artwork
14+
15+
| File | Source | Source SHA-256 | Transformation |
16+
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
17+
| `zeta-halo.webp`, `zeta-halo-mobile.webp` | [HINF PK ZetaHalo.png](https://www.halopedia.org/File:HINF_PK_ZetaHalo.png) (1920 × 1080) | `e8613afe5a2ca6802924439133b575ffd4b9afda7e27f61779bf1eed335ce2ee` | Bottom 105 px (press watermarks) cropped; WebP q78; 720 × 641 centered phone crop |
18+
| `ring-vista.webp`, `ring-vista-mobile.webp` | [Halo Infinite E319 A Ring Vista.jpg](https://www.halopedia.org/File:Halo_Infinite_E319_A_Ring_Vista.jpg) (7680 × 4320) | `61cbc5b3cc97cfa4484930c7716aba52f64f24970106ca05ad332e6df2001fc0` | Left 6% and bottom 10% (watermark) cropped; resized to 1600 / 800 px wide WebP |
19+
| `halo-logo.webp` | [Halo logo (2012-present).png](<https://www.halopedia.org/File:Halo_logo_(2012-present).png>) (3178 × 619) | `9fa435d9f7fff3b2f0d7b702538940f04ecfd1e8284816ead0ae72d9fd2cb533` | Trimmed to its alpha bounds; resized to 720 px wide WebP with alpha |
20+
21+
## Music
22+
23+
| File | Source | Source SHA-256 | Transformation |
24+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
25+
| `halo-theme.mp3` | "Halo Original Title" from the KHInsider [Halo: Combat Evolved gamerip](https://downloads.khinsider.com/game-soundtracks/album/halo-combat-evolved-gamerip-soundtrack-xbox-gamerip-2001) | `99e0945068506013472b676e8ae2ea68c69fca816e7c0bd65d27e595597f0393` | Re-encoded with `ffmpeg` to 44.1 kHz stereo 128 kbps MP3, metadata stripped |
26+
27+
Composed by Martin O'Donnell and Michael Salvatori for Bungie. The track loops
28+
while the Halo theme is selected; the header music toggle (`halo-music` in
29+
`localStorage`) mutes it. See `components/halo/halo-music.tsx`.
30+
31+
Fonts are documented in `src/app/fonts/HALO-FONTS.md`.
18.6 KB
Loading
2.15 MB
Binary file not shown.
8.21 KB
Loading
22.1 KB
Loading
29.1 KB
Loading

0 commit comments

Comments
 (0)