404 lines
10 KiB
Vue
404 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import Loader from './views/Loader.vue';
|
|
import s1Icon from './assets/s1_icon.png';
|
|
import s2Icon from './assets/s2_icon.png';
|
|
import s3Icon from './assets/s3_icon.png';
|
|
import s4Icon from './assets/s4_icon.png';
|
|
import AniEle from './ani-comp/AniEle.vue';
|
|
import { computed, ref, useTemplateRef } from 'vue';
|
|
import eas from './assets/audio';
|
|
|
|
const currentStage = ref(-1)
|
|
|
|
const stages = [1, 2, 3, 4]
|
|
|
|
const stageIcons = [
|
|
s1Icon,
|
|
s2Icon,
|
|
s3Icon,
|
|
s4Icon,
|
|
]
|
|
|
|
const icon_grow = new URL('./assets/按钮高亮.zip', import.meta.url).href;
|
|
const icon_intro = new URL('./assets/按钮引导.zip', import.meta.url).href;
|
|
|
|
const home_intro = new URL('./assets/首页引导.zip', import.meta.url).href;
|
|
const egg_intro = new URL('./assets/晶圆引导.zip', import.meta.url).href;
|
|
|
|
const loading = ref(true);
|
|
|
|
|
|
const mainVideo = useTemplateRef('main-video');
|
|
|
|
function continueAnimation() {
|
|
const cR = mainVideo.value?.getCurrentRule().name
|
|
console.log('continueAnimation', cR);
|
|
|
|
|
|
switch (cR) {
|
|
case 'intro-enter':
|
|
mainVideo.value?.jumpTo('intro-out');
|
|
eas.play('water_interface')
|
|
eas.play('shimmering_light')
|
|
eas.play('whoosh')
|
|
break;
|
|
case 'egg-loop':
|
|
mainVideo.value?.jumpToSoftly('egg-out');
|
|
eas.play('water_interface')
|
|
eas.play('whoosh')
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
const currentFrame = ref(0);
|
|
|
|
const showHomeIntro = computed(() => {
|
|
return currentFrame.value == 34;
|
|
});
|
|
|
|
const showEggIntro = computed(() => {
|
|
return currentFrame.value >= 124 && currentFrame.value < 175;
|
|
});
|
|
|
|
function onFrameProgress(frame: number) {
|
|
// console.log('onFrameProgress', frame);
|
|
|
|
currentFrame.value = frame;
|
|
if (frame === 240) {
|
|
currentStage.value = 0;
|
|
} else if (frame === 339) {
|
|
currentStage.value = 1;
|
|
} else if (frame === 498) {
|
|
currentStage.value = 2;
|
|
} else if (frame === 638) {
|
|
currentStage.value = 3;
|
|
} else if (frame === 1045) {
|
|
currentStage.value = -1; // Reset current stage when reaching the end
|
|
mainVideo.value?.jumpTo('intro-enter'); // Loop back to intro
|
|
}
|
|
|
|
if (frame == 868) {
|
|
eas.play('whoosh');
|
|
}
|
|
|
|
if (frame == 243) {
|
|
descStage.value = [0, 0];
|
|
} else if (frame == 303) {
|
|
descStage.value = [0, 1];
|
|
} else if (frame == 347) {
|
|
descStage.value = [1, 0];
|
|
} else if (frame == 506) {
|
|
descStage.value = [2, 0];
|
|
} else if (frame == 647) {
|
|
descStage.value = [3, 0];
|
|
} else if (frame == 803) {
|
|
descStage.value = [-1, 0]; // Reset description stage when reaching the end
|
|
}
|
|
}
|
|
|
|
function clickBtn(index: number) {
|
|
if (currentStage.value === index) {
|
|
mainVideo.value?.jumpTo(`stage-${index + 1}`);
|
|
eas.play('water_interface')
|
|
currentStage.value = -1; // Reset current stage after clicking
|
|
}
|
|
}
|
|
|
|
|
|
const bgm = useTemplateRef('bgm-ele');
|
|
const bgmURL = new URL('./assets/audio/The AI Technology.mp3', import.meta.url).href;
|
|
|
|
window.addEventListener('pointerdown', () => {
|
|
if (bgm.value?.paused) {
|
|
bgm.value.play().catch((error) => {
|
|
console.error('Error playing background music:', error);
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
const isPortrait = ref(window.innerHeight > window.innerWidth);
|
|
|
|
window.addEventListener('resize', () => {
|
|
isPortrait.value = window.innerHeight > window.innerWidth;
|
|
});
|
|
|
|
document.addEventListener('pointerdown', (e) => {
|
|
//鼠标中键
|
|
if (e.buttons == 4) {
|
|
// 处理鼠标中键点击事件
|
|
document.body.requestFullscreen().catch((error) => {
|
|
console.error('Error entering fullscreen:', error);
|
|
});
|
|
}
|
|
});
|
|
|
|
const loadProgress = ref({ download: 0, decode: 0 });
|
|
|
|
const mainVideoURL = new URL('./assets/main.mp4', import.meta.url).href;
|
|
const mainZipURL = new URL('./assets/main.zip', import.meta.url).href;
|
|
|
|
const descStage = ref([-1, 0])
|
|
|
|
// 描述内容数据放在 setup 中
|
|
interface DescItem { title: string; contents: string[] }
|
|
const descData: DescItem[] = [
|
|
{ title: '第1步', contents: ['首先,引入一种化学物质', '在反应腔内与硅片表面发生反应形成一层薄膜'] },
|
|
{ title: '第2步', contents: ['然后,将所有残留的分子<br>通过惰性气体或化学惰性气体吹扫出去'] },
|
|
{ title: '第3步', contents: ['接下来,引入第二种元素的反应气体与薄膜发生反应'] },
|
|
{ title: '第4步', contents: ['随后反应腔内残留的原子和分子再次被吹扫清除'] },
|
|
];
|
|
|
|
const currentDesc = computed(() => {
|
|
const [sIdx, subIdx] = descStage.value;
|
|
if (sIdx < 0) return null;
|
|
const item = descData[sIdx];
|
|
return { title: item.title, content: item.contents[Math.min(subIdx, item.contents.length - 1)] };
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="app" :class="{ rotate: isPortrait }">
|
|
<audio :src="bgmURL" loop autoplay ref="bgm-ele"></audio>
|
|
|
|
<Transition name="fade">
|
|
<Loader v-if="loadProgress.decode < 1" :progress="loadProgress" />
|
|
</Transition>
|
|
|
|
<AniEle ref="main-video" class="main-video" :rules="[
|
|
{ name: 'intro-enter', duration: 33, frame: 35, loop: 1, pauseAfter: true },
|
|
{ name: 'intro-out', duration: 33, frame: 89, loop: 1 },
|
|
{ name: 'egg-loop', duration: 33, frame: 50, loop: 0 },
|
|
{ name: 'egg-out', duration: 33, frame: 67, loop: 1, pauseAfter: true },
|
|
{ name: 'stage-1', duration: 33, frame: 99, loop: 1, pauseAfter: true },
|
|
{ name: 'stage-2', duration: 33, frame: 159, loop: 1, pauseAfter: true },
|
|
{ name: 'stage-3', duration: 33, frame: 140, loop: 1, pauseAfter: true },
|
|
{ name: 'stage-4', duration: 33, frame: 407, loop: 1 },
|
|
]" :width="1920" :height="1080" stretch autoPlay log @click="continueAnimation"
|
|
:url="{ video: mainVideoURL, zip: mainZipURL }" type="video" @loading="loadProgress = $event"
|
|
@progress="onFrameProgress" />
|
|
|
|
<Transition name="fade">
|
|
<AniEle v-if="showHomeIntro" :url="home_intro" :width="444" :height="50" :rules="[
|
|
{ name: 'main', loop: 0, duration: 33, frame: 24 },
|
|
]" class="home-intro intro-text" />
|
|
</Transition>
|
|
<Transition name="fade">
|
|
<AniEle v-if="showEggIntro" :url="egg_intro" :width="516" :height="54" :rules="[
|
|
{ name: 'main', loop: 0, duration: 33, frame: 25 },
|
|
]" class="egg-intro intro-text" />
|
|
</Transition>
|
|
|
|
<Transition name="slide-in">
|
|
<div class="actions" v-if="currentStage >= 0">
|
|
<div class="action" v-for="(s, index) in stages" :key="index"
|
|
:style="{ cursor: currentStage == index ? 'pointer' : 'not-allowed' }" @click="clickBtn(index)">
|
|
<img src="./assets/icon_bg.png" alt="" class="bg" />
|
|
<img :src="stageIcons[index]" alt="" class="icon" />
|
|
<AniEle :url="icon_grow" :width="210" :height="242" :rules="[{
|
|
name: 'grow',
|
|
duration: 33,
|
|
frame: 24,
|
|
loop: 0,
|
|
}]" class="grow" v-if="index == currentStage" />
|
|
<AniEle :url="icon_intro" :width="425" :height="54" :rules="[
|
|
{ name: 'enter', duration: 33, frame: 25, loop: 1, },
|
|
{ name: 'loop', duration: 33, frame: 23, loop: 0, }]" v-if="index == currentStage" class="intro" />
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
|
|
<Transition name="fade">
|
|
<div class="desc" v-if="descStage[0] >= 0">
|
|
<div class="desc-wrapper">
|
|
<Transition name="fade" mode="out-in">
|
|
<div class="desc-item" :key="descStage[0]">
|
|
<div class="desc-title">{{ currentDesc?.title }}</div>
|
|
<Transition name="fade" mode="out-in">
|
|
<div class="desc-content" :key="descStage[0] + '-' + descStage[1]" v-html="currentDesc?.content"></div>
|
|
</Transition>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
@keyframes stretch {
|
|
0% {
|
|
width: 0;
|
|
}
|
|
|
|
100% {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
|
|
.desc {
|
|
color: white;
|
|
|
|
.desc-wrapper {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
aspect-ratio: 16 / 9;
|
|
height: 100vmin;
|
|
max-width: 100vmax;
|
|
max-height: calc(100vmax * 9 / 16);
|
|
}
|
|
|
|
.desc-item {
|
|
position: relative;
|
|
left: 9vmin;
|
|
top: 6vmin;
|
|
display: flex;
|
|
/* 横向排列 */
|
|
flex-direction: row;
|
|
align-items: flex-end;
|
|
/* 底部对齐 */
|
|
gap: 4vmin;
|
|
/* 标题与内容间距 */
|
|
padding: 1vmin .5vmin;
|
|
/* 给下划线留空间 */
|
|
width: 70vmin;
|
|
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: .3vmin;
|
|
/* 更细的下划线 */
|
|
background-color: white;
|
|
animation: stretch 1s ease forwards;
|
|
}
|
|
}
|
|
|
|
.desc-title {
|
|
font-size: 5.5vmin;
|
|
letter-spacing: .2em;
|
|
line-height: 1;
|
|
/* 避免额外高度 */
|
|
white-space: nowrap;
|
|
/* 保持一行 */
|
|
}
|
|
|
|
.desc-content {
|
|
font-size: 2vmin;
|
|
line-height: 1.2;
|
|
font-weight: light;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.intro-text {
|
|
position: absolute;
|
|
transform: translate(-50%, -50%);
|
|
z-index: 1000;
|
|
height: 4.4vmin;
|
|
pointer-events: none;
|
|
|
|
&.home-intro {
|
|
left: 50%;
|
|
top: 63%;
|
|
}
|
|
|
|
&.egg-intro {
|
|
left: 70%;
|
|
top: 50%;
|
|
}
|
|
}
|
|
|
|
.app {
|
|
position: relative;
|
|
height: 100vh;
|
|
width: 100vw;
|
|
|
|
&.rotate {
|
|
height: 100vw;
|
|
width: 100vh;
|
|
transform: rotate(90deg) translateY(-100%);
|
|
transform-origin: 0% 0%;
|
|
}
|
|
}
|
|
|
|
img {
|
|
display: block;
|
|
}
|
|
|
|
.actions {
|
|
height: 100%;
|
|
background-color: #411341;
|
|
position: fixed;
|
|
right: 0;
|
|
top: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: space-around;
|
|
padding: 7vmin 7vmin 7vmin 8vmin;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.action {
|
|
position: relative;
|
|
|
|
.bg {
|
|
height: 18vmin;
|
|
}
|
|
|
|
.grow {
|
|
position: absolute;
|
|
z-index: 1;
|
|
height: 22vmin;
|
|
width: fit-content;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.icon {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 8vmin;
|
|
}
|
|
|
|
.intro {
|
|
position: absolute;
|
|
left: -4vmin;
|
|
top: 50%;
|
|
transform: translate(-100%, -100%);
|
|
height: 5vmin;
|
|
}
|
|
}
|
|
|
|
.main-video {
|
|
position: absolute;
|
|
inset: 0;
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity .4s;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
</style>
|