293 lines
6.8 KiB
Vue
293 lines
6.8 KiB
Vue
<script setup lang="ts">
|
|
import { useWindowSize } from "@vueuse/core";
|
|
import gsap from "gsap";
|
|
import { LazyLagModal } from "#components";
|
|
|
|
const { isReady } = useAssets();
|
|
|
|
const app = useAppStore();
|
|
|
|
const overlay = useOverlay();
|
|
const lagModal = overlay.create(LazyLagModal);
|
|
|
|
watch(
|
|
() => app.lagDetected,
|
|
(detected) => {
|
|
if (detected) lagModal.open();
|
|
},
|
|
);
|
|
|
|
const topScreen = useTemplateRef("topScreen");
|
|
const bottomScreen = useTemplateRef("bottomScreen");
|
|
|
|
const topScreenCanvas = computed(() => topScreen.value?.canvas ?? null);
|
|
const bottomScreenCanvas = computed(() => bottomScreen.value?.canvas ?? null);
|
|
|
|
const isIOS = () => /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
const isTouchDevice = () =>
|
|
"ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
|
|
const showFullscreenBtn = ref(true);
|
|
|
|
const toggleFullscreen = () => {
|
|
if (document.fullscreenElement) {
|
|
document.exitFullscreen();
|
|
} else {
|
|
document.documentElement.requestFullscreen();
|
|
}
|
|
};
|
|
|
|
const windowSize = useWindowSize();
|
|
|
|
watch([windowSize.width, windowSize.height], ([width, height]) => {
|
|
if (width / height > 614 / 667) {
|
|
if (app.booted) app.allowHints();
|
|
} else {
|
|
app.disallowHints();
|
|
}
|
|
});
|
|
|
|
const helpButton = useTemplateRef("helpButton");
|
|
let helpAnimation: gsap.core.Timeline | null = null;
|
|
|
|
const showHelpLabels = async (yoyo = false) => {
|
|
if (!app.hintsAllowed) return;
|
|
|
|
helpAnimation?.kill();
|
|
app.hintsVisible = true;
|
|
|
|
await nextTick();
|
|
|
|
helpAnimation = gsap
|
|
.timeline({
|
|
onComplete: () => {
|
|
helpAnimation = null;
|
|
},
|
|
})
|
|
.fromTo(
|
|
helpButton.value,
|
|
{ color: "#666666", opacity: 0.5 },
|
|
{ color: "#ffffff", opacity: 1, duration: 0.2, ease: "power1.out" },
|
|
)
|
|
.to(helpButton.value, {
|
|
color: "#666666",
|
|
opacity: 0.5,
|
|
duration: 0.2,
|
|
ease: "power1.in",
|
|
delay: 3,
|
|
})
|
|
.call(() => {
|
|
app.hintsVisible = false;
|
|
});
|
|
|
|
if (yoyo) {
|
|
helpAnimation.to(helpButton.value, {
|
|
color: "#ffffff",
|
|
opacity: 1,
|
|
duration: 0.3,
|
|
repeat: 5,
|
|
yoyo: true,
|
|
delay: 0.3,
|
|
});
|
|
}
|
|
};
|
|
|
|
const hideHelpLabels = () => {
|
|
if (!app.hintsVisible) return;
|
|
|
|
helpAnimation?.kill();
|
|
helpAnimation = null;
|
|
|
|
app.hintsVisible = false;
|
|
|
|
gsap.to(helpButton.value, {
|
|
color: "#666666",
|
|
opacity: 0.5,
|
|
duration: 0.2,
|
|
ease: "power1.in",
|
|
});
|
|
};
|
|
|
|
watch(
|
|
() => app.hintsAllowed,
|
|
(allowed) => {
|
|
if (!allowed) hideHelpLabels();
|
|
},
|
|
);
|
|
|
|
onMounted(async () => {
|
|
if (isIOS()) {
|
|
showFullscreenBtn.value = false;
|
|
}
|
|
|
|
if (isTouchDevice()) {
|
|
const landscape = window.matchMedia("(orientation: landscape)");
|
|
|
|
const onOrientationChange = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
if (e.matches && !document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen().catch(() => {});
|
|
}
|
|
};
|
|
|
|
landscape.addEventListener("change", onOrientationChange);
|
|
|
|
onUnmounted(() => {
|
|
landscape.removeEventListener("change", onOrientationChange);
|
|
});
|
|
}
|
|
|
|
const scaleX = (window.innerWidth - 40) / 235;
|
|
const scaleY = (window.innerHeight - 40) / 431;
|
|
const scale = Math.min(scaleX, scaleY);
|
|
|
|
if (app.settings.renderingMode === "2d" && scale < 1) return;
|
|
|
|
if (!app.booted) {
|
|
watch(
|
|
() => app.hintsAllowed,
|
|
async (allowed) => {
|
|
if (allowed) await showHelpLabels(true);
|
|
},
|
|
{ once: true },
|
|
);
|
|
}
|
|
});
|
|
|
|
useKeyDown(async ({ key, repeated }) => {
|
|
if (!repeated && key.toLocaleLowerCase() === "h") {
|
|
if (app.hintsVisible) {
|
|
hideHelpLabels();
|
|
} else {
|
|
await showHelpLabels();
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<LoadingScreen v-if="!isReady || !app.userHasInteracted" />
|
|
<div v-else>
|
|
<TresCanvas
|
|
v-if="app.settings.renderingMode === '3d'"
|
|
window-size
|
|
clear-color="#181818"
|
|
>
|
|
<TresPerspectiveCamera :args="[45, 1, 0.001, 1000]" />
|
|
|
|
<TresAmbientLight />
|
|
<TresDirectionalLight />
|
|
|
|
<NDS3D
|
|
v-if="topScreenCanvas && bottomScreenCanvas"
|
|
:top-screen-canvas="topScreenCanvas"
|
|
:bottom-screen-canvas="bottomScreenCanvas"
|
|
/>
|
|
</TresCanvas>
|
|
|
|
<NDS2D
|
|
:style="{
|
|
visibility: app.settings.renderingMode === '3d' ? 'hidden' : 'visible',
|
|
}"
|
|
>
|
|
<template #topScreen>
|
|
<Screen ref="topScreen">
|
|
<IntroTopScreen v-if="!app.booted" />
|
|
<HomeTopScreen v-else-if="app.screen === 'home'" />
|
|
<ContactTopScreen v-else-if="app.screen === 'contact'" />
|
|
<ProjectsTopScreen v-else-if="app.screen === 'projects'" />
|
|
<SettingsTopScreen v-else-if="app.screen === 'settings'" />
|
|
<GalleryTopScreen v-else-if="app.screen === 'gallery'" />
|
|
<AchievementsTopScreen v-else-if="app.screen === 'achievements'" />
|
|
<CreditsTopScreen v-else-if="app.screen === 'credits'" />
|
|
|
|
<AchievementsNotification />
|
|
<CommonConfetti screen="top" />
|
|
</Screen>
|
|
</template>
|
|
<template #bottomScreen>
|
|
<Screen ref="bottomScreen">
|
|
<IntroBottomScreen v-if="!app.booted" />
|
|
<HomeBottomScreen v-else-if="app.screen === 'home'" />
|
|
<ContactBottomScreen v-else-if="app.screen === 'contact'" />
|
|
<ProjectsBottomScreen v-else-if="app.screen === 'projects'" />
|
|
<SettingsBottomScreen v-else-if="app.screen === 'settings'" />
|
|
<GalleryBottomScreen v-else-if="app.screen === 'gallery'" />
|
|
<AchievementsBottomScreen v-else-if="app.screen === 'achievements'" />
|
|
<CreditsBottomScreen v-else-if="app.screen === 'credits'" />
|
|
|
|
<CommonConfetti screen="bottom" />
|
|
</Screen>
|
|
</template>
|
|
</NDS2D>
|
|
|
|
<button
|
|
v-if="app.hintsAllowed"
|
|
ref="helpButton"
|
|
class="help-btn"
|
|
@click="app.hintsVisible ? hideHelpLabels() : showHelpLabels()"
|
|
>
|
|
?
|
|
</button>
|
|
|
|
<button
|
|
v-if="showFullscreenBtn"
|
|
class="fullscreen-btn"
|
|
@click="toggleFullscreen"
|
|
>
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.help-btn {
|
|
position: fixed;
|
|
bottom: 16px;
|
|
left: 16px;
|
|
width: 30px;
|
|
height: 30px;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: #666;
|
|
font-size: 18px;
|
|
cursor: pointer;
|
|
opacity: 0.5;
|
|
transition: opacity 0.2s;
|
|
user-select: none;
|
|
z-index: 100;
|
|
}
|
|
|
|
.help-btn:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
.fullscreen-btn {
|
|
position: fixed;
|
|
bottom: 16px;
|
|
right: 16px;
|
|
width: 32px;
|
|
height: 32px;
|
|
padding: 4px;
|
|
background: none;
|
|
border: none;
|
|
color: #666;
|
|
cursor: pointer;
|
|
opacity: 0.5;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.fullscreen-btn:hover {
|
|
opacity: 1;
|
|
}
|
|
</style>
|