feat(contact): animate outro

This commit is contained in:
2025-11-17 23:13:40 +01:00
parent 8c8af8bac8
commit 8e418ac51d
9 changed files with 118 additions and 29 deletions

View File

@@ -7,10 +7,10 @@ const contactBackgroundImage = useTemplateRef("contactBackgroundImage");
useRender((ctx) => {
if (!homeBackgroundImage.value || !contactBackgroundImage.value) return;
if (store.isIntro) {
ctx.drawImage(homeBackgroundImage.value, 0, 0);
ctx.globalAlpha = store.intro.stage2Opacity;
}
ctx.globalAlpha = store.isIntro
? store.intro.stage2Opacity
: store.outro.stage3Opacity;
ctx.drawImage(contactBackgroundImage.value, 0, 0);
});

View File

@@ -13,7 +13,9 @@ useRender((ctx) => {
if (!topBarImage.value || !bottomBarImage.value || !bottomBarOkImage.value)
return;
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
ctx.globalAlpha = store.isIntro
? store.intro.stage3Opacity
: store.outro.stage2Opacity;
// top bar
ctx.drawImage(topBarImage.value, 0, store.isIntro ? store.intro.topBarY : 0);

View File

@@ -41,6 +41,11 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
},
initialButton: "github",
onButtonClick: async (button) => {
actionateButton(button);
},
});
const actionateButton = async (button: (typeof selectedButton)["value"]) => {
const [action, verb, content] = ACTIONS[button];
if (action === "Copy") {
try {
@@ -53,7 +58,17 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
await navigateTo(content, { open: { target: "_blank " } });
store.pushNotification(`${verb} opened`);
}
},
};
const QUIT_BUTTON: Rect = [31, 172, 80, 18];
const OK_BUTTON: Rect = [144, 172, 80, 18];
useScreenClick((x, y) => {
if (rectContains(QUIT_BUTTON, [x, y])) {
store.animateOutro();
} else if (rectContains(OK_BUTTON, [x, y])) {
actionateButton(selectedButton.value);
}
});
</script>
@@ -63,7 +78,9 @@ const { selectedButton, selectorPosition } = useButtonNavigation({
<Buttons />
<ButtonSelector
:rect="selectorPosition"
:opacity="store.isIntro ? store.intro.stage3Opacity : 1"
:opacity="
store.isIntro ? store.intro.stage3Opacity : store.outro.stage1Opacity
"
/>
<Bars :ok-label="ACTIONS[selectedButton][0]" />

View File

@@ -6,7 +6,9 @@ const buttonsImage = useTemplateRef("buttonsImage");
useRender((ctx) => {
if (!buttonsImage.value) return;
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
ctx.globalAlpha = store.isIntro
? store.intro.stage3Opacity
: store.outro.stage1Opacity;
ctx.drawImage(buttonsImage.value, 31, 32);
});
</script>

View File

@@ -7,12 +7,10 @@ const contactBackgroundImage = useTemplateRef("contactBackgroundImage");
useRender((ctx) => {
if (!homeBackgroundImage.value || !contactBackgroundImage.value) return;
ctx.drawImage(contactBackgroundImage.value, 0, 0);
if (store.isIntro) {
ctx.drawImage(homeBackgroundImage.value, 0, 0);
ctx.globalAlpha = store.intro.stage2Opacity;
}
ctx.globalAlpha = store.isIntro
? store.intro.stage2Opacity
: store.outro.stage3Opacity;
ctx.drawImage(contactBackgroundImage.value, 0, 0);
});

View File

@@ -7,10 +7,14 @@ const thingsImage = useTemplateRef("thingsImage");
useRender((ctx) => {
if (!backgroundImage.value || !thingsImage.value) return;
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.outro.stage2Opacity;
ctx.drawImage(backgroundImage.value, 0, 0);
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
ctx.globalAlpha = store.isIntro
? store.intro.stage3Opacity
: store.outro.stage1Opacity;
ctx.drawImage(thingsImage.value, 0, 0);
});
</script>

View File

@@ -8,6 +8,7 @@ const titleImage = useTemplateRef("titleImage");
useRender((ctx) => {
if (!notificationImage.value || !titleImage.value) return;
ctx.globalAlpha = store.outro.stage2Opacity;
ctx.font = "10px NDS10";
// notifications
@@ -24,7 +25,9 @@ useRender((ctx) => {
}
// title
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
ctx.globalAlpha = store.isIntro
? store.intro.stage1Opacity
: store.outro.stage2Opacity;
ctx.drawImage(
titleImage.value,
21,

View File

@@ -13,7 +13,14 @@ export const useContactStore = defineStore("contact", {
bottomBarY: SCREEN_HEIGHT + 20,
},
outro: {
stage1Opacity: 1,
stage2Opacity: 1,
stage3Opacity: 1,
},
isIntro: true,
isOutro: true,
notifications: [] as string[],
notificationsYOffset: 0,
@@ -23,7 +30,7 @@ export const useContactStore = defineStore("contact", {
animateIntro() {
this.isIntro = true;
const start = 3;
const start = 2;
gsap.fromTo(
this.intro,
@@ -83,5 +90,53 @@ export const useContactStore = defineStore("contact", {
{ notificationsYOffset: 0, duration: 0.075 },
);
},
animateOutro() {
this.isOutro = true;
gsap.fromTo(
this.outro,
{
stage1Opacity: 1,
},
{
stage1Opacity: 0,
duration: 0.2,
ease: "none",
},
);
gsap.fromTo(
this.outro,
{
stage2Opacity: 1,
},
{
stage2Opacity: 0,
duration: 0.25,
delay: 0.25,
ease: "none",
},
);
gsap.fromTo(
this.outro,
{
stage3Opacity: 1,
},
{
stage3Opacity: 0,
duration: 0.3,
delay: 0.5,
ease: "none",
onComplete: () => {
setTimeout(() => {
this.isOutro = false;
navigateTo("/");
}, 2000);
},
},
);
},
},
});

8
app/utils/math.ts Normal file
View File

@@ -0,0 +1,8 @@
export type Point = [x: number, y: number];
export type Rect = [x: number, y: number, width: number, height: number];
export function rectContains(rect: Rect, point: Point): boolean {
const [x, y, width, height] = rect;
const [px, py] = point;
return px >= x && px <= x + width && py >= y && py <= y + height;
}