diff --git a/src/App.vue b/src/App.vue index ddd9be0..7cbf77d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,7 +5,7 @@ import Page1 from './pages/Page1.vue'; import assets from './assets'; import Loader from './pages/Loader.vue'; -const stage = ref(-1); +const stage = ref(1); const userData = ref({ region: '奥莱', diff --git a/src/assets/ani/crop_script.py b/src/assets/ani/crop_script.py new file mode 100644 index 0000000..40c0d67 --- /dev/null +++ b/src/assets/ani/crop_script.py @@ -0,0 +1,78 @@ +import os +from PIL import Image +import re + +# --- 配置 --- +# 目标尺寸 +TARGET_WIDTH = 670 +TARGET_HEIGHT = 939 + +# 裁剪区域 (left, upper, right, lower) +# 我们从左上角 (0, 0) 开始,裁剪出一个 670x939 的区域 +CROP_BOX = (0, 0, TARGET_WIDTH, TARGET_HEIGHT) + +# 输出文件夹名称 +OUTPUT_DIR = "cropped" +# --- 结束配置 --- + +def batch_crop_images(): + """ + 批量裁剪当前目录下的 WebP 图片 + """ + # 获取当前脚本所在的目录 + current_dir = os.getcwd() + output_path = os.path.join(current_dir, OUTPUT_DIR) + + # 如果输出目录不存在,则创建它 + if not os.path.exists(output_path): + os.makedirs(output_path) + print(f"已创建输出目录: {output_path}") + + # 获取目录下所有文件 + files = os.listdir(current_dir) + + # 定义一个正则表达式来匹配 '数字.webp' 格式的文件名 + file_pattern = re.compile(r"^\d+\.webp$") + + image_files_to_process = [f for f in files if file_pattern.match(f)] + + if not image_files_to_process: + print("未在当前目录找到符合 '数字.webp' 格式的图片文件。") + return + + print(f"找到了 {len(image_files_to_process)} 个待处理的图片文件。") + + # 遍历所有符合条件的图片文件 + for filename in image_files_to_process: + try: + # 构建完整的文件路径 + file_path = os.path.join(current_dir, filename) + + # 打开图片 + with Image.open(file_path) as img: + print(f"正在处理: {filename} (原始尺寸: {img.size[0]}x{img.size[1]})") + + # 检查原始尺寸是否符合预期 + if img.size != (671, 940): + print(f" -> 警告: {filename} 的尺寸 ({img.size[0]}x{img.size[1]}) 与预期的 671x940 不符,已跳过。") + continue + + # 进行裁剪 + cropped_img = img.crop(CROP_BOX) + + # 构建输出文件路径 + save_path = os.path.join(output_path, filename) + + # 保存裁剪后的图片 + # quality=100 可以尽量保持高质量,你可以根据需要调整 + cropped_img.save(save_path, "WEBP", quality=100) + + print(f" -> 已裁剪并保存至: {save_path} (新尺寸: {cropped_img.size[0]}x{cropped_img.size[1]})") + + except Exception as e: + print(f"处理 {filename} 时发生错误: {e}") + + print("\n所有图片处理完成!") + +if __name__ == "__main__": + batch_crop_images() \ No newline at end of file diff --git a/src/assets/ani/海报.zip b/src/assets/ani/海报.zip deleted file mode 100644 index 62b8944..0000000 Binary files a/src/assets/ani/海报.zip and /dev/null differ diff --git a/src/assets/ani/海报p1.zip b/src/assets/ani/海报p1.zip index 46bd3c7..b0380d7 100644 Binary files a/src/assets/ani/海报p1.zip and b/src/assets/ani/海报p1.zip differ diff --git a/src/assets/ani/海报p2.zip b/src/assets/ani/海报p2.zip index 43cebe8..1b55086 100644 Binary files a/src/assets/ani/海报p2.zip and b/src/assets/ani/海报p2.zip differ diff --git a/src/pages/Game.vue b/src/pages/Game.vue index b62a9c4..fd0a6a4 100644 --- a/src/pages/Game.vue +++ b/src/pages/Game.vue @@ -22,10 +22,14 @@ const props = defineProps<{ const sunEle = useTemplateRef('sun-ani'); const sunEndEle = useTemplateRef('sun-ani-end'); +const posterP2Ele = useTemplateRef('gui-ani-end-post-p2'); const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const emit = defineEmits(['cloudUp', 'cloudDown', 'restart']); +// Intersection Observer for p2 animation +const hasP2AnimationPlayed = ref(false); + async function shoot() { //sunEle.value?.jumpTo('天上飞') sunEle.value?.jumpTo('蓄力飞') @@ -52,7 +56,7 @@ async function shoot() { emit('cloudUp'); document.querySelector('.bed')?.animate([ { transform: 'translateY(0)' }, - { transform: 'translateY(150%)' }, + { transform: 'translateY(170%)' }, ], { duration: 500, delay: 200, @@ -547,7 +551,7 @@ async function gameEnd() { }) document.querySelector('.bed')?.animate([ - { transform: 'translateY(150%)' }, + { transform: 'translateY(170%)' }, { transform: 'translateY(0)' }, ], { duration: 600, @@ -592,7 +596,7 @@ async function gameEnd() { document.querySelector('.bed')?.animate([ { transform: 'translateY(0)' }, - { transform: 'translateY(150%)' }, + { transform: 'translateY(170%)' }, ], { duration: 600, easing: 'cubic-bezier(0.4, 0, 1, 0.5)', @@ -713,9 +717,48 @@ async function finishCollect() { showLastPage.value = true; + // 在最后一页显示后设置 Intersection Observer 监听 p2 元素 + await wait(100); // 等待 DOM 更新 + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !hasP2AnimationPlayed.value) { + // p2 进入视口且动画未播放过 + hasP2AnimationPlayed.value = true; + posterP2Ele.value?.jumpTo('p2'); + console.log('Poster P2 animation triggered'); + + // 停止观察,因为只需要触发一次 + observer.unobserve(entry.target); + } + }); + }, { + threshold: 0.5, // 当 50% 的元素进入视口时触发 + rootMargin: '0px' + }); + + // 观察 p2 元素 + const p2Element = posterP2Ele.value?.$el; + if (p2Element) { + observer.observe(p2Element); + console.log('Started observing P2 element'); + } else { + console.warn('P2 element not found'); + } + + // 8秒后如果用户没有看过p2,自动滚动到p2 setTimeout(() => { - if (!posterHasChangedToPage2.value) posterHasChangedToPage2.value = true; - }, 8000); + if (!hasP2AnimationPlayed.value) { + console.log('Auto scrolling to P2 after 8 seconds'); + const posterContainer = document.querySelector('.poster-container'); + if (posterContainer) { + // 滚动到p2位置(第二个元素,占总宽度的50%) + posterContainer.scrollTo({ + left: posterContainer.scrollWidth * 0.5, + behavior: 'smooth' + }); + } + } + }, 8000); // 8秒 = 8000毫秒 } @@ -883,17 +926,17 @@ function posterSwap(e: PointerEvent) { :rules="[{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }]" style="position: absolute;top: 13%;width: 80%;left: 11%; pointer-events:none;" ref="main-logo" /> -
+
- + @@ -959,6 +1002,11 @@ function posterSwap(e: PointerEvent) { diff --git a/vite.config.ts b/vite.config.ts index b280a84..ffe4670 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,12 +1,10 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -import minipic from 'vite-plugin-minipic' // https://vite.dev/config/ export default defineConfig({ plugins: [ - vue(), - minipic() + vue() ], server: { host: '0.0.0.0'