feat(gallery): transition to and from the nds
This commit is contained in:
21
app/components/Gallery/BottomScreen/BottomScreen.vue
Normal file
21
app/components/Gallery/BottomScreen/BottomScreen.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
const { onRender } = useScreen();
|
||||
const store = useGalleryStore();
|
||||
const { assets } = useAssets();
|
||||
|
||||
onRender((ctx) => {
|
||||
ctx.drawImage(assets.home.bottomScreen.background, 0, 0);
|
||||
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.globalAlpha = store.isIntro
|
||||
? store.intro.fadeOpacity
|
||||
: store.isOutro
|
||||
? store.outro.fadeOpacity
|
||||
: 0;
|
||||
ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
31
app/components/Gallery/TopScreen/TopScreen.vue
Normal file
31
app/components/Gallery/TopScreen/TopScreen.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
const { onRender } = useScreen();
|
||||
const store = useGalleryStore();
|
||||
const { assets } = useAssets();
|
||||
|
||||
onMounted(() => {
|
||||
if (store.shouldAnimateOutro) {
|
||||
store.shouldAnimateOutro = false;
|
||||
store.animateOutro();
|
||||
} else {
|
||||
store.$reset();
|
||||
store.animateIntro();
|
||||
}
|
||||
});
|
||||
|
||||
onRender((ctx) => {
|
||||
ctx.drawImage(assets.home.topScreen.background, 0, 0);
|
||||
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.globalAlpha = store.isIntro
|
||||
? store.intro.fadeOpacity
|
||||
: store.isOutro
|
||||
? store.outro.fadeOpacity
|
||||
: 0;
|
||||
ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
@@ -11,7 +11,7 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
|
||||
buttons: {
|
||||
projects: [31, 23, 193, 49],
|
||||
contact: [31, 71, 97, 49],
|
||||
downloadPlay: [127, 71, 97, 49],
|
||||
gallery: [127, 71, 97, 49],
|
||||
|
||||
theme: [0, 167, 31, 26],
|
||||
settings: [112, 167, 31, 26],
|
||||
@@ -19,7 +19,7 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
|
||||
},
|
||||
initialButton: "projects",
|
||||
onButtonClick: (button) => {
|
||||
if (button === "downloadPlay" || button === "theme" || button === "alarm")
|
||||
if (button === "theme" || button === "alarm")
|
||||
throw new Error(`Not implemented: ${button}`);
|
||||
|
||||
store.animateOutro(button);
|
||||
@@ -28,15 +28,15 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
|
||||
projects: {
|
||||
down: "last",
|
||||
left: "contact",
|
||||
right: "downloadPlay",
|
||||
right: "gallery",
|
||||
horizontalMode: "preview",
|
||||
},
|
||||
contact: {
|
||||
up: "projects",
|
||||
right: "downloadPlay",
|
||||
right: "gallery",
|
||||
down: "settings",
|
||||
},
|
||||
downloadPlay: {
|
||||
gallery: {
|
||||
up: "projects",
|
||||
left: "contact",
|
||||
down: "settings",
|
||||
@@ -70,6 +70,19 @@ const getOpacity = (button?: (typeof selectedButton)["value"]) => {
|
||||
|
||||
onRender((ctx) => {
|
||||
ctx.globalAlpha = getOpacity();
|
||||
|
||||
// gallery
|
||||
ctx.font = "7px NDS7";
|
||||
ctx.fillStyle = "#2c2c2c";
|
||||
fillTextCentered(
|
||||
ctx,
|
||||
$t("home.photoGallery"),
|
||||
132,
|
||||
78 + getButtonOffset("gallery"),
|
||||
87,
|
||||
);
|
||||
|
||||
// gba thing
|
||||
ctx.font = "10px NDS10";
|
||||
ctx.fillStyle = "#a2a2a2";
|
||||
fillTextCentered(ctx, $t("home.greeting"), 79, 135, 140);
|
||||
@@ -91,9 +104,9 @@ onRender((ctx) => {
|
||||
/>
|
||||
<Button
|
||||
:x="128"
|
||||
:y="72 + getButtonOffset('downloadPlay')"
|
||||
:opacity="getOpacity('downloadPlay')"
|
||||
:image="assets.home.bottomScreen.buttons.downloadPlay"
|
||||
:y="72 + getButtonOffset('gallery')"
|
||||
:opacity="getOpacity('gallery')"
|
||||
:image="assets.home.bottomScreen.buttons.gallery"
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -41,6 +41,15 @@ const { camera, renderer } = useTresContext();
|
||||
|
||||
model.scale.set(100, 100, 100);
|
||||
|
||||
const app = useAppStore();
|
||||
watch(
|
||||
() => camera.activeCamera.value,
|
||||
(cam) => {
|
||||
if (cam) app.setCamera(cam);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
meshes.clear();
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
@@ -199,6 +208,7 @@ const handleClick = (event: MouseEvent) => {
|
||||
switch (intersection.object.name) {
|
||||
case TOP_SCREEN:
|
||||
case BOTTOM_SCREEN: {
|
||||
console.log(intersection);
|
||||
const canvas =
|
||||
intersection.object.name === TOP_SCREEN
|
||||
? props.topScreenCanvas
|
||||
|
||||
@@ -133,6 +133,8 @@ const animateIntro = async () => {
|
||||
);
|
||||
};
|
||||
|
||||
const galleryStore = useGalleryStore();
|
||||
|
||||
const animateOutro = async () => {
|
||||
isAnimating.value = true;
|
||||
|
||||
@@ -156,6 +158,7 @@ const animateOutro = async () => {
|
||||
typeText(descriptionText, DESCRIPTION, DESCRIPTION_DURATION, true, {
|
||||
onComplete: async () => {
|
||||
await sleep(ANIMATION_SLEEP * 1000);
|
||||
galleryStore.shouldAnimateOutro = true;
|
||||
router.push("/");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import * as THREE from "three";
|
||||
import type { Screen as NDSScreen } from "#components";
|
||||
|
||||
// TODO: keys doesn't work anymore if 3D modea is disabled
|
||||
const ENABLE_3D = false;
|
||||
const ENABLE_3D = true;
|
||||
|
||||
type ScreenInstance = InstanceType<typeof NDSScreen>;
|
||||
|
||||
@@ -59,7 +59,7 @@ useKeyUp((key) => {
|
||||
<TresPerspectiveCamera
|
||||
:args="[45, 1, 0.001, 1000]"
|
||||
:position="[0, 10, 12]"
|
||||
:rotation="[-Math.PI / 4.7, 0, 0]"
|
||||
:rotation="[THREE.MathUtils.degToRad(-38.3), 0, 0]"
|
||||
/>
|
||||
|
||||
<TresAmbientLight />
|
||||
@@ -79,6 +79,7 @@ useKeyUp((key) => {
|
||||
<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'" />
|
||||
</Screen>
|
||||
</div>
|
||||
<div>
|
||||
@@ -87,6 +88,7 @@ useKeyUp((key) => {
|
||||
<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'" />
|
||||
</Screen>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod/mini";
|
||||
import type * as THREE from "three";
|
||||
|
||||
const STORAGE_ID = "app_settings";
|
||||
|
||||
@@ -29,6 +30,7 @@ export const useAppStore = defineStore("app", {
|
||||
booted: false,
|
||||
settings,
|
||||
screen: "home" as AppScreen,
|
||||
camera: null as THREE.Camera | null,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -41,6 +43,10 @@ export const useAppStore = defineStore("app", {
|
||||
this.screen = screen;
|
||||
},
|
||||
|
||||
setCamera(camera: THREE.Camera) {
|
||||
this.camera = camera;
|
||||
},
|
||||
|
||||
save() {
|
||||
localStorage.setItem(STORAGE_ID, JSON.stringify(this.settings));
|
||||
},
|
||||
|
||||
147
app/stores/gallery.ts
Normal file
147
app/stores/gallery.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import gsap from "gsap";
|
||||
import * as THREE from "three";
|
||||
|
||||
export const useGalleryStore = defineStore("gallery", {
|
||||
state: () => ({
|
||||
intro: {
|
||||
fadeOpacity: 0,
|
||||
},
|
||||
|
||||
outro: {
|
||||
fadeOpacity: 1,
|
||||
},
|
||||
|
||||
isIntro: true,
|
||||
isOutro: false,
|
||||
shouldAnimateOutro: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
animateIntro() {
|
||||
const CAMERA_DELAY = 0.7;
|
||||
const CAMERA_DURATION = 3;
|
||||
|
||||
const FADE_DELAY = 0;
|
||||
const FADE_DURATION = 1;
|
||||
|
||||
this.isIntro = true;
|
||||
this.isOutro = false;
|
||||
|
||||
const app = useAppStore();
|
||||
if (app.camera) {
|
||||
gsap.fromTo(
|
||||
app.camera.position,
|
||||
{
|
||||
x: 0,
|
||||
y: 10,
|
||||
z: 12,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 3,
|
||||
z: -1.7,
|
||||
duration: CAMERA_DURATION,
|
||||
delay: CAMERA_DELAY,
|
||||
ease: "power2.inOut",
|
||||
},
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
app.camera.rotation,
|
||||
{
|
||||
x: THREE.MathUtils.degToRad(-38.3),
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
{
|
||||
x: THREE.MathUtils.degToRad(-25),
|
||||
y: 0,
|
||||
z: 0,
|
||||
duration: CAMERA_DURATION * 0.9,
|
||||
delay: CAMERA_DELAY,
|
||||
ease: "power2.inOut",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
gsap.fromTo(
|
||||
this.intro,
|
||||
{ fadeOpacity: 0 },
|
||||
{
|
||||
fadeOpacity: 1,
|
||||
duration: FADE_DURATION,
|
||||
delay: FADE_DELAY,
|
||||
ease: "none",
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
navigateTo("/gallery");
|
||||
}, 3150);
|
||||
},
|
||||
|
||||
animateOutro() {
|
||||
const CAMERA_DELAY = 0;
|
||||
const CAMERA_DURATION = 3;
|
||||
|
||||
const FADE_DELAY = 2.3;
|
||||
const FADE_DURATION = 1;
|
||||
|
||||
this.isIntro = false;
|
||||
this.isOutro = true;
|
||||
|
||||
const app = useAppStore();
|
||||
if (app.camera) {
|
||||
gsap.fromTo(
|
||||
app.camera.position,
|
||||
{
|
||||
x: 0,
|
||||
y: 3,
|
||||
z: -1.7,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: 10,
|
||||
z: 12,
|
||||
duration: CAMERA_DURATION,
|
||||
delay: CAMERA_DELAY,
|
||||
ease: "power2.inOut",
|
||||
},
|
||||
);
|
||||
|
||||
gsap.fromTo(
|
||||
app.camera.rotation,
|
||||
{
|
||||
x: THREE.MathUtils.degToRad(-25),
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
{
|
||||
x: THREE.MathUtils.degToRad(-38.3),
|
||||
y: 0,
|
||||
z: 0,
|
||||
duration: CAMERA_DURATION * 0.9,
|
||||
delay: CAMERA_DELAY,
|
||||
ease: "power2.inOut",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
gsap.fromTo(
|
||||
this.outro,
|
||||
{ fadeOpacity: 1 },
|
||||
{
|
||||
fadeOpacity: 0,
|
||||
duration: FADE_DURATION,
|
||||
delay: FADE_DELAY,
|
||||
ease: "none",
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
this.isOutro = false;
|
||||
app.navigateTo("home");
|
||||
}, 3310);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -61,8 +61,13 @@ export const useHomeStore = defineStore("home", {
|
||||
const timeline = gsap.timeline({
|
||||
onComplete: () => {
|
||||
this.isOutro = true;
|
||||
|
||||
const app = useAppStore();
|
||||
app.navigateTo(to);
|
||||
if (to === "gallery") {
|
||||
app.navigateTo("gallery");
|
||||
} else {
|
||||
app.navigateTo(to);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
2
app/types/app.d.ts
vendored
2
app/types/app.d.ts
vendored
@@ -1 +1 @@
|
||||
type AppScreen = "home" | "contact" | "projects" | "settings";
|
||||
type AppScreen = "home" | "contact" | "projects" | "settings" | "gallery";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"home": {
|
||||
"greeting": "Welcome to my website!"
|
||||
"greeting": "Welcome to my website!",
|
||||
"photoGallery": "Photo Gallery"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 470 B |
BIN
public/nds/images/home/bottom-screen/buttons/gallery.webp
Normal file
BIN
public/nds/images/home/bottom-screen/buttons/gallery.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 384 B |
Reference in New Issue
Block a user