Add featured Dark House track with default landing ambient playback
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Wire featured-dark-house-2025.mp3 as the default ambient source, enable BGM for first-time visitors, and attempt autoplay on Command Deck with gesture-unlock fallback. The ~57MB MP3 stays gitignored; copy instructions are in public/audio/.gitkeep.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
# Local media (not part of the product)
|
# Local media (not part of the product)
|
||||||
/music/
|
/music/
|
||||||
|
/server/web/public/audio/featured-dark-house-2025.mp3
|
||||||
|
|
||||||
# Temp downloads
|
# Temp downloads
|
||||||
*.crdownload
|
*.crdownload
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
# Drop your looping ambient track here as ambient.mp3 (MP3 preferred; OGG/WAV also work).
|
# Background music for the dashboard ambient player.
|
||||||
# Enable background music in Settings → Sound & Haptics after adding the file.
|
#
|
||||||
|
# Featured (default): featured-dark-house-2025.mp3 (~57MB — gitignored; copy locally)
|
||||||
|
# Copy-Item "music/DARK HOUSE 2025 Deep Hypnotic Electronic Experience.mp3" `
|
||||||
|
# "server/web/public/audio/featured-dark-house-2025.mp3"
|
||||||
|
#
|
||||||
|
# Legacy fallback: ambient.mp3 (smaller loops still work if you point AMBIENT_MUSIC_SRC at them).
|
||||||
|
|||||||
@@ -17,11 +17,16 @@ describe('ambientMusic prefs', () => {
|
|||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults music off and volume ~0.22', () => {
|
it('defaults music on for first visit and volume ~0.22', () => {
|
||||||
expect(loadBgmEnabled()).toBe(false);
|
expect(loadBgmEnabled()).toBe(true);
|
||||||
expect(loadBgmVolume()).toBeCloseTo(0.22);
|
expect(loadBgmVolume()).toBeCloseTo(0.22);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('respects explicit opt-out', () => {
|
||||||
|
localStorage.setItem(BGM_STORAGE_KEY, '0');
|
||||||
|
expect(loadBgmEnabled()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('persists enabled flag', () => {
|
it('persists enabled flag', () => {
|
||||||
const p = new AmbientMusicPlayer();
|
const p = new AmbientMusicPlayer();
|
||||||
p.setEnabled(true);
|
p.setEnabled(true);
|
||||||
@@ -38,8 +43,8 @@ describe('ambientMusic prefs', () => {
|
|||||||
expect(localStorage.getItem(BGM_VOLUME_KEY)).toBe('0');
|
expect(localStorage.getItem(BGM_VOLUME_KEY)).toBe('0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('points at public audio path', () => {
|
it('points at featured public audio path', () => {
|
||||||
expect(AMBIENT_MUSIC_SRC).toBe('/audio/ambient.mp3');
|
expect(AMBIENT_MUSIC_SRC).toBe('/audio/featured-dark-house-2025.mp3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ducks effective volume 30% while modal registered', () => {
|
it('ducks effective volume 30% while modal registered', () => {
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* Looping background music — drop your MP3 at public/audio/ambient.mp3
|
* Looping background music — featured track at public/audio/featured-dark-house-2025.mp3
|
||||||
* (MP3 preferred; OGG/WAV also work if you update AMBIENT_MUSIC_SRC).
|
* (copy from repo music/; legacy ambient.mp3 still valid if you change AMBIENT_MUSIC_SRC).
|
||||||
*/
|
*/
|
||||||
export const BGM_STORAGE_KEY = 'aetherforge-bgm';
|
export const BGM_STORAGE_KEY = 'aetherforge-bgm';
|
||||||
export const BGM_VOLUME_KEY = 'aetherforge-bgm-volume';
|
export const BGM_VOLUME_KEY = 'aetherforge-bgm-volume';
|
||||||
/** Served from Vite public/ — place ambient.mp3 here before enabling in Settings. */
|
/** Featured Command Deck landing track (gitignored when >15MB — copy locally from music/). */
|
||||||
export const AMBIENT_MUSIC_SRC = '/audio/ambient.mp3';
|
export const FEATURED_MUSIC_SRC = '/audio/featured-dark-house-2025.mp3';
|
||||||
|
export const FEATURED_MUSIC_TITLE = 'Dark House 2025';
|
||||||
|
/** Default looping source for the global ambient player. */
|
||||||
|
export const AMBIENT_MUSIC_SRC = FEATURED_MUSIC_SRC;
|
||||||
|
|
||||||
/** Route → playback multiplier (0–1). User volume × intensity = effective output. */
|
/** Route → playback multiplier (0–1). User volume × intensity = effective output. */
|
||||||
export const PAGE_AMBIENT_INTENSITY: Record<string, number> = {
|
export const PAGE_AMBIENT_INTENSITY: Record<string, number> = {
|
||||||
@@ -40,9 +43,10 @@ export function resolvePageAmbientIntensity(pathname: string): number {
|
|||||||
export function loadBgmEnabled(): boolean {
|
export function loadBgmEnabled(): boolean {
|
||||||
try {
|
try {
|
||||||
const v = localStorage.getItem(BGM_STORAGE_KEY);
|
const v = localStorage.getItem(BGM_STORAGE_KEY);
|
||||||
|
if (v === null) return true;
|
||||||
return v === '1';
|
return v === '1';
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useAmbientMusic } from '../context/AmbientMusicContext';
|
import { useAmbientMusic } from '../context/AmbientMusicContext';
|
||||||
import { resolvePageAmbientIntensity } from '../audio/ambientMusic';
|
import { ambientMusicPlayer, FEATURED_MUSIC_TITLE, resolvePageAmbientIntensity } from '../audio/ambientMusic';
|
||||||
import './GlobalMusicPlayer.css';
|
import './GlobalMusicPlayer.css';
|
||||||
|
|
||||||
export default function GlobalMusicPlayer() {
|
export default function GlobalMusicPlayer() {
|
||||||
@@ -14,13 +14,19 @@ export default function GlobalMusicPlayer() {
|
|||||||
setPageIntensity(routeIntensity);
|
setPageIntensity(routeIntensity);
|
||||||
}, [routeIntensity, setPageIntensity]);
|
}, [routeIntensity, setPageIntensity]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const path = location.pathname.split('?')[0].replace(/\/$/, '') || '/';
|
||||||
|
if (path === '/dashboard') void ambientMusicPlayer.tryPlay();
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`global-music-player${isDim ? ' global-music-player--ambient-dim' : ''}`}
|
className={`global-music-player${isDim ? ' global-music-player--ambient-dim' : ''}`}
|
||||||
data-sfx="off"
|
data-sfx="off"
|
||||||
data-ambient-intensity={pageIntensity.toFixed(2)}
|
data-ambient-intensity={pageIntensity.toFixed(2)}
|
||||||
role="region"
|
role="region"
|
||||||
aria-label="Background music controls"
|
aria-label={`Background music — ${FEATURED_MUSIC_TITLE}`}
|
||||||
|
title={FEATURED_MUSIC_TITLE}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ export function AmbientMusicProvider({ children }: { children: React.ReactNode }
|
|||||||
return ambientMusicPlayer.subscribe(setPlaying);
|
return ambientMusicPlayer.subscribe(setPlaying);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (enabled) void ambientMusicPlayer.tryPlay();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ambientMusicPlayer.setEnabled(enabled);
|
ambientMusicPlayer.setEnabled(enabled);
|
||||||
ambientMusicPlayer.setVolume(volume);
|
ambientMusicPlayer.setVolume(volume);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import FleetRuntimePanel from '../components/Fleet/FleetRuntimePanel';
|
|||||||
import { AuditLogStrip } from '../components/Fleet/FleetOpsWidgets';
|
import { AuditLogStrip } from '../components/Fleet/FleetOpsWidgets';
|
||||||
import { useSound } from '../context/SoundContext';
|
import { useSound } from '../context/SoundContext';
|
||||||
import { useAmbientMusic } from '../context/AmbientMusicContext';
|
import { useAmbientMusic } from '../context/AmbientMusicContext';
|
||||||
import { AMBIENT_MUSIC_SRC } from '../audio/ambientMusic';
|
import { AMBIENT_MUSIC_SRC, FEATURED_MUSIC_TITLE } from '../audio/ambientMusic';
|
||||||
import { useVisualEffects } from '../context/VisualEffectsContext';
|
import { useVisualEffects } from '../context/VisualEffectsContext';
|
||||||
import {
|
import {
|
||||||
FORGE_SKIN_IDS,
|
FORGE_SKIN_IDS,
|
||||||
@@ -732,9 +732,9 @@ export default function SettingsPage() {
|
|||||||
<hr style={{ border: 'none', borderTop: '1px solid var(--border-dim)', margin: '1.25rem 0' }} />
|
<hr style={{ border: 'none', borderTop: '1px solid var(--border-dim)', margin: '1.25rem 0' }} />
|
||||||
<h3 className="font-tech" style={{ fontSize: '0.85rem', marginBottom: '0.35rem' }}>Background music</h3>
|
<h3 className="font-tech" style={{ fontSize: '0.85rem', marginBottom: '0.35rem' }}>Background music</h3>
|
||||||
<p className="section-desc" style={{ marginBottom: '0.75rem' }}>
|
<p className="section-desc" style={{ marginBottom: '0.75rem' }}>
|
||||||
Optional looping ambient track served from <code className="mono-sm">{AMBIENT_MUSIC_SRC}</code>. Use the
|
Featured track <strong>{FEATURED_MUSIC_TITLE}</strong> served from{' '}
|
||||||
mini player in the bottom-right corner on any page, or enable here. Defaults off — browsers block autoplay
|
<code className="mono-sm">{AMBIENT_MUSIC_SRC}</code>. On by default on Command Deck — browsers may still
|
||||||
until you press play or toggle this on.
|
block autoplay until you click or press a key; use the mini player in the bottom-right or toggle here.
|
||||||
</p>
|
</p>
|
||||||
<div className="form-group checkbox-group">
|
<div className="form-group checkbox-group">
|
||||||
<label className="checkbox-label">
|
<label className="checkbox-label">
|
||||||
|
|||||||
Reference in New Issue
Block a user