feat(settings/clock/achievements): intro and outro animation

This commit is contained in:
2026-02-06 13:20:13 +01:00
parent 789b338fde
commit 6bb452a7cf

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { SettingsBottomScreenNumberInput as NumberInput } from "#components";
import { ICONS } from "~/utils/icons";
import gsap from "gsap";
const APP_COLOR_TO_FONT_COLOR: Record<string, string> = {
"#61829a": "#fbfbfb", // cyan
@@ -32,7 +33,68 @@ const achievements = useAchievementsStore();
const achievementsScreen = useAchievementsScreen();
const confirmationModal = useConfirmationModal();
const handleCancel = () => {
const SLIDE_OFFSET = 96;
const SLIDE_DURATION = 0.25;
const ARROW_SLIDE_DELAY = 0.15;
const ARROW_SLIDE_DURATION = 0.15;
const VIEW_ALL_OFFSET = -20;
const animation = reactive({
offsetY: SLIDE_OFFSET,
opacity: 0,
viewAllOffsetY: VIEW_ALL_OFFSET,
});
const obtainedRef =
useTemplateRef<InstanceType<typeof NumberInput>>("obtained");
const totalRef = useTemplateRef<InstanceType<typeof NumberInput>>("total");
const animateIntro = async () => {
await Promise.all([
obtainedRef.value?.animateIntro(),
totalRef.value?.animateIntro(),
gsap
.timeline()
.to(animation, { offsetY: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
.to(animation, { opacity: 1, duration: SLIDE_DURATION, ease: "none" }, 0)
.to(
animation,
{ viewAllOffsetY: 0, duration: ARROW_SLIDE_DURATION, ease: "none" },
SLIDE_DURATION + ARROW_SLIDE_DELAY,
),
]);
};
const animateOutro = async () => {
await Promise.all([
obtainedRef.value?.animateOutro(),
totalRef.value?.animateOutro(),
gsap
.timeline()
.to(
animation,
{
viewAllOffsetY: VIEW_ALL_OFFSET,
duration: ARROW_SLIDE_DURATION,
ease: "none",
},
0,
)
.to(
animation,
{ offsetY: SLIDE_OFFSET, duration: SLIDE_DURATION, ease: "none" },
0,
)
.to(animation, { opacity: 0, duration: SLIDE_DURATION, ease: "none" }, 0),
]);
};
onMounted(() => {
animateIntro();
});
const handleCancel = async () => {
await animateOutro();
store.closeSubMenu();
};
@@ -66,6 +128,8 @@ onRender((ctx) => {
assets.images.home.topScreen.background.draw(ctx, 0, 0);
// slash divider
ctx.globalAlpha = animation.opacity;
ctx.translate(0, animation.offsetY);
ctx.textBaseline = "top";
ctx.fillStyle = "#515151";
ctx.fillRect(7 * 16 - 1, 4 * 16 - 1, 16 * 3 + 1, 16 * 3 + 1);
@@ -80,6 +144,8 @@ onRender((ctx) => {
});
onRender((ctx) => {
ctx.translate(0, animation.viewAllOffsetY);
achievementAssets.viewAllButton.draw(ctx, 127, 2);
achievementAssets.smallTrophy.draw(ctx, 131, 6);
@@ -100,6 +166,7 @@ onRender((ctx) => {
<template>
<NumberInput
ref="obtained"
:model-value="achievements.unlocked.length"
:title="$t('settings.clock.achievements.obtained')"
:x="4 * 16 - 1"
@@ -108,6 +175,7 @@ onRender((ctx) => {
/>
<NumberInput
ref="total"
:model-value="ACHIEVEMENTS.length"
:title="$t('settings.clock.achievements.total')"
:x="9 * 16 - 1"