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: {
|
buttons: {
|
||||||
projects: [31, 23, 193, 49],
|
projects: [31, 23, 193, 49],
|
||||||
contact: [31, 71, 97, 49],
|
contact: [31, 71, 97, 49],
|
||||||
downloadPlay: [127, 71, 97, 49],
|
gallery: [127, 71, 97, 49],
|
||||||
|
|
||||||
theme: [0, 167, 31, 26],
|
theme: [0, 167, 31, 26],
|
||||||
settings: [112, 167, 31, 26],
|
settings: [112, 167, 31, 26],
|
||||||
@@ -19,7 +19,7 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
|
|||||||
},
|
},
|
||||||
initialButton: "projects",
|
initialButton: "projects",
|
||||||
onButtonClick: (button) => {
|
onButtonClick: (button) => {
|
||||||
if (button === "downloadPlay" || button === "theme" || button === "alarm")
|
if (button === "theme" || button === "alarm")
|
||||||
throw new Error(`Not implemented: ${button}`);
|
throw new Error(`Not implemented: ${button}`);
|
||||||
|
|
||||||
store.animateOutro(button);
|
store.animateOutro(button);
|
||||||
@@ -28,15 +28,15 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
|
|||||||
projects: {
|
projects: {
|
||||||
down: "last",
|
down: "last",
|
||||||
left: "contact",
|
left: "contact",
|
||||||
right: "downloadPlay",
|
right: "gallery",
|
||||||
horizontalMode: "preview",
|
horizontalMode: "preview",
|
||||||
},
|
},
|
||||||
contact: {
|
contact: {
|
||||||
up: "projects",
|
up: "projects",
|
||||||
right: "downloadPlay",
|
right: "gallery",
|
||||||
down: "settings",
|
down: "settings",
|
||||||
},
|
},
|
||||||
downloadPlay: {
|
gallery: {
|
||||||
up: "projects",
|
up: "projects",
|
||||||
left: "contact",
|
left: "contact",
|
||||||
down: "settings",
|
down: "settings",
|
||||||
@@ -70,6 +70,19 @@ const getOpacity = (button?: (typeof selectedButton)["value"]) => {
|
|||||||
|
|
||||||
onRender((ctx) => {
|
onRender((ctx) => {
|
||||||
ctx.globalAlpha = getOpacity();
|
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.font = "10px NDS10";
|
||||||
ctx.fillStyle = "#a2a2a2";
|
ctx.fillStyle = "#a2a2a2";
|
||||||
fillTextCentered(ctx, $t("home.greeting"), 79, 135, 140);
|
fillTextCentered(ctx, $t("home.greeting"), 79, 135, 140);
|
||||||
@@ -91,9 +104,9 @@ onRender((ctx) => {
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
:x="128"
|
:x="128"
|
||||||
:y="72 + getButtonOffset('downloadPlay')"
|
:y="72 + getButtonOffset('gallery')"
|
||||||
:opacity="getOpacity('downloadPlay')"
|
:opacity="getOpacity('gallery')"
|
||||||
:image="assets.home.bottomScreen.buttons.downloadPlay"
|
:image="assets.home.bottomScreen.buttons.gallery"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ const { camera, renderer } = useTresContext();
|
|||||||
|
|
||||||
model.scale.set(100, 100, 100);
|
model.scale.set(100, 100, 100);
|
||||||
|
|
||||||
|
const app = useAppStore();
|
||||||
|
watch(
|
||||||
|
() => camera.activeCamera.value,
|
||||||
|
(cam) => {
|
||||||
|
if (cam) app.setCamera(cam);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
meshes.clear();
|
meshes.clear();
|
||||||
model.traverse((child) => {
|
model.traverse((child) => {
|
||||||
if (child instanceof THREE.Mesh) {
|
if (child instanceof THREE.Mesh) {
|
||||||
@@ -199,6 +208,7 @@ const handleClick = (event: MouseEvent) => {
|
|||||||
switch (intersection.object.name) {
|
switch (intersection.object.name) {
|
||||||
case TOP_SCREEN:
|
case TOP_SCREEN:
|
||||||
case BOTTOM_SCREEN: {
|
case BOTTOM_SCREEN: {
|
||||||
|
console.log(intersection);
|
||||||
const canvas =
|
const canvas =
|
||||||
intersection.object.name === TOP_SCREEN
|
intersection.object.name === TOP_SCREEN
|
||||||
? props.topScreenCanvas
|
? props.topScreenCanvas
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ const animateIntro = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const galleryStore = useGalleryStore();
|
||||||
|
|
||||||
const animateOutro = async () => {
|
const animateOutro = async () => {
|
||||||
isAnimating.value = true;
|
isAnimating.value = true;
|
||||||
|
|
||||||
@@ -156,6 +158,7 @@ const animateOutro = async () => {
|
|||||||
typeText(descriptionText, DESCRIPTION, DESCRIPTION_DURATION, true, {
|
typeText(descriptionText, DESCRIPTION, DESCRIPTION_DURATION, true, {
|
||||||
onComplete: async () => {
|
onComplete: async () => {
|
||||||
await sleep(ANIMATION_SLEEP * 1000);
|
await sleep(ANIMATION_SLEEP * 1000);
|
||||||
|
galleryStore.shouldAnimateOutro = true;
|
||||||
router.push("/");
|
router.push("/");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import * as THREE from "three";
|
||||||
import type { Screen as NDSScreen } from "#components";
|
import type { Screen as NDSScreen } from "#components";
|
||||||
|
|
||||||
// TODO: keys doesn't work anymore if 3D modea is disabled
|
const ENABLE_3D = true;
|
||||||
const ENABLE_3D = false;
|
|
||||||
|
|
||||||
type ScreenInstance = InstanceType<typeof NDSScreen>;
|
type ScreenInstance = InstanceType<typeof NDSScreen>;
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ useKeyUp((key) => {
|
|||||||
<TresPerspectiveCamera
|
<TresPerspectiveCamera
|
||||||
:args="[45, 1, 0.001, 1000]"
|
:args="[45, 1, 0.001, 1000]"
|
||||||
:position="[0, 10, 12]"
|
:position="[0, 10, 12]"
|
||||||
:rotation="[-Math.PI / 4.7, 0, 0]"
|
:rotation="[THREE.MathUtils.degToRad(-38.3), 0, 0]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TresAmbientLight />
|
<TresAmbientLight />
|
||||||
@@ -79,6 +79,7 @@ useKeyUp((key) => {
|
|||||||
<ContactTopScreen v-else-if="app.screen === 'contact'" />
|
<ContactTopScreen v-else-if="app.screen === 'contact'" />
|
||||||
<ProjectsTopScreen v-else-if="app.screen === 'projects'" />
|
<ProjectsTopScreen v-else-if="app.screen === 'projects'" />
|
||||||
<SettingsTopScreen v-else-if="app.screen === 'settings'" />
|
<SettingsTopScreen v-else-if="app.screen === 'settings'" />
|
||||||
|
<GalleryTopScreen v-else-if="app.screen === 'gallery'" />
|
||||||
</Screen>
|
</Screen>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -87,6 +88,7 @@ useKeyUp((key) => {
|
|||||||
<ContactBottomScreen v-else-if="app.screen === 'contact'" />
|
<ContactBottomScreen v-else-if="app.screen === 'contact'" />
|
||||||
<ProjectsBottomScreen v-else-if="app.screen === 'projects'" />
|
<ProjectsBottomScreen v-else-if="app.screen === 'projects'" />
|
||||||
<SettingsBottomScreen v-else-if="app.screen === 'settings'" />
|
<SettingsBottomScreen v-else-if="app.screen === 'settings'" />
|
||||||
|
<GalleryBottomScreen v-else-if="app.screen === 'gallery'" />
|
||||||
</Screen>
|
</Screen>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod/mini";
|
import { z } from "zod/mini";
|
||||||
|
import type * as THREE from "three";
|
||||||
|
|
||||||
const STORAGE_ID = "app_settings";
|
const STORAGE_ID = "app_settings";
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ export const useAppStore = defineStore("app", {
|
|||||||
booted: false,
|
booted: false,
|
||||||
settings,
|
settings,
|
||||||
screen: "home" as AppScreen,
|
screen: "home" as AppScreen,
|
||||||
|
camera: null as THREE.Camera | null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -41,6 +43,10 @@ export const useAppStore = defineStore("app", {
|
|||||||
this.screen = screen;
|
this.screen = screen;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setCamera(camera: THREE.Camera) {
|
||||||
|
this.camera = camera;
|
||||||
|
},
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
localStorage.setItem(STORAGE_ID, JSON.stringify(this.settings));
|
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({
|
const timeline = gsap.timeline({
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
this.isOutro = true;
|
this.isOutro = true;
|
||||||
|
|
||||||
const app = useAppStore();
|
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": {
|
"home": {
|
||||||
"greeting": "Welcome to my website!"
|
"greeting": "Welcome to my website!",
|
||||||
|
"photoGallery": "Photo Gallery"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "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