feat: integrate authored nightdrive soundtrack

This commit is contained in:
2026-09-04 14:39:28 +03:00
parent eb7f276b91
commit 745fa8169e
39 changed files with 1013 additions and 714 deletions

View File

@@ -17,14 +17,19 @@ import { useAudioEngine } from '@/composables/useAudioEngine';
import { useJourneySections } from '@/composables/useJourneySections';
import { useKeyboardNavigation } from '@/composables/useKeyboardNavigation';
import { useMotionPreferences } from '@/composables/useMotionPreferences';
import type { PortfolioContent, SectionId, SectionLayout } from '@/content/types';
import { assetUrl } from '@/content/asset-url';
import type { PortfolioContent, PublicAssetManifest, SectionId, SectionLayout } from '@/content/types';
import type { SceneHostState } from '@/composables/useSceneController';
import type { MotionPolicy } from '@/journey/motion-policy';
import type { SoundtrackSources } from '@/audio/types';
declare const __PORTFOLIO_TEST_BUILD__: boolean;
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
const props = defineProps<{ content: Readonly<PortfolioContent> }>();
const props = defineProps<{
content: Readonly<PortfolioContent>;
manifest: Readonly<PublicAssetManifest>;
}>();
const canonicalSectionIds = [
'start', 'profile', 'experience', 'infrastructure', 'selected-builds', 'additional-work', 'toolkit', 'contact',
@@ -59,8 +64,44 @@ const journey = useJourneySections(
navigation.state,
computed(() => motion.policy.value.allowSceneRequest),
);
const pendingAudioDestination = shallowRef<SectionId | null>(null);
let audioNavigationEpoch = -1;
watch(
[navigation.state, () => journey.value.activeSectionId],
([navigationState, activeSectionId]) => {
if (navigationState.epoch !== audioNavigationEpoch) {
audioNavigationEpoch = navigationState.epoch;
pendingAudioDestination.value = navigationState.source === 'passive-scroll'
? null
: navigationState.destinationId;
}
if (pendingAudioDestination.value === activeSectionId) {
pendingAudioDestination.value = null;
}
},
{ flush: 'sync', immediate: true },
);
const audioSectionId = computed(() => (
pendingAudioDestination.value ?? journey.value.activeSectionId
));
const soundtrackSources = computed<SoundtrackSources | null>(() => {
const soundtrack = props.content.soundtrack;
if (soundtrack === undefined) return null;
return {
intro: [
{ url: assetUrl(props.manifest, soundtrack.intro.opusAssetId), mediaType: 'audio/ogg' },
{ url: assetUrl(props.manifest, soundtrack.intro.mp3AssetId), mediaType: 'audio/mpeg' },
],
main: [
{ url: assetUrl(props.manifest, soundtrack.main.opusAssetId), mediaType: 'audio/ogg' },
{ url: assetUrl(props.manifest, soundtrack.main.mp3AssetId), mediaType: 'audio/mpeg' },
],
};
});
const audio = useAudioEngine({
activeSectionId: audioSectionId,
intensity: computed(() => journey.value.acceleration),
soundtrack: soundtrackSources,
volume: motion.volume,
setVolume: motion.setVolume,
});