2025-08-17 16:40:54 +08:00

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>