From f4538395996bb5dc34f6c31706686bfbbfd12999 Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Mon, 26 Jan 2026 17:54:13 +0100 Subject: [PATCH] feat(achievements): black fade in and out from home and settings screen --- app/components/Achievements/BottomScreen.vue | 15 +-- app/components/Achievements/FadeToBlack.vue | 17 +++ app/components/Achievements/TopScreen.vue | 16 +-- .../Home/BottomScreen/BottomScreen.vue | 2 +- app/components/Home/BottomScreen/Buttons.vue | 6 +- app/components/Home/TopScreen/TopScreen.vue | 4 +- .../Settings/BottomScreen/BottomScreen.vue | 1 + .../BottomScreen/Menus/Clock/Alarm.vue | 3 +- .../Settings/BottomScreen/Menus/Menus.vue | 4 +- .../Settings/TopScreen/TopScreen.vue | 1 + app/stores/achievementsScreen.ts | 115 +++++++++++------- app/stores/app.ts | 2 + app/stores/home.ts | 30 ++++- app/stores/settings.ts | 17 +++ 14 files changed, 161 insertions(+), 72 deletions(-) create mode 100644 app/components/Achievements/FadeToBlack.vue diff --git a/app/components/Achievements/BottomScreen.vue b/app/components/Achievements/BottomScreen.vue index 00ba1bd..96d00d7 100644 --- a/app/components/Achievements/BottomScreen.vue +++ b/app/components/Achievements/BottomScreen.vue @@ -29,21 +29,14 @@ onClick((x, y) => { }); onRender((ctx) => { - assets.images.home.bottomScreen.background.draw(ctx, 0, 0); - - ctx.globalAlpha = store.isIntro - ? store.intro.stage1Opacity - : store.isOutro - ? store.outro.stage1Opacity - : 1; ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, LOGICAL_WIDTH, LOGICAL_HEIGHT); // achievement list (reversed iteration because they appear in cascade) ctx.globalAlpha = store.isIntro - ? store.intro.stage2Opacity + ? store.intro.stage1Opacity : store.isOutro - ? store.outro.stage2Opacity + ? store.outro.stage1Opacity : 1; ctx.font = "7px NDS7"; ctx.textBaseline = "top"; @@ -78,9 +71,9 @@ onRender((ctx) => { } ctx.globalAlpha = store.isIntro - ? store.intro.stage2Opacity + ? store.intro.stage1Opacity : store.isOutro - ? store.outro.stage3Opacity + ? store.outro.stage2Opacity : 1; assets.images.achievements.quit.draw(ctx, QUIT_X, QUIT_Y); }); diff --git a/app/components/Achievements/FadeToBlack.vue b/app/components/Achievements/FadeToBlack.vue new file mode 100644 index 0000000..9511dbd --- /dev/null +++ b/app/components/Achievements/FadeToBlack.vue @@ -0,0 +1,17 @@ + diff --git a/app/components/Achievements/TopScreen.vue b/app/components/Achievements/TopScreen.vue index a61e728..2c59a73 100644 --- a/app/components/Achievements/TopScreen.vue +++ b/app/components/Achievements/TopScreen.vue @@ -2,7 +2,6 @@ const { onRender } = useScreen(); const store = useAchievementsScreen(); const achievementsStore = useAchievementsStore(); -const { assets } = useAssets(); const PROGRESS_BAR_WIDTH = 140; const PROGRESS_BAR_HEIGHT = 10; @@ -15,21 +14,14 @@ onMounted(() => { }); onRender((ctx) => { - assets.images.home.topScreen.background.draw(ctx, 0, 0); - - ctx.globalAlpha = store.isIntro - ? store.intro.stage1Opacity - : store.isOutro - ? store.outro.stage1Opacity - : 1; ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, LOGICAL_WIDTH, LOGICAL_HEIGHT); // header ctx.globalAlpha = store.isIntro - ? store.intro.stage2Opacity + ? store.intro.stage1Opacity : store.isOutro - ? store.outro.stage3Opacity + ? store.outro.stage2Opacity : 1; ctx.fillStyle = "#ffffff"; ctx.textBaseline = "top"; @@ -87,9 +79,9 @@ onRender((ctx) => { // achievement list (reversed iteration because they appear in cascade) ctx.globalAlpha = store.isIntro - ? store.intro.stage2Opacity + ? store.intro.stage1Opacity : store.isOutro - ? store.outro.stage2Opacity + ? store.outro.stage1Opacity : 1; ctx.font = "7px NDS7"; for (let i = ACHIEVEMENTS_TOP_SCREEN_COUNT - 1; i >= 0; i--) { diff --git a/app/components/Home/BottomScreen/BottomScreen.vue b/app/components/Home/BottomScreen/BottomScreen.vue index 0d0aa30..3cad681 100644 --- a/app/components/Home/BottomScreen/BottomScreen.vue +++ b/app/components/Home/BottomScreen/BottomScreen.vue @@ -5,6 +5,6 @@ import Buttons from "./Buttons.vue"; diff --git a/app/components/Home/BottomScreen/Buttons.vue b/app/components/Home/BottomScreen/Buttons.vue index 04a5246..0d60a90 100644 --- a/app/components/Home/BottomScreen/Buttons.vue +++ b/app/components/Home/BottomScreen/Buttons.vue @@ -17,7 +17,7 @@ const { selected, selectorPosition } = useButtonNavigation({ settings: [112, 167, 31, 26], achievements: [225, 167, 31, 26], }, - initialButton: "projects", + initialButton: store.selectedButton, onButtonClick: (button) => { if (button === "theme") throw new Error(`Not implemented: ${button}`); @@ -64,6 +64,10 @@ const { selected, selectorPosition } = useButtonNavigation({ }, }); +watch(selected, (newSelected) => { + store.selectedButton = newSelected; +}); + const getButtonOffset = (button: (typeof selected)["value"]) => { if (selected.value === button) return store.outro.buttonOffsetY; return 0; diff --git a/app/components/Home/TopScreen/TopScreen.vue b/app/components/Home/TopScreen/TopScreen.vue index 1e5e180..126c480 100644 --- a/app/components/Home/TopScreen/TopScreen.vue +++ b/app/components/Home/TopScreen/TopScreen.vue @@ -7,8 +7,7 @@ import StatusBar from "./StatusBar.vue"; const store = useHomeStore(); onMounted(() => { - store.$reset(); - store.animateIntro(); + store.reset(); }); @@ -17,4 +16,5 @@ onMounted(() => { + diff --git a/app/components/Settings/BottomScreen/BottomScreen.vue b/app/components/Settings/BottomScreen/BottomScreen.vue index e8026bd..2df3aab 100644 --- a/app/components/Settings/BottomScreen/BottomScreen.vue +++ b/app/components/Settings/BottomScreen/BottomScreen.vue @@ -8,4 +8,5 @@ import Menus from "./Menus/Menus.vue"; + diff --git a/app/components/Settings/BottomScreen/Menus/Clock/Alarm.vue b/app/components/Settings/BottomScreen/Menus/Clock/Alarm.vue index 3516b43..d819b97 100644 --- a/app/components/Settings/BottomScreen/Menus/Clock/Alarm.vue +++ b/app/components/Settings/BottomScreen/Menus/Clock/Alarm.vue @@ -28,6 +28,7 @@ const { onRender, onClick } = useScreen(); const app = useAppStore(); const store = useSettingsStore(); const achievements = useAchievementsStore(); +const achievementsScreen = useAchievementsScreen(); const confirmationModal = useConfirmationModal(); const handleCancel = () => { @@ -44,7 +45,7 @@ const handleReset = () => { }; const handleVisitAll = () => { - throw new Error("Not implemented"); + achievementsScreen.animateFadeToBlackIntro(); }; onClick((x, y) => { diff --git a/app/components/Settings/BottomScreen/Menus/Menus.vue b/app/components/Settings/BottomScreen/Menus/Menus.vue index 20751f8..f5479e3 100644 --- a/app/components/Settings/BottomScreen/Menus/Menus.vue +++ b/app/components/Settings/BottomScreen/Menus/Menus.vue @@ -52,7 +52,7 @@ const { select, selected, selectorPosition } = useButtonNavigation({ touchScreen: [175, 119, 49, 49], }, - initialButton: "options", + initialButton: settingsStore.selectedButton, onButtonClick: (buttonName) => { if (isSubMenu(buttonName)) { settingsStore.openSubMenu(buttonName); @@ -154,6 +154,8 @@ provide("menusContext", { watch( selected, (newSelected) => { + settingsStore.selectedButton = newSelected; + if (settingsStore.currentSubMenu === null) { if (isMainMenu(newSelected)) { settingsStore.openMenu(newSelected, false); diff --git a/app/components/Settings/TopScreen/TopScreen.vue b/app/components/Settings/TopScreen/TopScreen.vue index 85f4874..e9fdc46 100644 --- a/app/components/Settings/TopScreen/TopScreen.vue +++ b/app/components/Settings/TopScreen/TopScreen.vue @@ -12,4 +12,5 @@ import Notifications from "./Notifications.vue"; + diff --git a/app/stores/achievementsScreen.ts b/app/stores/achievementsScreen.ts index 5cae488..e6974a1 100644 --- a/app/stores/achievementsScreen.ts +++ b/app/stores/achievementsScreen.ts @@ -2,9 +2,14 @@ import gsap from "gsap"; export const useAchievementsScreen = defineStore("achievementsScreen", { state: () => ({ + fadeToBlack: { + opacity: 0, + active: false, + isOutro: false, + }, + intro: { stage1Opacity: 0, - stage2Opacity: 0, itemOffsets: {} as Record, progressBar: 0, }, @@ -12,7 +17,6 @@ export const useAchievementsScreen = defineStore("achievementsScreen", { outro: { stage1Opacity: 1, stage2Opacity: 1, - stage3Opacity: 1, }, isIntro: true, @@ -20,6 +24,51 @@ export const useAchievementsScreen = defineStore("achievementsScreen", { }), actions: { + animateFadeToBlackIntro() { + this.fadeToBlack.active = true; + this.fadeToBlack.isOutro = false; + + gsap + .timeline({ + onComplete: () => { + const app = useAppStore(); + app.navigateTo("achievements"); + }, + }) + .fromTo( + this.fadeToBlack, + { opacity: 0 }, + { + opacity: 1, + duration: 0.4, + ease: "none", + }, + ); + }, + + animateFadeToBlackOutro() { + this.fadeToBlack.active = true; + this.fadeToBlack.isOutro = true; + this.fadeToBlack.opacity = 1; + + gsap + .timeline({ + onComplete: () => { + this.fadeToBlack.active = false; + this.isOutro = false; + }, + }) + .fromTo( + this.fadeToBlack, + { opacity: 1 }, + { + opacity: 0, + duration: 0.4, + ease: "none", + }, + ); + }, + animateIntro() { this.isIntro = true; this.isOutro = false; @@ -41,30 +90,20 @@ export const useAchievementsScreen = defineStore("achievementsScreen", { { stage1Opacity: 0 }, { stage1Opacity: 1, - duration: 0.3, + duration: 0.5, ease: "none", }, - ) - .fromTo( - this.intro, - { stage2Opacity: 0 }, - { - stage2Opacity: 1, - duration: 0.5, - ease: "none", - }, - 0.5, - ) - .fromTo( - this.intro, - { progressBar: 0 }, - { - progressBar: 1, - duration: 1.5, - ease: "power2.out", - }, - 0.25, - ); + 0.5, + ).fromTo( + this.intro, + { progressBar: 0 }, + { + progressBar: 1, + duration: 1.5, + ease: "power2.out", + }, + 0.25, + ); for (let i = 0; i < itemCount; i++) { tl.to( @@ -85,6 +124,14 @@ export const useAchievementsScreen = defineStore("achievementsScreen", { gsap .timeline() + .fromTo( + this.outro, + { stage1Opacity: 1 }, + { + stage1Opacity: 0, + duration: 0.3, + }, + ) .fromTo( this.outro, { stage2Opacity: 1 }, @@ -92,28 +139,12 @@ export const useAchievementsScreen = defineStore("achievementsScreen", { stage2Opacity: 0, duration: 0.3, }, - ) - .fromTo( - this.outro, - { stage3Opacity: 1 }, - { - stage3Opacity: 0, - duration: 0.3, - }, "-=0.15", ) - .fromTo( - this.outro, - { stage1Opacity: 1 }, - { - stage1Opacity: 0, - duration: 0.3, - ease: "none", - }, - ) .call(() => { const app = useAppStore(); - app.navigateTo("home"); + app.navigateTo(app.previousScreen); + this.animateFadeToBlackOutro(); }); }, diff --git a/app/stores/app.ts b/app/stores/app.ts index 3521033..cb8a3c7 100644 --- a/app/stores/app.ts +++ b/app/stores/app.ts @@ -29,6 +29,7 @@ export const useAppStore = defineStore("app", { return { booted: false, settings, + previousScreen: "home" as AppScreen, screen: "home" as AppScreen, camera: null as THREE.Camera | null, }; @@ -40,6 +41,7 @@ export const useAppStore = defineStore("app", { }, navigateTo(screen: AppScreen) { + this.previousScreen = this.screen; this.screen = screen; const achievements = useAchievementsStore(); diff --git a/app/stores/home.ts b/app/stores/home.ts index c487d9c..63b6e87 100644 --- a/app/stores/home.ts +++ b/app/stores/home.ts @@ -1,5 +1,13 @@ import gsap from "gsap"; +export type HomeButton = + | "projects" + | "contact" + | "gallery" + | "theme" + | "settings" + | "achievements"; + export const useHomeStore = defineStore("home", { state: () => ({ intro: { @@ -14,11 +22,25 @@ export const useHomeStore = defineStore("home", { animateTop: false, }, + selectedButton: "projects" as HomeButton, + isIntro: true, isOutro: false, }), actions: { + reset() { + const app = useAppStore(); + if (app.previousScreen === "achievements") { + return; + } + + const selectedButton = this.selectedButton; + this.$reset(); + this.selectedButton = selectedButton; + this.animateIntro(); + }, + animateIntro() { this.isIntro = true; @@ -52,12 +74,18 @@ export const useHomeStore = defineStore("home", { }, animateOutro(to: AppScreen) { + if (to === "achievements") { + const achievementsScreen = useAchievementsScreen(); + achievementsScreen.animateFadeToBlackIntro(); + return; + } + this.isOutro = true; this.outro.animateTop = to !== "settings"; const timeline = gsap.timeline({ onComplete: () => { - this.isOutro = true; + this.isOutro = false; const app = useAppStore(); if (to === "gallery") { diff --git a/app/stores/settings.ts b/app/stores/settings.ts index d543075..8dbb421 100644 --- a/app/stores/settings.ts +++ b/app/stores/settings.ts @@ -1,8 +1,25 @@ +export type SettingsButton = + | "options" + | "optionsLanguage" + | "optionsGbaMode" + | "optionsStartUp" + | "clock" + | "clockAlarm" + | "clockTime" + | "clockDate" + | "user" + | "userBirthday" + | "userUserName" + | "userMessage" + | "userColor" + | "touchScreen"; + export const useSettingsStore = defineStore("settings", { state: () => ({ currentMenu: null as SettingsMenu | null, currentSubMenu: null as SettingsSubMenu | null, menuExpanded: false, + selectedButton: "options" as SettingsButton, }), actions: {