feat(contact): intro animation

This commit is contained in:
2025-11-17 00:33:37 +01:00
parent 2efdb84234
commit 6383dc0697
18 changed files with 240 additions and 14 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -5,15 +5,17 @@ const props = withDefaults(
y: number; y: number;
width: number; width: number;
height: number; height: number;
animationSpeed?: number; opacity?: number;
}>(), }>(),
{ {
animationSpeed: 0.25, opacity: 1,
}, },
); );
const cornerImage = useTemplateRef("cornerImage"); const cornerImage = useTemplateRef("cornerImage");
const ANIMATION_SPEED = 0.25;
let currentX = props.x; let currentX = props.x;
let currentY = props.y; let currentY = props.y;
let currentWidth = props.width; let currentWidth = props.width;
@@ -38,10 +40,10 @@ useRender((ctx) => {
currentWidth = props.width; currentWidth = props.width;
currentHeight = props.height; currentHeight = props.height;
} else { } else {
currentX += dx * props.animationSpeed; currentX += dx * ANIMATION_SPEED;
currentY += dy * props.animationSpeed; currentY += dy * ANIMATION_SPEED;
currentWidth += dw * props.animationSpeed; currentWidth += dw * ANIMATION_SPEED;
currentHeight += dh * props.animationSpeed; currentHeight += dh * ANIMATION_SPEED;
} }
const x = Math.floor(currentX); const x = Math.floor(currentX);
@@ -49,6 +51,8 @@ useRender((ctx) => {
const w = Math.floor(currentWidth); const w = Math.floor(currentWidth);
const h = Math.floor(currentHeight); const h = Math.floor(currentHeight);
ctx.globalAlpha = props.opacity;
ctx.drawImage(cornerImage.value, x, y); ctx.drawImage(cornerImage.value, x, y);
ctx.save(); ctx.save();

View File

@@ -1,16 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
const backgroundImage = useTemplateRef("backgroundImage"); const store = useContactStore();
const homeBackgroundImage = useTemplateRef("homeBackgroundImage");
const contactBackgroundImage = useTemplateRef("contactBackgroundImage");
callOnce("contactIntro", store.animateIntro);
useRender((ctx) => { useRender((ctx) => {
if (!backgroundImage.value) return; if (!homeBackgroundImage.value || !contactBackgroundImage.value) return;
ctx.drawImage(backgroundImage.value, 0, 0); if (store.isIntro) {
ctx.drawImage(homeBackgroundImage.value, 0, 0);
ctx.globalAlpha = store.intro.stage2Opacity;
}
ctx.drawImage(contactBackgroundImage.value, 0, 0);
}); });
</script> </script>
<template> <template>
<img <img
ref="backgroundImage" ref="homeBackgroundImage"
src="/assets/images/home/bottom-screen/background.png"
hidden
/>
<img
ref="contactBackgroundImage"
src="/assets/images/contact/bottom-screen/background.png" src="/assets/images/contact/bottom-screen/background.png"
hidden hidden
/> />

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
const store = useContactStore();
const topBarImage = useTemplateRef("topBarImage");
const bottomBarImage = useTemplateRef("bottomBarImage");
useRender((ctx) => {
if (!topBarImage.value || !bottomBarImage.value) return;
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
ctx.drawImage(topBarImage.value, 0, store.isIntro ? store.intro.topBarY : 0);
ctx.drawImage(
bottomBarImage.value,
0,
store.isIntro ? store.intro.bottomBarY : SCREEN_HEIGHT - 24,
);
});
</script>
<template>
<img
ref="topBarImage"
src="/assets/images/contact/bottom-screen/top-bar.png"
hidden
/>
<img
ref="bottomBarImage"
src="/assets/images/contact/bottom-screen/bottom-bar.png"
hidden
/>
</template>

View File

@@ -1,6 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import Background from "./Background.vue"; import Background from "./Background.vue";
import Buttons from "./Buttons.vue";
import ButtonSelector from "~/components/Common/ButtonSelector.vue"; import ButtonSelector from "~/components/Common/ButtonSelector.vue";
import Bars from "./Bars.vue";
const store = useContactStore();
const { selectorPosition } = useButtonNavigation({ const { selectorPosition } = useButtonNavigation({
buttons: { buttons: {
@@ -35,10 +39,14 @@ const { selectorPosition } = useButtonNavigation({
<template> <template>
<Background /> <Background />
<Buttons />
<ButtonSelector <ButtonSelector
:x="selectorPosition[0]" :x="selectorPosition[0]"
:y="selectorPosition[1]" :y="selectorPosition[1]"
:width="selectorPosition[2]" :width="selectorPosition[2]"
:height="selectorPosition[3]" :height="selectorPosition[3]"
:opacity="store.isIntro ? store.intro.stage3Opacity : 1"
/> />
<Bars />
</template> </template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
const store = useContactStore();
const buttonsImage = useTemplateRef("buttonsImage");
useRender((ctx) => {
if (!buttonsImage.value) return;
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
ctx.drawImage(buttonsImage.value, 31, 32);
});
</script>
<template>
<img
ref="buttonsImage"
src="/assets/images/contact/bottom-screen/buttons.png"
hidden
/>
</template>

View File

@@ -1,16 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
const backgroundImage = useTemplateRef("backgroundImage"); const store = useContactStore();
const homeBackgroundImage = useTemplateRef("homeBackgroundImage");
const contactBackgroundImage = useTemplateRef("contactBackgroundImage");
useRender((ctx) => { useRender((ctx) => {
if (!backgroundImage.value) return; if (!homeBackgroundImage.value || !contactBackgroundImage.value) return;
ctx.drawImage(backgroundImage.value, 0, 0); ctx.drawImage(contactBackgroundImage.value, 0, 0);
if (store.isIntro) {
ctx.drawImage(homeBackgroundImage.value, 0, 0);
ctx.globalAlpha = store.intro.stage2Opacity;
}
ctx.drawImage(contactBackgroundImage.value, 0, 0);
}); });
</script> </script>
<template> <template>
<img <img
ref="backgroundImage" ref="homeBackgroundImage"
src="/assets/images/home/top-screen/background.png"
hidden
/>
<img
ref="contactBackgroundImage"
src="/assets/images/contact/top-screen/background.png" src="/assets/images/contact/top-screen/background.png"
hidden hidden
/> />

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
const store = useContactStore();
const backgroundImage = useTemplateRef("backgroundImage");
const thingsImage = useTemplateRef("thingsImage");
useRender((ctx) => {
if (!backgroundImage.value || !thingsImage.value) return;
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
ctx.drawImage(backgroundImage.value, 0, 0);
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
ctx.drawImage(thingsImage.value, 0, 0);
});
</script>
<template>
<img
ref="backgroundImage"
src="/assets/images/contact/top-screen/left-bar.png"
hidden
/>
<img
ref="thingsImage"
src="/assets/images/contact/top-screen/left-bar-things.png"
hidden
/>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const store = useContactStore();
const titleImage = useTemplateRef("titleImage");
useRender((ctx) => {
if (!titleImage.value) return;
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
ctx.drawImage(
titleImage.value,
21,
store.isIntro ? store.intro.titleY : SCREEN_HEIGHT - 23,
);
});
</script>
<template>
<img
ref="titleImage"
src="/assets/images/contact/top-screen/title.png"
hidden
/>
</template>

View File

@@ -1,7 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import Background from "./Background.vue"; import Background from "./Background.vue";
import LeftBar from "./LeftBar.vue";
import Title from "./Title.vue";
</script> </script>
<template> <template>
<Background /> <Background />
<LeftBar />
<Title />
</template> </template>

74
app/stores/contact.ts Normal file
View File

@@ -0,0 +1,74 @@
import gsap from "gsap";
export const useContactStore = defineStore("contact", {
state: () => ({
intro: {
stage1Opacity: 0,
stage2Opacity: 0,
stage3Opacity: 0,
titleY: SCREEN_HEIGHT,
topBarY: -20,
bottomBarY: SCREEN_HEIGHT + 20,
},
isIntro: false,
}),
actions: {
animateIntro() {
this.isIntro = true;
const start = 3;
gsap.fromTo(
this.intro,
{
stage1Opacity: 0,
titleY: SCREEN_HEIGHT,
},
{
stage1Opacity: 1,
titleY: SCREEN_HEIGHT - 23,
duration: 0.1,
delay: start,
ease: "none",
},
);
gsap.fromTo(
this.intro,
{
stage2Opacity: 0,
},
{
stage2Opacity: 1,
duration: 0.1,
delay: start + 0.15,
ease: "none",
},
);
gsap.fromTo(
this.intro,
{
stage3Opacity: 0,
topBarY: -20,
bottomBarY: SCREEN_HEIGHT - 4,
},
{
stage3Opacity: 1,
topBarY: 0,
bottomBarY: SCREEN_HEIGHT - 24,
duration: 0.1,
delay: start + 0.15 + 0.15,
ease: "none",
onComplete: () => {
this.isIntro = false;
},
},
);
},
},
});